diff --git a/.gitignore b/.gitignore index 182af33a3bb55..5369a0072b746 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,5 @@ ObjectStore build docker/distroless/bazel-* /.apt_generated_tests/ +quarkus.log +replay_*.log diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c0e670e109e83..6fe620f38dec7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -20,7 +20,7 @@ The DCO text is also included verbatim in the [dco.txt](dco.txt) file in the roo This project uses GitHub issues to manage the issues. Open an issue directly in GitHub. If you believe you found a bug, and it's likely possible, please indicate a way to reproduce it, what you are seeing and what you would expect to see. -Don't forget to indicate your Java, Maven and GraalVM version. +Don't forget to indicate your Quarkus, Java, Maven/Gradle and GraalVM version. ## Before you contribute @@ -51,7 +51,6 @@ If you have not done so on this machine, you need to: * Install Git and configure your GitHub access * Install Java SDK (OpenJDK recommended) -* Download and Apache Maven (3.5+) * Install [GraalVM](http://www.graalvm.org/downloads/) (community edition is enough) * Install platform C developer tools: * Linux @@ -71,14 +70,14 @@ On Linux, check [the post-installation guide](https://docs.docker.com/install/li ### IDE Config and Code Style Quarkus has a strictly enforced code style. Code formatting is done by the Eclipse code formatter, using the config files -found in the `ide-config` directory. By default when you run `mvn install` the code will be formatted automatically. +found in the `ide-config` directory. By default when you run `./mvnw install` the code will be formatted automatically. When submitting a pull request the CI build will fail if running the formatter results in any code changes, so it is -recommended that you always run a full maven build before submitting a pull request. +recommended that you always run a full Maven build before submitting a pull request. #### Eclipse Setup Open the *Preferences* window, and then navigate to _Java_ -> _Code Style_ -> _Formatter_. Click _Import_ and then -select the `eclipse-formatter.xml` file in the `ide-config` directory. +select the `eclipse-format.xml` file in the `ide-config` directory. Next navigate to _Java_ -> _Code Style_ -> _Organize Imports_. Click _Import_ and select the `eclipse.importorder` file. @@ -89,7 +88,7 @@ Open the _Preferences_ window, navigate to _Plugins_ and install the [Eclipse Co Restart you IDE, open the *Preferences* window again and navigate to _Other Settings_ -> _Eclipse Code Formatter_. Select _Use the Eclipse Code Formatter_, then change the _Eclipse Java Formatter Config File_ to point to the -`eclipse-formatter.xml` file in the `ide-config` directory. Make sure the _Optimize Imports_ box is ticked, and +`eclipse-format.xml` file in the `ide-config` directory. Make sure the _Optimize Imports_ box is ticked, and select the `eclipse.importorder` file as the import order config file. @@ -97,21 +96,22 @@ select the `eclipse.importorder` file as the import order config file. * Clone the repository: `git clone https://github.com/quarkusio/quarkus.git` * Navigate to the directory: `cd quarkus` -* Invoke `mvn clean install` from the root directory +* Invoke `./mvnw clean install` from the root directory ```bash git clone https://github.com/quarkusio/quarkus.git cd quarkus -mvn clean install +./mvnw clean install # Wait... success! ``` -The default build will create two different native images, which is quite time consuming. You can skip this -by disabling the `native-image` profile: `mvn install -Dno-native`. +The default build does not create native images, which is quite time consuming. + +You can build and test native images in the integration tests supporting it by using `./mvnw install -Dnative`. By default the build will use the native image server. This speeds up the build, but can cause problems due to the cache not being invalidated correctly in some cases. To run a build with a new instance of the server you can use -`mvn install -Dnative-image.new-server=true`. +`./mvnw install -Dnative-image.new-server=true`. ## The small print diff --git a/DECISIONS.adoc b/DECISIONS.adoc new file mode 100644 index 0000000000000..312c24dda19ce --- /dev/null +++ b/DECISIONS.adoc @@ -0,0 +1,69 @@ += Decision-Making Framework + +== Proposal + +Design and process decision proposals should be made to the `quarkus-dev` +Google group (https://groups.google.com/forum/#!forum/quarkus-dev[on the web] +or mailto:quarkus-dev@groups.google.com[over e-mail]). + +== Bake time + +Any significant design or process decision should "bake" +for a minimum of 2 regular working days before becoming policy. +If this time elapses with no discussion, this should be considered +to be assent - with a few caveats. This allows things to move along +without new ideas being stuck in an idle "limbo" for weeks and weeks waiting for +an OK. + +== Notification + +When proposing a design decision, it is important to ensure that the +maintainer(s) and contributor(s) of any impacted areas are aware of +the decision. If a design proposal impacts a system and the +maintainer has not responded (even just to say "OK"), then it is the +responsibility of the proposer to ensure that they are not on PTO +(vacation) or anything of that nature. The proposer should make a +reasonable effort to contact the maintainer(s) and significant contributor(s) +of the given area and ensure that they've at least _seen_ the proposal. +To streamline this, it's a good idea for such maintainers to give their +quick OK to a proposal on a timely basis. + +In cases where it is unclear to the proposer who is responsible for a +given area, this should be stated in the proposal so that the responsible +person or people can be found. + +For a process decision, the same rules apply, except that the +"maintainers" of this "system" should be considered to be the project +lead(s). Again silence means assent - but only if they've _seen_ the +proposal. + +It is the responsibility of all maintainers to stay on top of design +proposals, even if just to respond and say "I need time to think about +it" before the 48 hours expire. + +Notification may take place over the Google group or e-mail list, or +over https://quarkusio.zulipchat.com/#[the online Zulip chat]. + +== Issues and pull requests on GitHub + +If a https://github.com/quarkusio/quarkus/issues[GitHub issue] or +https://github.com/quarkusio/quarkus/pulls[pull request] is created which +proposes a design change, then it should be labelled `Needs Decision`, and the +corresponding discussion should then be held on the Google group as explained +above. A link to the discussion should be placed in a comment of the issue. +Such pull requests should not be merged, nor issues resolved, until the design +decision is resolved according to this process. + +== Disputes + +Because good ideas can come from anywhere, anyone may propose a design +change; likewise, anyone may dispute a proposal. It is the responsibility +of all parties to participate fairly and respectfully. + +In the case of unresolvable disputes, the final decision rests with +the project lead(s); it is their responsibility to +ensure that they are informed on the details of the decision before +ruling. The project leads are encouraged to facilitate resolution +among the disputing parties before resorting to an executive ruling, +but at the end of the day the project needs to move forward and so +this should be kept in mind. diff --git a/azure-mvn-settings.xml b/azure-mvn-settings.xml index d989e5f6a6404..2c0e125266c7e 100644 --- a/azure-mvn-settings.xml +++ b/azure-mvn-settings.xml @@ -1,34 +1,34 @@ - - - jboss-public-repository-group - *,!quarkus-nexus-release,!quarkus-nexus-snapshot - jboss - http://repository.jboss.org/nexus/content/groups/developer/ - - - jboss-nexus + google-mirror-jboss-proxy - jboss-public-repository-group - JBoss Public Repository Group - http://repository.jboss.org/nexus/content/groups/public/ - default - - true - never - - - true - never - + google-maven-central + GCS Maven Central mirror EU + https://maven-central-eu.storage-download.googleapis.com/repos/central/data/ + + + jboss-maven-central-proxy + JBoss Maven Central proxy + https://repository.jboss.org/nexus/content/repositories/central/ + + + google-maven-central + GCS Maven Central mirror EU + https://maven-central-eu.storage-download.googleapis.com/repos/central/data/ + + + jboss-maven-central-proxy + JBoss Maven Central proxy + https://repository.jboss.org/nexus/content/repositories/central/ + + - jboss-nexus + google-mirror-jboss-proxy diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 5494fb5c7448f..04e765199f2e9 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -24,7 +24,7 @@ pr: jobs: - job: Build_Native_Linux - timeoutInMinutes: 120 + timeoutInMinutes: 150 pool: vmImage: 'Ubuntu 16.04' @@ -40,7 +40,7 @@ jobs: displayName: 'Maven Build' inputs: goals: 'install' - options: '-B --settings azure-mvn-settings.xml -Dnative-image.docker-build -Dtest-postgresql -Dnative-image.xmx=5g -Dnative -Dno-format' + options: '-B --settings azure-mvn-settings.xml -Dnative-image.docker-build -Dtest-postgresql -Dtest-elasticsearch -Dnative-image.xmx=6g -Dnative -Dno-format' - job: Windows_Build timeoutInMinutes: 60 diff --git a/bom/pom.xml b/bom/pom.xml index fa7218c5c1366..e5a1157667ec2 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -30,38 +30,41 @@ 1.11 2.1.1.Final - 4.0.0.Beta8 + 4.0.0.Final 0.31.0 - 0.3.1 - 0.2.2 + 0.4.1 + 0.2.3 0.1.5 0.2.0 - 0.33.1 - 2.0.17.Final - 3.6.5.Final + 0.34.0 + 2.0.19.Final + 3.7.0.Final 1.0.0.Final 1.3 - 1.2.1 + 1.3.1 1.0 1.2.1 1.3.5 - 1.0.2 + 1.0.3 1.1.3 - 1.1.0 - 1.2.1 - 1.1.0 - 1.1.0 - 1.0.0 - 0.0.4 - 1.2.1 + 1.1.3 + 1.3.0 + 2.0.4 + 1.1.4 + 1.0.3 + 1.0.3 + 0.0.7 + 1.2.2 + 3.20.9 1.1.1 1 1.3.2 1.0.2.Final 1.0.2.Final 1.2.4.Final - 1.5.1.Final + 1.5.2.Final 1.1.4 + 1.0 2.3.1 1.0.2.Final 2.1.1 @@ -77,71 +80,85 @@ 1.7.1 - 1.0.0-rc13 - 1.0.0.Alpha2 + 1.0.0-rc16 + 1.0.0.Alpha3 2.9.8 - 1.8.3 + 1.9.3 1.2 + 3.8.1 + 1.11 2.0.1.Final 1.3.4 - 3.0.1.b08-redhat-1 - 6.1.0.Alpha3 - 5.4.1.Final + 3.0.1.b11 + 6.1.0.Alpha4 + 5.4.2.Final + 6.0.0.Alpha5 5.9.3.Final 1.1.1.Final 1.4 7.6.0.Final 2.2 - 2.2.5 + 7.0.0 + 2.2.8 1.0.6.Final 2.1.0.Final - 1.2.16 - 2.0 + 1.2.17 + 2.11.2 1.7.25 1.1.0.Final 1.5.0.Final-format-001 - 1.0.0.Final - 2.0.0.Alpha1 + 1.0.1.Final + 2.0.0.Alpha4 1.8.7.Final - 3.0.0.Alpha4 - 3.6.2 - 4.5.6 - 4.4.10 - 4.1.3 + 3.0.0.Beta3 + 3.6.3 + 4.5.7 + 4.4.11 + 4.1.4 2.3.0 1.2.1 - 1.4.197 + 1.4.197 42.2.5 - 2.3.0 + 2.4.1 7.2.1.jre8 1.2.6 3.3.0 4.12 - 5.3.2 - 3.11.1 + 5.4.1 + 3.12.1 + 2.3 10.0.0.Beta2 2.6.2 - 4.1.30.Final + 4.1.34.Final 1.0.2 - 1.10.6 + 1.10.7 3.3.2.Final - 0.0.1 + 0.0.4 + 0.11.2 1.1.0 1.1.0 - 0.8.3.Final + 0.9.2.Final 3.4.10 2.12.2 1.1.0 + 2.2.5 + 1.3.1 1.3.21 - 3.0.0-M1 + 3.0.0-M2 + 0.3.1 1.4.0 0.10.0 2.14.6 3.0.1 - 3.1.5 + 3.1.6 1.6.5 - 1.0.2 - 9.4.14.v20181114 + 1.0.3 + 9.4.15.v20190215 + 5.2.4 + 1.0.3 + + 3.1.0 + 6.0.0 @@ -172,13 +189,11 @@ io.quarkus quarkus-core ${project.version} - provided io.quarkus quarkus-arc ${project.version} - provided @@ -187,19 +202,26 @@ io.quarkus quarkus-caffeine ${project.version} - provided io.quarkus quarkus-jaxb ${project.version} - provided + + + io.quarkus + quarkus-jsonb + ${project.version} + + + io.quarkus + quarkus-jsonp + ${project.version} io.quarkus quarkus-netty ${project.version} - provided @@ -208,217 +230,241 @@ io.quarkus quarkus-agroal ${project.version} - provided io.quarkus quarkus-camel-core ${project.version} - provided + + + io.quarkus + quarkus-camel-aws-s3 + ${project.version} io.quarkus quarkus-camel-salesforce ${project.version} - provided io.quarkus quarkus-camel-netty4-http ${project.version} - provided io.quarkus quarkus-camel-infinispan ${project.version} - provided + + + io.quarkus + quarkus-elasticsearch-rest-client + ${project.version} + + + io.quarkus + quarkus-elytron-security + ${project.version} + + + io.quarkus + quarkus-flyway + ${project.version} + + + io.quarkus + quarkus-hibernate-orm + ${project.version} + + + io.quarkus + quarkus-hibernate-orm-panache + ${project.version} + + + ${project.groupId} + quarkus-hibernate-search-elasticsearch + ${project.version} io.quarkus quarkus-hibernate-validator ${project.version} - provided io.quarkus quarkus-infinispan-client ${project.version} - provided io.quarkus quarkus-jdbc-postgresql ${project.version} - provided io.quarkus quarkus-jdbc-h2 ${project.version} - provided io.quarkus quarkus-jdbc-mariadb ${project.version} - provided io.quarkus quarkus-jdbc-mssql ${project.version} - provided io.quarkus quarkus-kafka-client ${project.version} - provided io.quarkus quarkus-smallrye-health ${project.version} - provided io.quarkus quarkus-smallrye-jwt ${project.version} - provided io.quarkus quarkus-smallrye-reactive-streams-operators ${project.version} - provided io.quarkus quarkus-smallrye-reactive-type-converters ${project.version} - provided io.quarkus quarkus-smallrye-reactive-messaging ${project.version} - provided io.quarkus quarkus-smallrye-reactive-messaging-kafka ${project.version} - provided io.quarkus quarkus-smallrye-metrics ${project.version} - provided io.quarkus quarkus-smallrye-openapi ${project.version} - provided io.quarkus quarkus-smallrye-opentracing ${project.version} - provided io.quarkus quarkus-smallrye-rest-client ${project.version} - provided io.quarkus quarkus-resteasy ${project.version} - provided io.quarkus quarkus-resteasy-jsonb ${project.version} - provided + + + io.quarkus + quarkus-resteasy-server-common + ${project.version} io.quarkus quarkus-jaxrs-json ${project.version} - provided io.quarkus quarkus-narayana-jta ${project.version} - provided io.quarkus quarkus-undertow ${project.version} - provided io.quarkus - quarkus-hibernate-orm + quarkus-smallrye-fault-tolerance ${project.version} - provided io.quarkus - quarkus-smallrye-fault-tolerance + quarkus-vertx ${project.version} - provided io.quarkus - quarkus-vertx + quarkus-vertx-web ${project.version} - provided io.quarkus - quarkus-hibernate-orm-panache + quarkus-reactive-pg-client ${project.version} - provided io.quarkus quarkus-undertow-websockets ${project.version} - provided io.quarkus quarkus-scheduler ${project.version} - provided io.quarkus quarkus-spring-di ${project.version} - provided + + + io.quarkus + quarkus-swagger-ui + ${project.version} io.quarkus quarkus-kotlin ${project.version} - provided io.quarkus quarkus-amazon-lambda ${project.version} - provided + + + io.quarkus + quarkus-amazon-lambda-resteasy + ${project.version} + + + io.quarkus + quarkus-kubernetes + ${project.version} + + + io.quarkus + quarkus-keycloak + ${project.version} @@ -427,13 +473,11 @@ io.quarkus quarkus-junit4 ${project.version} - test io.quarkus quarkus-junit5 ${project.version} - test @@ -450,6 +494,17 @@ + + org.apache.camel + camel-aws-s3 + ${camel.version} + + + com.sun.xml.messaging.saaj + saaj-impl + + + org.apache.camel camel-headersmap @@ -499,6 +554,14 @@ + + + io.ap4k + kubernetes-annotations + ${ap4k.version} + noapt + + org.jboss.logging jboss-logging @@ -509,6 +572,10 @@ jboss-logmanager-embedded ${jboss-logmanager.version} + + com.oracle.substratevm + svm + org.jboss.modules jboss-modules @@ -529,7 +596,6 @@ io.quarkus.gizmo gizmo test-jar - test ${gizmo.version} @@ -537,14 +603,24 @@ commons-beanutils - commons-beanutils-core - ${commons-beanutils-core.version} + commons-beanutils + ${commons-beanutils.version} commons-logging commons-logging ${commons-logging.version} + + org.apache.commons + commons-lang3 + ${commons-lang3.version} + + + commons-codec + commons-codec + ${commons-codec.version} + com.github.ben-manes.caffeine caffeine @@ -575,6 +651,11 @@ jackson-core ${jackson.version} + + com.fasterxml.jackson.dataformat + jackson-dataformat-cbor + ${jackson.version} + com.sun.activation jakarta.activation @@ -632,6 +713,17 @@ opentracing-util ${opentracing.version} + + io.opentracing + opentracing-mock + ${opentracing.version} + + + io.opentracing + opentracing-util + ${opentracing.version} + test-jar + io.opentracing.contrib opentracing-jaxrs2 @@ -656,12 +748,37 @@ io.rest-assured rest-assured ${rest-assured.version} - test javax.activation activation + + + com.sun.xml.bind + jaxb-osgi + + + + + io.rest-assured + json-schema-validator + ${rest-assured.version} + + + javax.activation + activation + + + + com.google.code.findbugs + jsr305 + + + + com.sun.xml.bind + jaxb-osgi + @@ -669,14 +786,12 @@ io.debezium debezium-core ${debezium.version} - test io.debezium debezium-core ${debezium.version} test-jar - test org.scala-lang @@ -845,25 +960,21 @@ junit junit ${junit4.version} - test org.junit.jupiter junit-jupiter-api ${junit.jupiter.version} - test org.junit.jupiter junit-jupiter-params ${junit.jupiter.version} - test org.junit.jupiter junit-jupiter-engine ${junit.jupiter.version} - test log4j @@ -894,7 +1005,6 @@ org.apache.kafka kafka_2.12 ${kafka2.version} - test org.apache.zookeeper @@ -983,13 +1093,16 @@ org.assertj assertj-core ${assertj.version} - test + + + net.minidev + json-smart + ${json-smart.version} org.testcontainers testcontainers ${test-containers.version} - test @@ -1017,11 +1130,35 @@ microprofile-rest-client-api ${microprofile-rest-client.version} + + org.eclipse + yasson + ${yasson.version} + + + org.glassfish + jakarta.json + + + jakarta.json.bind + jakarta.json.bind-api + + + jakarta.json + jakarta.json-api + + + org.glassfish javax.json ${javax.json.version} + + javax.json.bind + javax.json.bind-api + ${javax.json.bind-api.version} + org.glassfish.jaxb jaxb-runtime @@ -1236,7 +1373,7 @@ io.quarkus.arc - arc-runtime + arc ${project.version} @@ -1283,6 +1420,11 @@ wildfly-elytron-auth-server ${wildfly-elytron.version} + + org.wildfly.security + wildfly-elytron-password-impl + ${wildfly-elytron.version} + org.wildfly.security wildfly-elytron-realm @@ -1344,6 +1486,23 @@ hibernate-core ${hibernate-orm.version} + + org.hibernate.search + hibernate-search-backend-elasticsearch + ${hibernate-search.version} + + + org.hibernate.search + hibernate-search-mapper-orm + ${hibernate-search.version} + + + + org.hibernate.common + hibernate-commons-annotations + + + org.glassfish.web el-impl @@ -1364,11 +1523,26 @@ mssql-jdbc ${mssql-jdbc.version} + + org.elasticsearch.client + elasticsearch-rest-client + ${elasticsearch-rest-client.version} + + + org.elasticsearch.client + elasticsearch-rest-client-sniffer + ${elasticsearch-rest-client.version} + io.vertx vertx-core ${vertx.version} + + io.vertx + vertx-web + ${vertx.version} + io.smallrye.reactive smallrye-axle-generator @@ -1399,6 +1573,21 @@ + + io.smallrye.reactive + smallrye-axle-web-client + ${axle-client.version} + + + io.reactiverse + reactive-pg-client + ${reactive-pg-client.version} + + + io.smallrye.reactive + smallrye-axle-postgres-client + ${axle-client.version} + io.vertx vertx-rx-java2 @@ -1548,21 +1737,11 @@ io.smallrye.reactive smallrye-reactive-streams-operators ${smallrye-reactive-streams-operators.version} - - - org.apache.logging.log4j - log4j-core - - - org.apache.logging.log4j - log4j-api - - io.smallrye.reactive - smallrye-converter-api - ${smallrye-reactive-streams-operators.version} + smallrye-reactive-converter-api + ${smallrye-converter-api.version} @@ -1581,6 +1760,16 @@ aws-lambda-java-core ${aws-lambda-java.version} + + com.amazonaws + aws-lambda-java-events + ${aws-lambda-java-events.version} + + + com.amazonaws.serverless + aws-serverless-java-container-core + ${aws-lambda-serverless-java-container.version} + org.apache.maven.shared @@ -1650,6 +1839,40 @@ ${jetty.version} + + org.webjars + swagger-ui + ${swagger-ui.version} + + + + + org.keycloak + keycloak-core + ${keycloak.version} + + + org.keycloak + keycloak-undertow-adapter + ${keycloak.version} + + + org.keycloak + keycloak-authz-client + ${keycloak.version} + + + + org.flywaydb + flyway-core + ${flyway.version} + + + + org.webjars + bootstrap + ${bootstrap.version} + diff --git a/build-parent/pom.xml b/build-parent/pom.xml index 709497284f1c5..1dc63149fe652 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -30,6 +30,10 @@ pom + + 6.12 + 7.0.0 + 5.1.4.RELEASE @@ -39,8 +43,10 @@ - 1.0.0-rc13 + 1.0.0-rc16 3.3.0 + 0.0.4 + 3.6.3 jdt_apt @@ -49,6 +55,10 @@ Supported Maven versions, interpreted as a version range --> [3.5.3,) + + + 3.6.1 + 0.7.6 @@ -65,6 +75,18 @@ + + io.quarkus + quarkus-bootstrap-core + ${project.version} + + + io.quarkus + quarkus-bootstrap-core + ${project.version} + test-jar + test + io.quarkus quarkus-builder @@ -77,7 +99,7 @@ io.quarkus - quarkus-core-runtime + quarkus-core-deployment ${project.version} @@ -87,7 +109,7 @@ io.quarkus - quarkus-arc-runtime + quarkus-arc-deployment ${project.version} @@ -123,7 +145,7 @@ io.quarkus - quarkus-caffeine-runtime + quarkus-caffeine-deployment ${project.version} @@ -133,7 +155,27 @@ io.quarkus - quarkus-jaxb-runtime + quarkus-jaxb-deployment + ${project.version} + + + io.quarkus + quarkus-jsonb + ${project.version} + + + io.quarkus + quarkus-jsonb-deployment + ${project.version} + + + io.quarkus + quarkus-jsonp + ${project.version} + + + io.quarkus + quarkus-jsonp-deployment ${project.version} @@ -143,7 +185,7 @@ io.quarkus - quarkus-netty-runtime + quarkus-netty-deployment ${project.version} @@ -156,7 +198,7 @@ io.quarkus - quarkus-agroal-runtime + quarkus-agroal-deployment ${project.version} @@ -166,7 +208,7 @@ io.quarkus - quarkus-hibernate-validator-runtime + quarkus-hibernate-validator-deployment ${project.version} @@ -176,7 +218,17 @@ io.quarkus - quarkus-camel-core-runtime + quarkus-camel-core-deployment + ${project.version} + + + io.quarkus + quarkus-camel-aws-s3 + ${project.version} + + + io.quarkus + quarkus-camel-aws-s3-deployment ${project.version} @@ -186,7 +238,7 @@ io.quarkus - quarkus-camel-salesforce-runtime + quarkus-camel-salesforce-deployment ${project.version} @@ -196,7 +248,7 @@ io.quarkus - quarkus-camel-netty4-http-runtime + quarkus-camel-netty4-http-deployment ${project.version} @@ -206,7 +258,7 @@ io.quarkus - quarkus-camel-infinispan-runtime + quarkus-camel-infinispan-deployment ${project.version} @@ -216,7 +268,7 @@ io.quarkus - quarkus-jdbc-postgresql-runtime + quarkus-jdbc-postgresql-deployment ${project.version} @@ -226,7 +278,7 @@ io.quarkus - quarkus-jdbc-h2-runtime + quarkus-jdbc-h2-deployment ${project.version} @@ -236,17 +288,17 @@ io.quarkus - quarkus-jdbc-mariadb-runtime + quarkus-jdbc-mariadb-deployment ${project.version} io.quarkus - quarkus-jdbc-mssql + quarkus-jdbc-mssql-deployment ${project.version} io.quarkus - quarkus-jdbc-mssql-runtime + quarkus-jdbc-mssql ${project.version} @@ -256,7 +308,7 @@ io.quarkus - quarkus-kafka-client-runtime + quarkus-kafka-client-deployment ${project.version} @@ -266,7 +318,7 @@ io.quarkus - quarkus-smallrye-reactive-streams-operators-runtime + quarkus-smallrye-reactive-streams-operators-deployment ${project.version} @@ -276,7 +328,7 @@ io.quarkus - quarkus-smallrye-reactive-type-converters-runtime + quarkus-smallrye-reactive-type-converters-deployment ${project.version} @@ -286,7 +338,7 @@ io.quarkus - quarkus-smallrye-fault-tolerance-runtime + quarkus-smallrye-fault-tolerance-deployment ${project.version} @@ -296,7 +348,7 @@ io.quarkus - quarkus-smallrye-health-runtime + quarkus-smallrye-health-deployment ${project.version} @@ -306,7 +358,12 @@ io.quarkus - quarkus-smallrye-metrics-runtime + quarkus-smallrye-metrics-deployment + ${project.version} + + + io.quarkus + quarkus-smallrye-openapi-common-deployment ${project.version} @@ -316,7 +373,7 @@ io.quarkus - quarkus-smallrye-openapi-runtime + quarkus-smallrye-openapi-deployment ${project.version} @@ -326,7 +383,7 @@ io.quarkus - quarkus-smallrye-opentracing-runtime + quarkus-smallrye-opentracing-deployment ${project.version} @@ -336,7 +393,7 @@ io.quarkus - quarkus-smallrye-rest-client-runtime + quarkus-smallrye-rest-client-deployment ${project.version} @@ -346,7 +403,17 @@ io.quarkus - quarkus-jaeger-runtime + quarkus-jaeger-deployment + ${project.version} + + + io.quarkus + quarkus-reactive-pg-client + ${project.version} + + + io.quarkus + quarkus-reactive-pg-client-deployment ${project.version} @@ -356,7 +423,17 @@ io.quarkus - quarkus-resteasy-runtime + quarkus-resteasy-deployment + ${project.version} + + + io.quarkus + quarkus-resteasy-server-common + ${project.version} + + + io.quarkus + quarkus-resteasy-server-common-deployment ${project.version} @@ -366,7 +443,7 @@ io.quarkus - quarkus-resteasy-jsonb-runtime + quarkus-resteasy-jsonb-deployment ${project.version} @@ -376,7 +453,7 @@ io.quarkus - quarkus-smallrye-jwt-runtime + quarkus-smallrye-jwt-deployment ${project.version} @@ -386,7 +463,7 @@ io.quarkus - quarkus-rest-client-runtime + quarkus-rest-client-deployment ${project.version} @@ -396,7 +473,7 @@ io.quarkus - quarkus-resteasy-common-runtime + quarkus-resteasy-common-deployment ${project.version} @@ -406,7 +483,7 @@ io.quarkus - quarkus-narayana-jta-runtime + quarkus-narayana-jta-deployment ${project.version} @@ -416,7 +493,7 @@ io.quarkus - quarkus-undertow-runtime + quarkus-undertow-deployment ${project.version} @@ -426,7 +503,7 @@ io.quarkus - quarkus-hibernate-orm-runtime + quarkus-hibernate-orm-deployment ${project.version} @@ -436,12 +513,37 @@ io.quarkus - quarkus-hibernate-orm-panache-runtime + quarkus-hibernate-orm-panache-deployment + ${project.version} + + + io.quarkus + quarkus-panache-common ${project.version} io.quarkus - quarkus-panache-common-runtime + quarkus-panache-common-deployment + ${project.version} + + + ${project.groupId} + quarkus-hibernate-search-elasticsearch-deployment + ${project.version} + + + ${project.groupId} + quarkus-hibernate-search-elasticsearch + ${project.version} + + + io.quarkus + quarkus-elasticsearch-rest-client + ${project.version} + + + io.quarkus + quarkus-elasticsearch-rest-client-deployment ${project.version} @@ -451,7 +553,17 @@ io.quarkus - quarkus-vertx-runtime + quarkus-vertx-deployment + ${project.version} + + + io.quarkus + quarkus-vertx-web + ${project.version} + + + io.quarkus + quarkus-vertx-web-deployment ${project.version} @@ -461,7 +573,7 @@ io.quarkus - quarkus-undertow-websockets-runtime + quarkus-undertow-websockets-deployment ${project.version} @@ -471,7 +583,7 @@ io.quarkus - quarkus-scheduler-runtime + quarkus-scheduler-deployment ${project.version} @@ -481,7 +593,7 @@ io.quarkus - quarkus-smallrye-reactive-messaging-runtime + quarkus-smallrye-reactive-messaging-deployment ${project.version} @@ -491,7 +603,7 @@ io.quarkus - quarkus-smallrye-reactive-messaging-kafka-runtime + quarkus-smallrye-reactive-messaging-kafka-deployment ${project.version} @@ -499,6 +611,16 @@ quarkus-spring-di ${project.version} + + io.quarkus + quarkus-swagger-ui + ${project.version} + + + io.quarkus + quarkus-swagger-ui-deployment + ${project.version} + io.quarkus quarkus-elytron-security @@ -506,12 +628,12 @@ io.quarkus - quarkus-elytron-security-runtime + quarkus-elytron-security-deployment ${project.version} io.quarkus - quarkus-infinispan-client-runtime + quarkus-infinispan-client-deployment ${project.version} @@ -526,7 +648,17 @@ io.quarkus - quarkus-amazon-lambda-runtime + quarkus-amazon-lambda-deployment + ${project.version} + + + io.quarkus + quarkus-amazon-lambda-resteasy + ${project.version} + + + io.quarkus + quarkus-amazon-lambda-resteasy-deployment ${project.version} @@ -534,6 +666,46 @@ quarkus-kotlin ${project.version} + + io.quarkus + quarkus-kotlin-deployment + ${project.version} + + + io.quarkus + quarkus-kubernetes + ${project.version} + + + io.quarkus + quarkus-kubernetes-deployment + ${project.version} + + + io.quarkus + quarkus-kubernetes-spi + ${project.version} + + + io.quarkus + quarkus-flyway + ${project.version} + + + io.quarkus + quarkus-flyway-deployment + ${project.version} + + + io.quarkus + quarkus-keycloak + ${project.version} + + + io.quarkus + quarkus-keycloak-deployment + ${project.version} + @@ -572,6 +744,12 @@ ${project.version} test + + io.quarkus + quarkus-test-amazon-lambda + ${project.version} + test + io.quarkus quarkus-junit5-internal @@ -606,22 +784,20 @@ ${project.groupId} quarkus-maven-plugin ${project.version} - - - true - - maven-dependency-plugin + io.quarkus + quarkus-bootstrap-maven-plugin + ${project.version} - resolve + extension-descriptor compile - true - ${project.build.outputDirectory}/dependencies.runtime + ${project.groupId}:${project.artifactId}-deployment:${project.version} + @@ -641,8 +817,10 @@ - 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.annotation:jboss-annotations-api_1.2_spec + + org.jboss.spec.javax.annotation:jboss-annotations-api_1.3_spec + org.jboss.logging:jboss-logmanager @@ -656,6 +834,10 @@ org.jboss.spec.javax.servlet:jboss-servlet-api_3.1_spec javax.json:javax.json-api + + org.glassfish:jakarta.json + jakarta.json.bind:jakarta.json.bind-api + jakarta.json:jakarta.json-api @@ -669,7 +851,7 @@ io.fabric8 docker-maven-plugin - 0.28.0 + 0.29.0 maven-javadoc-plugin @@ -683,6 +865,15 @@ org.jboss.jandex jandex-maven-plugin 1.0.5 + + + + + org.jboss + jandex + 2.1.1.Final + + net.revelc.code.formatter @@ -703,11 +894,44 @@ ${format.skip} + + com.google.code.maven-replacer-plugin + maven-resources-plugin + 1.5.3 + + + maven-resources-plugin + 3.1.0 + + + com.github.alexcojocaru + elasticsearch-maven-plugin + ${elasticsearch-maven-plugin.version} + + ${elasticsearch-server.version} + + + + jdk-8-classpath + + [9, + + + + + maven-compiler-plugin + + 8 + + + + + format @@ -789,14 +1013,15 @@ - org.sonatype.plugins + org.sonatype.plugins nexus-staging-maven-plugin - 1.6.3 + ${nexus-staging-maven-plugin.version} true https://oss.sonatype.org/ ossrh true + true diff --git a/core/builder/src/main/java/org/jboss/builder/BuildChain.java b/core/builder/src/main/java/io/quarkus/builder/BuildChain.java similarity index 99% rename from core/builder/src/main/java/org/jboss/builder/BuildChain.java rename to core/builder/src/main/java/io/quarkus/builder/BuildChain.java index 57afb4ee44c64..d0aaf15047f12 100644 --- a/core/builder/src/main/java/org/jboss/builder/BuildChain.java +++ b/core/builder/src/main/java/io/quarkus/builder/BuildChain.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.jboss.builder; +package io.quarkus.builder; import java.util.ArrayList; import java.util.Collection; diff --git a/core/builder/src/main/java/org/jboss/builder/BuildChainBuilder.java b/core/builder/src/main/java/io/quarkus/builder/BuildChainBuilder.java similarity index 99% rename from core/builder/src/main/java/org/jboss/builder/BuildChainBuilder.java rename to core/builder/src/main/java/io/quarkus/builder/BuildChainBuilder.java index 7cfb396d11f04..7a284377c3d85 100644 --- a/core/builder/src/main/java/org/jboss/builder/BuildChainBuilder.java +++ b/core/builder/src/main/java/io/quarkus/builder/BuildChainBuilder.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.jboss.builder; +package io.quarkus.builder; import java.io.BufferedWriter; import java.io.FileOutputStream; @@ -35,11 +35,12 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.jboss.builder.item.BuildItem; -import org.jboss.builder.item.NamedBuildItem; -import org.jboss.builder.item.SymbolicBuildItem; 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. * diff --git a/core/builder/src/main/java/org/jboss/builder/BuildContext.java b/core/builder/src/main/java/io/quarkus/builder/BuildContext.java similarity index 97% rename from core/builder/src/main/java/org/jboss/builder/BuildContext.java rename to core/builder/src/main/java/io/quarkus/builder/BuildContext.java index 6220ceab406d9..2407188ce5b4f 100644 --- a/core/builder/src/main/java/org/jboss/builder/BuildContext.java +++ b/core/builder/src/main/java/io/quarkus/builder/BuildContext.java @@ -14,9 +14,9 @@ * limitations under the License. */ -package org.jboss.builder; +package io.quarkus.builder; -import static org.jboss.builder.Execution.*; +import static io.quarkus.builder.Execution.*; import java.util.ArrayList; import java.util.Collections; @@ -26,15 +26,16 @@ import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicInteger; -import org.jboss.builder.diag.Diagnostic; -import org.jboss.builder.item.BuildItem; -import org.jboss.builder.item.MultiBuildItem; -import org.jboss.builder.item.NamedBuildItem; -import org.jboss.builder.item.NamedMultiBuildItem; -import org.jboss.builder.item.SimpleBuildItem; -import org.jboss.builder.location.Location; import org.wildfly.common.Assert; +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; + /** * The context passed to a deployer's operation. * diff --git a/core/builder/src/main/java/org/jboss/builder/BuildException.java b/core/builder/src/main/java/io/quarkus/builder/BuildException.java similarity index 98% rename from core/builder/src/main/java/org/jboss/builder/BuildException.java rename to core/builder/src/main/java/io/quarkus/builder/BuildException.java index e78a3cef75691..78712e039657c 100644 --- a/core/builder/src/main/java/org/jboss/builder/BuildException.java +++ b/core/builder/src/main/java/io/quarkus/builder/BuildException.java @@ -14,13 +14,14 @@ * limitations under the License. */ -package org.jboss.builder; +package io.quarkus.builder; import java.util.List; -import org.jboss.builder.diag.Diagnostic; import org.wildfly.common.Assert; +import io.quarkus.builder.diag.Diagnostic; + /** * @author David M. Lloyd */ diff --git a/core/builder/src/main/java/org/jboss/builder/BuildExecutionBuilder.java b/core/builder/src/main/java/io/quarkus/builder/BuildExecutionBuilder.java similarity index 98% rename from core/builder/src/main/java/org/jboss/builder/BuildExecutionBuilder.java rename to core/builder/src/main/java/io/quarkus/builder/BuildExecutionBuilder.java index b381cb85c6312..4ef1f6d3bbae2 100644 --- a/core/builder/src/main/java/org/jboss/builder/BuildExecutionBuilder.java +++ b/core/builder/src/main/java/io/quarkus/builder/BuildExecutionBuilder.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.jboss.builder; +package io.quarkus.builder; import java.util.ArrayList; import java.util.Collections; @@ -22,10 +22,11 @@ import java.util.List; import java.util.Map; -import org.jboss.builder.item.BuildItem; -import org.jboss.builder.item.NamedBuildItem; import org.wildfly.common.Assert; +import io.quarkus.builder.item.BuildItem; +import io.quarkus.builder.item.NamedBuildItem; + /** * A builder for a deployer execution. * diff --git a/core/builder/src/main/java/org/jboss/builder/BuildProvider.java b/core/builder/src/main/java/io/quarkus/builder/BuildProvider.java similarity index 98% rename from core/builder/src/main/java/org/jboss/builder/BuildProvider.java rename to core/builder/src/main/java/io/quarkus/builder/BuildProvider.java index e595e6bcd26d9..cedc0fdf9a313 100644 --- a/core/builder/src/main/java/org/jboss/builder/BuildProvider.java +++ b/core/builder/src/main/java/io/quarkus/builder/BuildProvider.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.jboss.builder; +package io.quarkus.builder; import java.util.ServiceLoader; diff --git a/core/builder/src/main/java/org/jboss/builder/BuildResult.java b/core/builder/src/main/java/io/quarkus/builder/BuildResult.java similarity index 95% rename from core/builder/src/main/java/org/jboss/builder/BuildResult.java rename to core/builder/src/main/java/io/quarkus/builder/BuildResult.java index 276cc8b1a1e86..1cc044f95d0d4 100644 --- a/core/builder/src/main/java/org/jboss/builder/BuildResult.java +++ b/core/builder/src/main/java/io/quarkus/builder/BuildResult.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.jboss.builder; +package io.quarkus.builder; import java.util.ArrayList; import java.util.Collections; @@ -23,10 +23,10 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; -import org.jboss.builder.diag.Diagnostic; -import org.jboss.builder.item.BuildItem; -import org.jboss.builder.item.MultiBuildItem; -import org.jboss.builder.item.SimpleBuildItem; +import io.quarkus.builder.diag.Diagnostic; +import io.quarkus.builder.item.BuildItem; +import io.quarkus.builder.item.MultiBuildItem; +import io.quarkus.builder.item.SimpleBuildItem; /** * The final result of a successful deployment operation. diff --git a/core/builder/src/main/java/org/jboss/builder/BuildStep.java b/core/builder/src/main/java/io/quarkus/builder/BuildStep.java similarity index 97% rename from core/builder/src/main/java/org/jboss/builder/BuildStep.java rename to core/builder/src/main/java/io/quarkus/builder/BuildStep.java index 45727f19a6241..f9cbe17a09e90 100644 --- a/core/builder/src/main/java/org/jboss/builder/BuildStep.java +++ b/core/builder/src/main/java/io/quarkus/builder/BuildStep.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.jboss.builder; +package io.quarkus.builder; /** * A single atomic unit of build work. A build step either succeeds or it does not, with no intermediate states diff --git a/core/builder/src/main/java/org/jboss/builder/BuildStepBuilder.java b/core/builder/src/main/java/io/quarkus/builder/BuildStepBuilder.java similarity index 99% rename from core/builder/src/main/java/org/jboss/builder/BuildStepBuilder.java rename to core/builder/src/main/java/io/quarkus/builder/BuildStepBuilder.java index ca27c75b6f909..14476877f6307 100644 --- a/core/builder/src/main/java/org/jboss/builder/BuildStepBuilder.java +++ b/core/builder/src/main/java/io/quarkus/builder/BuildStepBuilder.java @@ -14,17 +14,18 @@ * limitations under the License. */ -package org.jboss.builder; +package io.quarkus.builder; import java.util.HashMap; import java.util.Map; import java.util.Set; -import org.jboss.builder.item.BuildItem; -import org.jboss.builder.item.NamedBuildItem; -import org.jboss.builder.item.SymbolicBuildItem; import org.wildfly.common.Assert; +import io.quarkus.builder.item.BuildItem; +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 * a destructor for items it produces, which will be run (in indeterminate order) at the end of processing. diff --git a/core/builder/src/main/java/org/jboss/builder/ChainBuildException.java b/core/builder/src/main/java/io/quarkus/builder/ChainBuildException.java similarity index 98% rename from core/builder/src/main/java/org/jboss/builder/ChainBuildException.java rename to core/builder/src/main/java/io/quarkus/builder/ChainBuildException.java index f7eb06c5592b4..a19de2963338b 100644 --- a/core/builder/src/main/java/org/jboss/builder/ChainBuildException.java +++ b/core/builder/src/main/java/io/quarkus/builder/ChainBuildException.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.jboss.builder; +package io.quarkus.builder; /** * The exception thrown when a deployer chain build fails. diff --git a/core/builder/src/main/java/org/jboss/builder/Constraint.java b/core/builder/src/main/java/io/quarkus/builder/Constraint.java similarity index 92% rename from core/builder/src/main/java/org/jboss/builder/Constraint.java rename to core/builder/src/main/java/io/quarkus/builder/Constraint.java index b6a0e448231ac..a7fded3c00035 100644 --- a/core/builder/src/main/java/org/jboss/builder/Constraint.java +++ b/core/builder/src/main/java/io/quarkus/builder/Constraint.java @@ -14,11 +14,12 @@ * limitations under the License. */ -package org.jboss.builder; +package io.quarkus.builder; /** */ enum Constraint { - REAL, ORDER_ONLY, + REAL, + ORDER_ONLY, ; } diff --git a/core/builder/src/main/java/org/jboss/builder/Consume.java b/core/builder/src/main/java/io/quarkus/builder/Consume.java similarity index 98% rename from core/builder/src/main/java/org/jboss/builder/Consume.java rename to core/builder/src/main/java/io/quarkus/builder/Consume.java index 692dd3e62b5bd..973cdae603ea3 100644 --- a/core/builder/src/main/java/org/jboss/builder/Consume.java +++ b/core/builder/src/main/java/io/quarkus/builder/Consume.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.jboss.builder; +package io.quarkus.builder; /** */ diff --git a/core/builder/src/main/java/org/jboss/builder/ConsumeFlag.java b/core/builder/src/main/java/io/quarkus/builder/ConsumeFlag.java similarity index 96% rename from core/builder/src/main/java/org/jboss/builder/ConsumeFlag.java rename to core/builder/src/main/java/io/quarkus/builder/ConsumeFlag.java index d0d593dca741a..e30f9628ab211 100644 --- a/core/builder/src/main/java/org/jboss/builder/ConsumeFlag.java +++ b/core/builder/src/main/java/io/quarkus/builder/ConsumeFlag.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.jboss.builder; +package io.quarkus.builder; /** */ diff --git a/core/builder/src/main/java/org/jboss/builder/ConsumeFlags.java b/core/builder/src/main/java/io/quarkus/builder/ConsumeFlags.java similarity index 98% rename from core/builder/src/main/java/org/jboss/builder/ConsumeFlags.java rename to core/builder/src/main/java/io/quarkus/builder/ConsumeFlags.java index fed0e3bfd15f9..bcb4dadc4dc72 100644 --- a/core/builder/src/main/java/org/jboss/builder/ConsumeFlags.java +++ b/core/builder/src/main/java/io/quarkus/builder/ConsumeFlags.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.jboss.builder; +package io.quarkus.builder; import org.wildfly.common.Assert; import org.wildfly.common.flags.Flags; diff --git a/core/builder/src/main/java/org/jboss/builder/Execution.java b/core/builder/src/main/java/io/quarkus/builder/Execution.java similarity index 97% rename from core/builder/src/main/java/org/jboss/builder/Execution.java rename to core/builder/src/main/java/io/quarkus/builder/Execution.java index 7e93c6cec743f..1423fedd0a0a7 100644 --- a/core/builder/src/main/java/org/jboss/builder/Execution.java +++ b/core/builder/src/main/java/io/quarkus/builder/Execution.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.jboss.builder; +package io.quarkus.builder; import static java.lang.Math.max; import static java.util.concurrent.locks.LockSupport.*; @@ -28,18 +28,19 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; -import org.jboss.builder.diag.Diagnostic; -import org.jboss.builder.item.BuildItem; import org.jboss.logging.Logger; import org.jboss.threads.EnhancedQueueExecutor; import org.jboss.threads.JBossExecutors; import org.jboss.threads.JBossThreadFactory; +import io.quarkus.builder.diag.Diagnostic; +import io.quarkus.builder.item.BuildItem; + /** */ final class Execution { - static final Logger log = Logger.getLogger("org.jboss.builder"); + static final Logger log = Logger.getLogger("io.quarkus.builder"); private final BuildChain chain; private final ConcurrentHashMap singles; diff --git a/core/builder/src/main/java/org/jboss/builder/FinalStep.java b/core/builder/src/main/java/io/quarkus/builder/FinalStep.java similarity index 96% rename from core/builder/src/main/java/org/jboss/builder/FinalStep.java rename to core/builder/src/main/java/io/quarkus/builder/FinalStep.java index 1acd541493161..8b669c5540c35 100644 --- a/core/builder/src/main/java/org/jboss/builder/FinalStep.java +++ b/core/builder/src/main/java/io/quarkus/builder/FinalStep.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.jboss.builder; +package io.quarkus.builder; /** */ diff --git a/core/builder/src/main/java/org/jboss/builder/ItemId.java b/core/builder/src/main/java/io/quarkus/builder/ItemId.java similarity index 91% rename from core/builder/src/main/java/org/jboss/builder/ItemId.java rename to core/builder/src/main/java/io/quarkus/builder/ItemId.java index 32a119a652bed..51d93248b8b77 100644 --- a/core/builder/src/main/java/org/jboss/builder/ItemId.java +++ b/core/builder/src/main/java/io/quarkus/builder/ItemId.java @@ -14,16 +14,17 @@ * limitations under the License. */ -package org.jboss.builder; +package io.quarkus.builder; import java.util.Objects; -import org.jboss.builder.item.BuildItem; -import org.jboss.builder.item.MultiBuildItem; -import org.jboss.builder.item.NamedBuildItem; -import org.jboss.builder.item.NamedMultiBuildItem; import org.wildfly.common.Assert; +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 { diff --git a/core/builder/src/main/java/org/jboss/builder/Messages.java b/core/builder/src/main/java/io/quarkus/builder/Messages.java similarity index 97% rename from core/builder/src/main/java/org/jboss/builder/Messages.java rename to core/builder/src/main/java/io/quarkus/builder/Messages.java index f1e6bdaec2e1f..f56486e6d12a8 100644 --- a/core/builder/src/main/java/org/jboss/builder/Messages.java +++ b/core/builder/src/main/java/io/quarkus/builder/Messages.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.jboss.builder; +package io.quarkus.builder; /** */ diff --git a/core/builder/src/main/java/org/jboss/builder/Produce.java b/core/builder/src/main/java/io/quarkus/builder/Produce.java similarity index 98% rename from core/builder/src/main/java/org/jboss/builder/Produce.java rename to core/builder/src/main/java/io/quarkus/builder/Produce.java index 9fa4b8f6fe58a..9f64063ac5e7b 100644 --- a/core/builder/src/main/java/org/jboss/builder/Produce.java +++ b/core/builder/src/main/java/io/quarkus/builder/Produce.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.jboss.builder; +package io.quarkus.builder; /** */ diff --git a/core/builder/src/main/java/org/jboss/builder/ProduceFlag.java b/core/builder/src/main/java/io/quarkus/builder/ProduceFlag.java similarity index 96% rename from core/builder/src/main/java/org/jboss/builder/ProduceFlag.java rename to core/builder/src/main/java/io/quarkus/builder/ProduceFlag.java index a5c28ed05bd46..a536a04f7ec4c 100644 --- a/core/builder/src/main/java/org/jboss/builder/ProduceFlag.java +++ b/core/builder/src/main/java/io/quarkus/builder/ProduceFlag.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.jboss.builder; +package io.quarkus.builder; /** */ diff --git a/core/builder/src/main/java/org/jboss/builder/ProduceFlags.java b/core/builder/src/main/java/io/quarkus/builder/ProduceFlags.java similarity index 98% rename from core/builder/src/main/java/org/jboss/builder/ProduceFlags.java rename to core/builder/src/main/java/io/quarkus/builder/ProduceFlags.java index c7112ae2b0c42..57f288af6c383 100644 --- a/core/builder/src/main/java/org/jboss/builder/ProduceFlags.java +++ b/core/builder/src/main/java/io/quarkus/builder/ProduceFlags.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.jboss.builder; +package io.quarkus.builder; import org.wildfly.common.Assert; import org.wildfly.common.flags.Flags; diff --git a/core/builder/src/main/java/org/jboss/builder/StepInfo.java b/core/builder/src/main/java/io/quarkus/builder/StepInfo.java similarity index 98% rename from core/builder/src/main/java/org/jboss/builder/StepInfo.java rename to core/builder/src/main/java/io/quarkus/builder/StepInfo.java index d80b5c2ecb874..32f56f8a26d2f 100644 --- a/core/builder/src/main/java/org/jboss/builder/StepInfo.java +++ b/core/builder/src/main/java/io/quarkus/builder/StepInfo.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.jboss.builder; +package io.quarkus.builder; import java.util.Set; diff --git a/core/builder/src/main/java/org/jboss/builder/Version.java b/core/builder/src/main/java/io/quarkus/builder/Version.java similarity index 95% rename from core/builder/src/main/java/org/jboss/builder/Version.java rename to core/builder/src/main/java/io/quarkus/builder/Version.java index 3307929b93262..9ae6b6daca175 100644 --- a/core/builder/src/main/java/org/jboss/builder/Version.java +++ b/core/builder/src/main/java/io/quarkus/builder/Version.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.jboss.builder; +package io.quarkus.builder; import java.io.IOException; import java.io.InputStream; @@ -70,6 +70,6 @@ public static String getJarName() { * @param args ignored */ public static void main(String[] args) { - System.out.printf("WildFly Deployer (%s) version %s%n", JAR_NAME, VERSION); + System.out.printf("Quarkus Builder (%s) version %s%n", JAR_NAME, VERSION); } } diff --git a/core/builder/src/main/java/org/jboss/builder/Version.properties b/core/builder/src/main/java/io/quarkus/builder/Version.properties similarity index 100% rename from core/builder/src/main/java/org/jboss/builder/Version.properties rename to core/builder/src/main/java/io/quarkus/builder/Version.properties diff --git a/core/builder/src/main/java/org/jboss/builder/diag/Diagnostic.java b/core/builder/src/main/java/io/quarkus/builder/diag/Diagnostic.java similarity index 94% rename from core/builder/src/main/java/org/jboss/builder/diag/Diagnostic.java rename to core/builder/src/main/java/io/quarkus/builder/diag/Diagnostic.java index aa1f33d2c5169..b7100b1d36f64 100644 --- a/core/builder/src/main/java/org/jboss/builder/diag/Diagnostic.java +++ b/core/builder/src/main/java/io/quarkus/builder/diag/Diagnostic.java @@ -14,13 +14,14 @@ * limitations under the License. */ -package org.jboss.builder.diag; +package io.quarkus.builder.diag; import java.io.PrintStream; -import org.jboss.builder.location.Location; import org.wildfly.common.Assert; +import io.quarkus.builder.location.Location; + /** */ public final class Diagnostic { @@ -88,7 +89,9 @@ public Level getLevel() { } public enum Level { - ERROR("error"), WARN("warning"), NOTE("note"), + ERROR("error"), + WARN("warning"), + NOTE("note"), ; private final String name; diff --git a/core/builder/src/main/java/org/jboss/builder/item/BuildItem.java b/core/builder/src/main/java/io/quarkus/builder/item/BuildItem.java similarity index 98% rename from core/builder/src/main/java/org/jboss/builder/item/BuildItem.java rename to core/builder/src/main/java/io/quarkus/builder/item/BuildItem.java index 67dfa51963be5..d0c6610699169 100644 --- a/core/builder/src/main/java/org/jboss/builder/item/BuildItem.java +++ b/core/builder/src/main/java/io/quarkus/builder/item/BuildItem.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.jboss.builder.item; +package io.quarkus.builder.item; import java.lang.reflect.Modifier; diff --git a/core/builder/src/main/java/org/jboss/builder/item/EnumNamedBuildItem.java b/core/builder/src/main/java/io/quarkus/builder/item/EnumNamedBuildItem.java similarity index 96% rename from core/builder/src/main/java/org/jboss/builder/item/EnumNamedBuildItem.java rename to core/builder/src/main/java/io/quarkus/builder/item/EnumNamedBuildItem.java index ed3a390b4424c..fa79da1ba2e6c 100644 --- a/core/builder/src/main/java/org/jboss/builder/item/EnumNamedBuildItem.java +++ b/core/builder/src/main/java/io/quarkus/builder/item/EnumNamedBuildItem.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.jboss.builder.item; +package io.quarkus.builder.item; /** * A build item which has a name that is an enum constant. diff --git a/core/builder/src/main/java/org/jboss/builder/item/EnumNamedMultiBuildItem.java b/core/builder/src/main/java/io/quarkus/builder/item/EnumNamedMultiBuildItem.java similarity index 96% rename from core/builder/src/main/java/org/jboss/builder/item/EnumNamedMultiBuildItem.java rename to core/builder/src/main/java/io/quarkus/builder/item/EnumNamedMultiBuildItem.java index 587ec08c9fcb7..ec60840a1425e 100644 --- a/core/builder/src/main/java/org/jboss/builder/item/EnumNamedMultiBuildItem.java +++ b/core/builder/src/main/java/io/quarkus/builder/item/EnumNamedMultiBuildItem.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.jboss.builder.item; +package io.quarkus.builder.item; /** * A multi build item which has a name that is an enum constant. diff --git a/core/builder/src/main/java/org/jboss/builder/item/MultiBuildItem.java b/core/builder/src/main/java/io/quarkus/builder/item/MultiBuildItem.java similarity index 96% rename from core/builder/src/main/java/org/jboss/builder/item/MultiBuildItem.java rename to core/builder/src/main/java/io/quarkus/builder/item/MultiBuildItem.java index d9a5f5402d610..2b835569fab9b 100644 --- a/core/builder/src/main/java/org/jboss/builder/item/MultiBuildItem.java +++ b/core/builder/src/main/java/io/quarkus/builder/item/MultiBuildItem.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.jboss.builder.item; +package io.quarkus.builder.item; /** * A build item that may be produced multiple times, and consumed as a {@code List}. {@code MultiBuildItem} subclasses diff --git a/core/builder/src/main/java/org/jboss/builder/item/NamedBuildItem.java b/core/builder/src/main/java/io/quarkus/builder/item/NamedBuildItem.java similarity index 96% rename from core/builder/src/main/java/org/jboss/builder/item/NamedBuildItem.java rename to core/builder/src/main/java/io/quarkus/builder/item/NamedBuildItem.java index efb6834b8214d..714134b72f431 100644 --- a/core/builder/src/main/java/org/jboss/builder/item/NamedBuildItem.java +++ b/core/builder/src/main/java/io/quarkus/builder/item/NamedBuildItem.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.jboss.builder.item; +package io.quarkus.builder.item; /** * A build item that can occur more than once in a build, discriminated by name. diff --git a/core/builder/src/main/java/org/jboss/builder/item/NamedMultiBuildItem.java b/core/builder/src/main/java/io/quarkus/builder/item/NamedMultiBuildItem.java similarity index 96% rename from core/builder/src/main/java/org/jboss/builder/item/NamedMultiBuildItem.java rename to core/builder/src/main/java/io/quarkus/builder/item/NamedMultiBuildItem.java index a6016d96004e2..6a10eedeebaa5 100644 --- a/core/builder/src/main/java/org/jboss/builder/item/NamedMultiBuildItem.java +++ b/core/builder/src/main/java/io/quarkus/builder/item/NamedMultiBuildItem.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.jboss.builder.item; +package io.quarkus.builder.item; /** * A build item that can occur more than once in a build, discriminated by name. diff --git a/core/builder/src/main/java/org/jboss/builder/item/SimpleBuildItem.java b/core/builder/src/main/java/io/quarkus/builder/item/SimpleBuildItem.java similarity index 96% rename from core/builder/src/main/java/org/jboss/builder/item/SimpleBuildItem.java rename to core/builder/src/main/java/io/quarkus/builder/item/SimpleBuildItem.java index 8722be5fd94fe..5815526ce04ae 100644 --- a/core/builder/src/main/java/org/jboss/builder/item/SimpleBuildItem.java +++ b/core/builder/src/main/java/io/quarkus/builder/item/SimpleBuildItem.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.jboss.builder.item; +package io.quarkus.builder.item; /** * A single-valued build item which is identified by its type. diff --git a/core/builder/src/main/java/org/jboss/builder/item/SymbolicBuildItem.java b/core/builder/src/main/java/io/quarkus/builder/item/SymbolicBuildItem.java similarity index 97% rename from core/builder/src/main/java/org/jboss/builder/item/SymbolicBuildItem.java rename to core/builder/src/main/java/io/quarkus/builder/item/SymbolicBuildItem.java index b9ea763f5b07e..85fe698711b7a 100644 --- a/core/builder/src/main/java/org/jboss/builder/item/SymbolicBuildItem.java +++ b/core/builder/src/main/java/io/quarkus/builder/item/SymbolicBuildItem.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.jboss.builder.item; +package io.quarkus.builder.item; /** * The symbolic build item. diff --git a/core/builder/src/main/java/org/jboss/builder/location/Location.java b/core/builder/src/main/java/io/quarkus/builder/location/Location.java similarity index 96% rename from core/builder/src/main/java/org/jboss/builder/location/Location.java rename to core/builder/src/main/java/io/quarkus/builder/location/Location.java index 2c61c9b9635e5..b4866a4d7294f 100644 --- a/core/builder/src/main/java/org/jboss/builder/location/Location.java +++ b/core/builder/src/main/java/io/quarkus/builder/location/Location.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.jboss.builder.location; +package io.quarkus.builder.location; import java.util.Objects; diff --git a/core/builder/src/test/java/org/jboss/builder/BasicTests.java b/core/builder/src/test/java/io/quarkus/builder/BasicTests.java similarity index 98% rename from core/builder/src/test/java/org/jboss/builder/BasicTests.java rename to core/builder/src/test/java/io/quarkus/builder/BasicTests.java index 3c00f7b6f81eb..e3097b07a537b 100644 --- a/core/builder/src/test/java/org/jboss/builder/BasicTests.java +++ b/core/builder/src/test/java/io/quarkus/builder/BasicTests.java @@ -14,15 +14,16 @@ * limitations under the License. */ -package org.jboss.builder; +package io.quarkus.builder; import static org.junit.Assert.*; import java.util.concurrent.atomic.AtomicBoolean; -import org.jboss.builder.item.SimpleBuildItem; import org.junit.Test; +import io.quarkus.builder.item.SimpleBuildItem; + /** */ public class BasicTests { diff --git a/core/creator/pom.xml b/core/creator/pom.xml index 475152c370a47..9d09f791b7c56 100644 --- a/core/creator/pom.xml +++ b/core/creator/pom.xml @@ -32,30 +32,20 @@ io.quarkus - quarkus-core - - - - org.apache.maven - maven-settings-builder - - - org.apache.maven - maven-resolver-provider - - - org.apache.maven.resolver - maven-resolver-connector-basic + quarkus-bootstrap-core + provided - org.apache.maven.resolver - maven-resolver-transport-file + io.quarkus + quarkus-core-deployment + - org.apache.maven.resolver - maven-resolver-transport-http + io.quarkus + quarkus-bootstrap-core + test-jar + test - junit junit diff --git a/core/creator/src/main/java/io/quarkus/creator/AppArtifactResolver.java b/core/creator/src/main/java/io/quarkus/creator/AppArtifactResolver.java deleted file mode 100644 index aa6be6aeb80cd..0000000000000 --- a/core/creator/src/main/java/io/quarkus/creator/AppArtifactResolver.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2018 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed 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.creator; - -import java.nio.file.Path; -import java.util.List; - -/** - * Artifact resolver used to resolve application and/or its dependency artifacts. - * - * @author Alexey Loubyansky - */ -public interface AppArtifactResolver { - - /** - * (Re-)links an artifact to a path. - * - * @param appArtifact an artifact to (re-)link to the path - * @param localPath local path to the artifact - * @throws AppCreatorException in case of a failure - */ - void relink(AppArtifact appArtifact, Path localPath) throws AppCreatorException; - - /** - * Resolves an artifact. - * - * @param artifact artifact to resolve - * @return local path - * @throws AppCreatorException in case of a failure - */ - Path resolve(AppArtifact artifact) throws AppCreatorException; - - /** - * Collects all the artifact dependencies. - * - * @param artifact root artifact - * @return collected dependencies - * @throws AppCreatorException in case of a failure - */ - List collectDependencies(AppArtifact artifact) throws AppCreatorException; - - /** - * Collects artifact dependencies merging the provided direct dependencies in - * - * @param root root artifact - * @param deps some or all of the direct dependencies that should be used in place of the original ones - * @return collected dependencies - * @throws AppCreatorException in case of a failure - */ - List collectDependencies(AppArtifact root, List deps) throws AppCreatorException; - - /** - * Lists versions released later than the version of the artifact up to the version - * specified or all the later versions in case the up-to-version is not provided. - * - * @param artifact artifact to list the versions for - * @return the list of versions released later than the version of the artifact - * @throws AppCreatorException in case of a failure - */ - List listLaterVersions(AppArtifact artifact, String upToVersion, boolean inclusive) throws AppCreatorException; - - /** - * Returns the next version for the artifact which is not later than the version specified. - * In case the next version is not available, the artifact's version is returned. - * - * @param artifact artifact - * @param upToVersion max version boundary - * @param inclusive whether the upToVersion should be included in the range or not - * @return the next version which is not later than the specified boundary - * @throws AppCreatorException in case of a failure - */ - String getNextVersion(AppArtifact artifact, String upToVersion, boolean inclusive) throws AppCreatorException; - - /** - * Returns the latest version for the artifact up to the version specified. - * In case there is no later version available, the artifact's version is returned. - * - * @param artifact artifact - * @param upToVersion max version boundary - * @param inclusive whether the upToVersion should be included in the range or not - * @return the latest version up to specified boundary - * @throws AppCreatorException in case of a failure - */ - String getLatestVersion(AppArtifact artifact, String upToVersion, boolean inclusive) throws AppCreatorException; -} diff --git a/core/creator/src/main/java/io/quarkus/creator/AppArtifactResolverBase.java b/core/creator/src/main/java/io/quarkus/creator/AppArtifactResolverBase.java deleted file mode 100644 index bc768285e5dc5..0000000000000 --- a/core/creator/src/main/java/io/quarkus/creator/AppArtifactResolverBase.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * - */ -package io.quarkus.creator; - -import java.nio.file.Path; - -/** - * - * @author Alexey Loubyansky - */ -public abstract class AppArtifactResolverBase implements AppArtifactResolver { - - @Override - public Path resolve(AppArtifact artifact) throws AppCreatorException { - Path path = artifact.getPath(); - if (path != null) { - return path; - } - doResolve(artifact); - path = artifact.getPath(); - if (path == null) { - throw new AppCreatorException("Failed to resolve " + artifact); - } - return path; - } - - protected static void setPath(AppArtifact artifact, Path p) { - artifact.setPath(p); - } - - protected abstract void doResolve(AppArtifact artifact) throws AppCreatorException; -} diff --git a/core/creator/src/main/java/io/quarkus/creator/AppCreator.java b/core/creator/src/main/java/io/quarkus/creator/AppCreator.java index 57afb855235c5..26e3a1cf3f9e0 100644 --- a/core/creator/src/main/java/io/quarkus/creator/AppCreator.java +++ b/core/creator/src/main/java/io/quarkus/creator/AppCreator.java @@ -28,12 +28,13 @@ import java.util.Map; import java.util.ServiceLoader; +import io.quarkus.bootstrap.resolver.AppModelResolver; +import io.quarkus.bootstrap.util.IoUtils; import io.quarkus.creator.config.reader.MappedPropertiesHandler; import io.quarkus.creator.config.reader.PropertiesConfigReaderException; import io.quarkus.creator.config.reader.PropertiesHandler; import io.quarkus.creator.outcome.OutcomeResolver; import io.quarkus.creator.outcome.OutcomeResolverFactory; -import io.quarkus.creator.util.IoUtils; /** * @@ -47,7 +48,7 @@ public static class Builder { private List phases = Collections.emptyList(); private Path appJar; private Path workDir; - private AppArtifactResolver artifactResolver; + private AppModelResolver modelResolver; private Builder() { } @@ -94,15 +95,15 @@ public Builder setWorkDir(Path dir) { } /** - * Artifact resolver which should be used to resolve application - * dependencies. + * Application model resolver which should be used to resolve + * application dependencies. * If artifact resolver is not set by the user, the default one will be * created based on the user Maven settings.xml file. * * @param resolver artifact resolver */ - public Builder setArtifactResolver(AppArtifactResolver resolver) { - this.artifactResolver = resolver; + public Builder setModelResolver(AppModelResolver resolver) { + this.modelResolver = resolver; return this; } @@ -175,7 +176,7 @@ public AppCreator getTarget() throws PropertiesConfigReaderException { private AppCreator initAppCreator() throws AppCreatorException { final AppCreator target = new AppCreator(); target.setWorkDir(workDir); - target.artifactResolver = artifactResolver; + target.artifactResolver = modelResolver; target.appJar = appJar; return target; } @@ -191,7 +192,7 @@ public static Builder builder() { } private OutcomeResolver outcomeResolver; - private AppArtifactResolver artifactResolver; + private AppModelResolver artifactResolver; private Path appJar; private Path workDir; private boolean deleteTmpDir = true; @@ -224,7 +225,7 @@ public Path getWorkDir() { * * @return artifact resolver for application dependencies */ - public AppArtifactResolver getArtifactResolver() { + public AppModelResolver getArtifactResolver() { return artifactResolver; } diff --git a/core/creator/src/main/java/io/quarkus/creator/NoOpArtifactResolver.java b/core/creator/src/main/java/io/quarkus/creator/NoOpArtifactResolver.java deleted file mode 100644 index 8cdef81653725..0000000000000 --- a/core/creator/src/main/java/io/quarkus/creator/NoOpArtifactResolver.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2018 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed 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.creator; - -import java.nio.file.Path; -import java.util.List; - -/** - * - * @author Alexey Loubyansky - */ -public class NoOpArtifactResolver extends AppArtifactResolverBase { - - @Override - public void relink(AppArtifact appArtifact, Path localPath) throws AppCreatorException { - throw new UnsupportedOperationException(); - } - - @Override - public List collectDependencies(AppArtifact artifact) throws AppCreatorException { - throw new UnsupportedOperationException(); - } - - @Override - public List collectDependencies(AppArtifact root, List deps) throws AppCreatorException { - throw new UnsupportedOperationException(); - } - - @Override - public List listLaterVersions(AppArtifact artifact, String upToVersion, boolean inclusive) - throws AppCreatorException { - throw new UnsupportedOperationException(); - } - - @Override - public String getNextVersion(AppArtifact artifact, String upToVersion, boolean inclusive) throws AppCreatorException { - throw new UnsupportedOperationException(); - } - - @Override - public String getLatestVersion(AppArtifact artifact, String upToVersion, boolean inclusive) throws AppCreatorException { - throw new UnsupportedOperationException(); - } - - @Override - protected void doResolve(AppArtifact artifact) throws AppCreatorException { - throw new UnsupportedOperationException(); - } -} diff --git a/core/creator/src/main/java/io/quarkus/creator/demo/AppCreatorDemo.java b/core/creator/src/main/java/io/quarkus/creator/demo/AppCreatorDemo.java deleted file mode 100644 index 879248c0abc9f..0000000000000 --- a/core/creator/src/main/java/io/quarkus/creator/demo/AppCreatorDemo.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright 2018 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed 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.creator.demo; - -import java.io.IOException; -import java.nio.file.DirectoryStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import io.quarkus.creator.AppCreator; -import io.quarkus.creator.phase.augment.AugmentPhase; -import io.quarkus.creator.phase.curate.CuratePhase; -import io.quarkus.creator.phase.nativeimage.NativeImageOutcome; -import io.quarkus.creator.phase.nativeimage.NativeImagePhase; -import io.quarkus.creator.phase.runnerjar.RunnerJarOutcome; -import io.quarkus.creator.phase.runnerjar.RunnerJarPhase; -import io.quarkus.creator.util.IoUtils; -import io.quarkus.creator.util.PropertyUtils; - -/** - * - * @author Alexey Loubyansky - */ -public class AppCreatorDemo { - - /** - * This demo assumes you've built quarkus and its artifacts are in your local repo - * (otherwise they would have to be available in one of the remote repos to be resolvable) - * - * AND also example quarkus-strict-bean-validation-example-999-SNAPSHOT.jar. This jar is - * used as an example user app that is augmented and turned into a runnable application - */ - - public static void main(String[] args) throws Exception { - final Path quarkusRoot = Paths.get("").toAbsolutePath().getParent().getParent(); - final Path exampleTarget = quarkusRoot.resolve("integration-tests").resolve("bean-validation-strict").resolve("target"); - - final Path appJar = exampleTarget.resolve("quarkus-integration-test-bean-validation-999-SNAPSHOT.jar"); - if (!Files.exists(appJar)) { - throw new Exception("Failed to locate user app " + appJar); - } - - final Path demoDir = Paths.get(PropertyUtils.getUserHome()).resolve("quarkus-creator-demo"); - IoUtils.recursiveDelete(demoDir); - - buildRunnableJar(appJar, demoDir); - //buildNativeImage(appJar, demoDir); - //curateRunnableJar(appJar, demoDir); - - //logLibDiff(exampleTarget, demoDir); - } - - private static void buildRunnableJar(Path userApp, Path outputDir) throws Exception { - - final RunnerJarOutcome runnerJar; - try (AppCreator appCreator = AppCreator.builder() - .addPhase(new CuratePhase()) - .addPhase(new AugmentPhase()/* .setOutputDir(outputDir) */) - .addPhase(new RunnerJarPhase()/* .setOutputDir(outputDir) */) - .addPhase(new NativeImagePhase()) - .setAppJar(userApp) - .build()) { - runnerJar = appCreator.resolveOutcome(RunnerJarOutcome.class); - System.out.println("Runner JAR: " + runnerJar.getRunnerJar() + " exists=" + Files.exists(runnerJar.getRunnerJar())); - } - System.out.println("Runner JAR: " + runnerJar.getRunnerJar() + " exists=" + Files.exists(runnerJar.getRunnerJar())); - } - - private static void buildNativeImage(Path userApp, Path outputDir) throws Exception { - - try (AppCreator appCreator = AppCreator.builder() - //.setOutput(outputDir) - .addPhase(new CuratePhase()) - .addPhase(new AugmentPhase()) - .addPhase(new RunnerJarPhase()) - .addPhase(new NativeImagePhase().setOutputDir(outputDir)) - .setAppJar(userApp) - .build()) { - appCreator.resolveOutcome(NativeImageOutcome.class); - } - } - - private static void logLibDiff(final Path exampleTarget, final Path testBuildDir) throws IOException { - final Set originalNames = readNames(exampleTarget.resolve("lib")); - final Set aetherNames = readNames(testBuildDir.resolve("lib")); - - final Set originalOnly = new HashSet<>(originalNames); - originalOnly.removeAll(aetherNames); - logNames("Original build lib jars not found in the test lib:", originalOnly); - - final Set aetherOnly = new HashSet<>(aetherNames); - aetherOnly.removeAll(originalNames); - logNames("Test lib jars not found in the original build lib:", aetherOnly); - } - - private static void logNames(String header, Set names) { - if (names.isEmpty()) { - return; - } - System.out.println(header); - final List sorted = new ArrayList<>(names); - Collections.sort(sorted); - for (int i = 0; i < sorted.size(); ++i) { - System.out.println((i + 1) + ") " + sorted.get(i)); - } - } - - private static Set readNames(Path path) throws IOException { - Set names = new HashSet<>(); - try (DirectoryStream stream = Files.newDirectoryStream(path)) { - for (Path p : stream) { - names.add(p.getFileName().toString()); - } - } - return names; - } -} diff --git a/core/creator/src/main/java/io/quarkus/creator/demo/CheckUpdatesDemo.java b/core/creator/src/main/java/io/quarkus/creator/demo/CheckUpdatesDemo.java deleted file mode 100644 index 571a49a977802..0000000000000 --- a/core/creator/src/main/java/io/quarkus/creator/demo/CheckUpdatesDemo.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2018 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed 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.creator.demo; - -import java.util.Properties; - -import io.quarkus.creator.AppCreator; -import io.quarkus.creator.AppDependency; -import io.quarkus.creator.phase.curate.CurateOutcome; -import io.quarkus.creator.phase.curate.CuratePhase; -import io.quarkus.creator.phase.curate.VersionUpdate; -import io.quarkus.creator.phase.curate.VersionUpdateNumber; - -/** - * - * @author Alexey Loubyansky - */ -public class CheckUpdatesDemo extends ConfigDemoBase { - - public static void main(String[] args) throws Exception { - new CheckUpdatesDemo().run(); - } - - @Override - protected void initProps(Properties props) { - props.setProperty(CuratePhase.completePropertyName(CuratePhase.CONFIG_PROP_VERSION_UPDATE), - VersionUpdate.NONE.getName()); // NONE, next, latest - props.setProperty(CuratePhase.completePropertyName(CuratePhase.CONFIG_PROP_VERSION_UPDATE_NUMBER), - VersionUpdateNumber.MINOR.getName()); // major, minor, MICRO - } - - @Override - protected boolean isLogLibDiff() { - return false; - } - - @Override - public void demo(AppCreator creator) throws Exception { - final CurateOutcome curate = creator.resolveOutcome(CurateOutcome.class); - final boolean updatesAvailable = curate.hasUpdatedDeps(); - System.out.println("Updates available: " + updatesAvailable); - if (!updatesAvailable) { - return; - } - System.out.println("Available updates:"); - for (AppDependency dep : curate.getUpdatedDeps()) { - System.out.println("- " + dep); - } - } -} diff --git a/core/creator/src/main/java/io/quarkus/creator/demo/ConfigDemoBase.java b/core/creator/src/main/java/io/quarkus/creator/demo/ConfigDemoBase.java deleted file mode 100644 index 241cc8f073e6e..0000000000000 --- a/core/creator/src/main/java/io/quarkus/creator/demo/ConfigDemoBase.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright 2018 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed 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.creator.demo; - -import java.io.IOException; -import java.io.OutputStream; -import java.nio.file.DirectoryStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Properties; -import java.util.Set; - -import io.quarkus.creator.AppCreator; -import io.quarkus.creator.config.reader.PropertiesConfigReader; -import io.quarkus.creator.config.reader.PropertiesHandler; -import io.quarkus.creator.phase.curate.CuratePhase; -import io.quarkus.creator.util.IoUtils; -import io.quarkus.creator.util.PropertyUtils; - -/** - * - * @author Alexey Loubyansky - */ -public class ConfigDemoBase { - - protected Path appJar; - protected Path workDir; - - public ConfigDemoBase() { - } - - public void run() throws Exception { - - final long startTime = System.currentTimeMillis(); - - final Path appJar = getAppJar(); - if (!Files.exists(appJar)) { - throw new IllegalStateException("Failed to locate user app " + appJar); - } - - final Path demoDir = getDemoWorkDir(); - IoUtils.recursiveDelete(demoDir); - - final Properties props = getProperties(); - Files.createDirectories(demoDir); - final Path propsFile = demoDir.resolve("app-creator.properties"); - try (OutputStream out = Files.newOutputStream(propsFile)) { - props.store(out, "Example AppCreator properties"); - } - - final PropertiesHandler propsHandler = AppCreator.builder() - .setAppJar(appJar) - .getPropertiesHandler(); - try (final AppCreator appCreator = PropertiesConfigReader.getInstance(propsHandler).read(propsFile)) { - demo(appCreator); - if (isLogLibDiff()) { - logLibDiff(appJar.getParent(), demoDir); - } - } - - final long time = System.currentTimeMillis() - startTime; - final long seconds = time / 1000; - System.out.println("Done in " + seconds + "." + (time - seconds * 1000) + " seconds"); - } - - protected void demo(AppCreator creator) throws Exception { - } - - protected boolean isLogLibDiff() { - return false; - } - - protected Path initAppJar() { - final Path quarkusRoot = Paths.get("").toAbsolutePath().getParent().getParent(); - //final Path appDir = quarkusRoot.resolve("integration-tests").resolve("bean-validation-strict").resolve("target"); - //final Path appJar = appDir.resolve("quarkus-integration-test-bean-validation-999-SNAPSHOT.jar"); - - final Path quickstartsRoot = quarkusRoot.getParent().resolve("quarkus-quickstarts"); - if (!Files.exists(quickstartsRoot)) { - throw new IllegalStateException("Failed to locate quarkus-quickstarts repo at " + quickstartsRoot); - } - final Path appDir = quickstartsRoot.resolve("application-configuration").resolve("target"); - final Path appJar = appDir.resolve("application-configuration-1.0-SNAPSHOT.jar"); - return appJar; - } - - public Path getAppJar() { - return appJar == null ? appJar = initAppJar() : appJar; - } - - public Path getDemoWorkDir() { - return workDir == null ? workDir = Paths.get(PropertyUtils.getUserHome()).resolve("quarkus-creator-demo") : workDir; - } - - public Properties getProperties() { - final Properties props = new Properties(); - final Path demoDir = getDemoWorkDir(); - if (demoDir != null) { - props.setProperty("output", demoDir.toString()); - } - props.setProperty(CuratePhase.completePropertyName(CuratePhase.CONFIG_PROP_LOCAL_REPO), - Paths.get(PropertyUtils.getUserHome(), "quarkus-curate-repo").toString()); - initProps(props); - return props; - } - - protected void initProps(Properties props) { - } - - private static void logLibDiff(final Path exampleTarget, final Path testBuildDir) throws IOException { - final Set originalNames = readNames(exampleTarget.resolve("lib")); - final Set aetherNames = readNames(testBuildDir.resolve("lib")); - - final Set originalOnly = new HashSet<>(originalNames); - originalOnly.removeAll(aetherNames); - logNames("Original build lib jars not found in the test lib:", originalOnly); - - final Set aetherOnly = new HashSet<>(aetherNames); - aetherOnly.removeAll(originalNames); - logNames("Test lib jars not found in the original build lib:", aetherOnly); - } - - private static void logNames(String header, Set names) { - if (names.isEmpty()) { - return; - } - System.out.println(header); - final List sorted = new ArrayList<>(names); - Collections.sort(sorted); - for (int i = 0; i < sorted.size(); ++i) { - System.out.println((i + 1) + ") " + sorted.get(i)); - } - } - - private static Set readNames(Path path) throws IOException { - Set names = new HashSet<>(); - try (DirectoryStream stream = Files.newDirectoryStream(path)) { - for (Path p : stream) { - names.add(p.getFileName().toString()); - } - } - return names; - } -} diff --git a/core/creator/src/main/java/io/quarkus/creator/demo/NativeImageOutcomeDemo.java b/core/creator/src/main/java/io/quarkus/creator/demo/NativeImageOutcomeDemo.java deleted file mode 100644 index 8e762bd021c91..0000000000000 --- a/core/creator/src/main/java/io/quarkus/creator/demo/NativeImageOutcomeDemo.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2018 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed 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.creator.demo; - -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Properties; - -import io.quarkus.creator.AppCreator; -import io.quarkus.creator.phase.nativeimage.NativeImageOutcome; - -/** - * - * @author Alexey Loubyansky - */ -public class NativeImageOutcomeDemo extends ConfigDemoBase { - - public static void main(String[] args) throws Exception { - new NativeImageOutcomeDemo().run(); - } - - @Override - protected Path initAppJar() { - final Path quarkusRoot = Paths.get("").toAbsolutePath().getParent().getParent(); - final Path appDir = quarkusRoot.resolve("integration-tests").resolve("bean-validation-strict").resolve("target"); - return appDir.resolve("quarkus-integration-test-bean-validation-999-SNAPSHOT.jar"); - } - - @Override - protected void initProps(Properties props) { - props.setProperty("native-image.disable-reports", "true"); - } - - @Override - public void demo(AppCreator creator) throws Exception { - creator.resolveOutcome(NativeImageOutcome.class); - } -} diff --git a/core/creator/src/main/java/io/quarkus/creator/demo/UpdateToLatestVersionDemo.java b/core/creator/src/main/java/io/quarkus/creator/demo/UpdateToLatestVersionDemo.java deleted file mode 100644 index eef1214f1cb59..0000000000000 --- a/core/creator/src/main/java/io/quarkus/creator/demo/UpdateToLatestVersionDemo.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2018 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed 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.creator.demo; - -import java.util.Properties; - -import io.quarkus.creator.AppCreator; -import io.quarkus.creator.phase.curate.CuratePhase; -import io.quarkus.creator.phase.curate.VersionUpdate; -import io.quarkus.creator.phase.curate.VersionUpdateNumber; -import io.quarkus.creator.phase.runnerjar.RunnerJarOutcome; - -/** - * - * @author Alexey Loubyansky - */ -public class UpdateToLatestVersionDemo extends ConfigDemoBase { - - public static void main(String[] args) throws Exception { - new UpdateToLatestVersionDemo().run(); - } - - @Override - protected void initProps(Properties props) { - props.setProperty(CuratePhase.completePropertyName(CuratePhase.CONFIG_PROP_VERSION_UPDATE), - VersionUpdate.LATEST.getName()); - props.setProperty(CuratePhase.completePropertyName(CuratePhase.CONFIG_PROP_VERSION_UPDATE_NUMBER), - VersionUpdateNumber.MINOR.getName()); - } - - @Override - public void demo(AppCreator creator) throws Exception { - creator.resolveOutcome(RunnerJarOutcome.class); - } -} diff --git a/core/creator/src/main/java/io/quarkus/creator/demo/UpdateToNextAndPersistStateDemo.java b/core/creator/src/main/java/io/quarkus/creator/demo/UpdateToNextAndPersistStateDemo.java deleted file mode 100644 index c35ea8f2df968..0000000000000 --- a/core/creator/src/main/java/io/quarkus/creator/demo/UpdateToNextAndPersistStateDemo.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2018 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed 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.creator.demo; - -import java.util.Properties; - -import io.quarkus.creator.AppCreator; -import io.quarkus.creator.phase.curate.CurateOutcome; -import io.quarkus.creator.phase.curate.CuratePhase; -import io.quarkus.creator.phase.curate.DependenciesOrigin; -import io.quarkus.creator.phase.curate.VersionUpdate; -import io.quarkus.creator.phase.curate.VersionUpdateNumber; -import io.quarkus.creator.phase.runnerjar.RunnerJarOutcome; - -/** - * - * @author Alexey Loubyansky - */ -public class UpdateToNextAndPersistStateDemo extends ConfigDemoBase { - - public static void main(String[] args) throws Exception { - new UpdateToNextAndPersistStateDemo().run(); - } - - @Override - protected void initProps(Properties props) { - props.setProperty(CuratePhase.completePropertyName(CuratePhase.CONFIG_PROP_DEPS_ORIGIN), - DependenciesOrigin.LAST_UPDATE.getName()); // APPLICATION, last-update - props.setProperty(CuratePhase.completePropertyName(CuratePhase.CONFIG_PROP_VERSION_UPDATE), - VersionUpdate.NEXT.getName()); - props.setProperty(CuratePhase.completePropertyName(CuratePhase.CONFIG_PROP_VERSION_UPDATE_NUMBER), - VersionUpdateNumber.MINOR.getName()); - } - - @Override - public void demo(AppCreator creator) throws Exception { - creator.resolveOutcome(RunnerJarOutcome.class); - final CurateOutcome curateOutcome = creator.getOutcome(CurateOutcome.class); - if (curateOutcome == null) { - throw new IllegalStateException("Curate outcome isn't available"); - } - curateOutcome.persist(creator); - } -} diff --git a/core/creator/src/main/java/io/quarkus/creator/demo/UpdateToNextVersionDemo.java b/core/creator/src/main/java/io/quarkus/creator/demo/UpdateToNextVersionDemo.java deleted file mode 100644 index 9fdff811b89ce..0000000000000 --- a/core/creator/src/main/java/io/quarkus/creator/demo/UpdateToNextVersionDemo.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2018 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed 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.creator.demo; - -import java.util.Properties; - -import io.quarkus.creator.AppCreator; -import io.quarkus.creator.phase.curate.CuratePhase; -import io.quarkus.creator.phase.curate.VersionUpdate; -import io.quarkus.creator.phase.curate.VersionUpdateNumber; -import io.quarkus.creator.phase.runnerjar.RunnerJarOutcome; - -/** - * - * @author Alexey Loubyansky - */ -public class UpdateToNextVersionDemo extends ConfigDemoBase { - - public static void main(String[] args) throws Exception { - new UpdateToNextVersionDemo().run(); - } - - /* - * @Override - * protected Path initAppJar() { - * final Path quarkusRoot = Paths.get("").toAbsolutePath().getParent().getParent(); - * final Path quickstartsRoot = quarkusRoot.getParent().resolve("quarkus-quickstarts"); - * if(!Files.exists(quickstartsRoot)) { - * throw new IllegalStateException("Failed to locate quarkus-quickstarts repo at " + quickstartsRoot); - * } - * final Path appDir = quickstartsRoot.resolve("input-validation").resolve("target"); - * return appDir.resolve("input-validation-1.0-SNAPSHOT.jar"); - * } - */ - @Override - protected void initProps(Properties props) { - props.setProperty(CuratePhase.completePropertyName(CuratePhase.CONFIG_PROP_VERSION_UPDATE), - VersionUpdate.NEXT.getName()); - props.setProperty(CuratePhase.completePropertyName(CuratePhase.CONFIG_PROP_VERSION_UPDATE_NUMBER), - VersionUpdateNumber.MINOR.getName()); - } - - @Override - public void demo(AppCreator creator) throws Exception { - creator.resolveOutcome(RunnerJarOutcome.class); - } -} diff --git a/core/creator/src/main/java/io/quarkus/creator/outcome/OutcomeProviderDescription.java b/core/creator/src/main/java/io/quarkus/creator/outcome/OutcomeProviderDescription.java index 91f5c4f16a693..52df853d2749d 100644 --- a/core/creator/src/main/java/io/quarkus/creator/outcome/OutcomeProviderDescription.java +++ b/core/creator/src/main/java/io/quarkus/creator/outcome/OutcomeProviderDescription.java @@ -28,7 +28,7 @@ public class OutcomeProviderDescription { static final int PROCESSING = 0b00001; - static final int PROCESSED = 0b00010; + static final int PROCESSED = 0b00010; protected final int id; protected final OutcomeProvider provider; @@ -41,7 +41,7 @@ protected OutcomeProviderDescription(int id, OutcomeProvider provider) { } protected void addProvidedType(Class providedType) { - if(providedTypes.isEmpty()) { + if (providedTypes.isEmpty()) { providedTypes = new ArrayList<>(1); } providedTypes.add(providedType); @@ -52,7 +52,7 @@ boolean isFlagOn(int flag) { } boolean setFlag(int flag) { - if((flags & flag) > 0) { + if ((flags & flag) > 0) { return false; } flags ^= flag; @@ -60,7 +60,7 @@ boolean setFlag(int flag) { } void clearFlag(int flag) { - if((flags & flag) > 0) { + if ((flags & flag) > 0) { flags ^= flag; } } diff --git a/core/creator/src/main/java/io/quarkus/creator/phase/augment/AugmentOutcome.java b/core/creator/src/main/java/io/quarkus/creator/phase/augment/AugmentOutcome.java index 5140355568990..a917350be6f11 100644 --- a/core/creator/src/main/java/io/quarkus/creator/phase/augment/AugmentOutcome.java +++ b/core/creator/src/main/java/io/quarkus/creator/phase/augment/AugmentOutcome.java @@ -19,8 +19,6 @@ import java.nio.file.Path; -import io.quarkus.creator.AppDependency; - /** * Represents an outcome of {@link AugmentPhase} * @@ -51,13 +49,9 @@ public interface AugmentOutcome { Path getWiringClassesDir(); /** - * Phases that consume this outcome may check whether an application - * dependency was "whitelisted" for processing during augmentation. - *

- * The need for this method has to be further reviewed. + * Directory containing config files used by the application * - * @param dep application dependency - * @return true if the dependency was whitelisted, otherwise - false + * @return directory containing config files */ - boolean isWhitelisted(AppDependency dep); + Path getConfigDir(); } diff --git a/core/creator/src/main/java/io/quarkus/creator/phase/augment/AugmentPhase.java b/core/creator/src/main/java/io/quarkus/creator/phase/augment/AugmentPhase.java index 898b78b5a274b..57dab7b440655 100644 --- a/core/creator/src/main/java/io/quarkus/creator/phase/augment/AugmentPhase.java +++ b/core/creator/src/main/java/io/quarkus/creator/phase/augment/AugmentPhase.java @@ -17,24 +17,23 @@ package io.quarkus.creator.phase.augment; -import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; import java.io.OutputStream; import java.net.URL; import java.net.URLClassLoader; -import java.nio.charset.StandardCharsets; +import java.nio.file.CopyOption; +import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.ExecutorService; @@ -42,28 +41,27 @@ import java.util.concurrent.Future; import java.util.function.BiFunction; import java.util.function.Consumer; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; import org.eclipse.microprofile.config.Config; -import org.jboss.builder.BuildResult; import org.jboss.logging.Logger; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; -import io.quarkus.creator.AppArtifact; -import io.quarkus.creator.AppArtifactResolver; +import io.quarkus.bootstrap.BootstrapDependencyProcessingException; +import io.quarkus.bootstrap.model.AppDependency; +import io.quarkus.bootstrap.resolver.AppModelResolver; +import io.quarkus.bootstrap.util.IoUtils; +import io.quarkus.bootstrap.util.ZipUtils; +import io.quarkus.builder.BuildResult; import io.quarkus.creator.AppCreationPhase; import io.quarkus.creator.AppCreator; import io.quarkus.creator.AppCreatorException; -import io.quarkus.creator.AppDependency; import io.quarkus.creator.config.reader.MappedPropertiesHandler; import io.quarkus.creator.config.reader.PropertiesHandler; import io.quarkus.creator.outcome.OutcomeProviderRegistration; import io.quarkus.creator.phase.curate.CurateOutcome; -import io.quarkus.creator.util.IoUtils; -import io.quarkus.creator.util.ZipUtils; +import io.quarkus.deployment.ApplicationInfoUtil; import io.quarkus.deployment.ClassOutput; import io.quarkus.deployment.QuarkusAugmentor; import io.quarkus.deployment.QuarkusClassWriter; @@ -81,17 +79,15 @@ */ public class AugmentPhase implements AppCreationPhase, AugmentOutcome { - private static final String DEPENDENCIES_RUNTIME = "dependencies.runtime"; - private static final String FILENAME_STEP_CLASSES = "META-INF/quarkus-build-steps.list"; - private static final String PROVIDED = "provided"; - private static final Logger log = Logger.getLogger(AugmentPhase.class); + private static final String APPLICATION_INFO_PROPERTIES = "application-info.properties"; + private static final String META_INF = "META-INF"; private Path outputDir; private Path appClassesDir; private Path transformedClassesDir; private Path wiringClassesDir; - private Set whitelist = new HashSet<>(); + private Path configDir; /** * Output directory for the outcome of this phase. @@ -144,6 +140,17 @@ public AugmentPhase setWiringClassesDir(Path wiringClassesDir) { return this; } + /** + * Directory containing the configuration files. + * + * @param configDir directory the configuration files (application.properties) + * @return this phase instance + */ + public AugmentPhase setConfigDir(Path configDir) { + this.configDir = configDir; + return this; + } + @Override public Path getAppClassesDir() { return appClassesDir; @@ -160,8 +167,8 @@ public Path getWiringClassesDir() { } @Override - public boolean isWhitelisted(AppDependency dep) { - return whitelist.contains(getDependencyConflictId(dep.getArtifact())); + public Path getConfigDir() { + return configDir; } @Override @@ -177,16 +184,34 @@ public void provideOutcome(AppCreator ctx) throws AppCreatorException { if (appClassesDir == null) { appClassesDir = outputDir.resolve("classes"); - final Path appJar = appState.getArtifactResolver().resolve(appState.getAppArtifact()); + Path appJar = appState.getAppArtifact().getPath(); try { ZipUtils.unzip(appJar, appClassesDir); } catch (IOException e) { throw new AppCreatorException("Failed to unzip " + appJar, e); } - final Path metaInf = appClassesDir.resolve("META-INF"); + final Path metaInf = appClassesDir.resolve(META_INF); IoUtils.recursiveDelete(metaInf.resolve("maven")); IoUtils.recursiveDelete(metaInf.resolve("INDEX.LIST")); IoUtils.recursiveDelete(metaInf.resolve("MANIFEST.MF")); + IoUtils.recursiveDelete(metaInf.resolve(APPLICATION_INFO_PROPERTIES)); + } + + ApplicationInfoUtil.writeApplicationInfoProperties(appState.getAppArtifact(), appClassesDir); + + //lets default to appClassesDir for now + if (configDir == null) + configDir = appClassesDir; + else { + //if we use gradle we copy the configDir contents to appClassesDir + try { + if (!Files.isSameFile(configDir, appClassesDir)) { + Files.walkFileTree(configDir, + new CopyDirVisitor(configDir, appClassesDir, StandardCopyOption.REPLACE_EXISTING)); + } + } catch (IOException e) { + throw new AppCreatorException("Failed while copying files from " + configDir + " to " + appClassesDir, e); + } } transformedClassesDir = IoUtils @@ -201,7 +226,7 @@ public void provideOutcome(AppCreator ctx) throws AppCreatorException { private void doProcess(CurateOutcome appState) throws AppCreatorException { //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 = appClassesDir.resolve("application.properties"); + Path config = configDir.resolve("application.properties"); if (Files.exists(config)) { try { Config built = SmallRyeConfigProviderResolver.instance().getBuilder() @@ -215,71 +240,27 @@ private void doProcess(CurateOutcome appState) throws AppCreatorException { } } - final AppArtifactResolver depResolver = appState.getArtifactResolver(); - final List appDeps = appState.getEffectiveDeps(); + final AppModelResolver depResolver = appState.getArtifactResolver(); + List appDeps; + try { + appDeps = appState.getEffectiveModel().getAllDependencies(); + } catch (BootstrapDependencyProcessingException e) { + throw new AppCreatorException("Failed to resolve application build classpath", e); + } URLClassLoader runnerClassLoader = null; try { // we need to make sure all the deployment artifacts are on the class path - final List cpUrls = new ArrayList<>(); + final List cpUrls = new ArrayList<>(appDeps.size() + 1); cpUrls.add(appClassesDir.toUri().toURL()); - List problems = null; for (AppDependency appDep : appDeps) { - final AppArtifact depArtifact = appDep.getArtifact(); - final Path resolvedDep = depResolver.resolve(depArtifact); + final Path resolvedDep = depResolver.resolve(appDep.getArtifact()); cpUrls.add(resolvedDep.toUri().toURL()); - - if (!"jar".equals(depArtifact.getType())) { - continue; - } - try (ZipFile zip = openZipFile(resolvedDep)) { - boolean deploymentArtifact = zip.getEntry("META-INF/quarkus-build-steps.list") != null; - if (!appDep.getScope().equals(PROVIDED) && deploymentArtifact) { - if (problems == null) { - problems = new ArrayList<>(); - } - problems.add("Artifact " + appDep - + " is a deployment artifact, however it does not have scope required. This will result in unnecessary jars being included in the final image"); - } - if (!deploymentArtifact) { - ZipEntry entry = zip.getEntry(DEPENDENCIES_RUNTIME); - if (entry != null) { - whitelist.add(getDependencyConflictId(appDep.getArtifact())); - try (InputStream in = zip.getInputStream(entry)) { - BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)); - String line; - while ((line = reader.readLine()) != null) { - String[] parts = line.trim().split(":"); - if (parts.length < 5) { - continue; - } - String scope = parts[4]; - if (scope.equals("test")) { - continue; - } - StringBuilder sb = new StringBuilder(); - //the last two bits are version and scope - //which we don't want - for (int i = 0; i < parts.length - 2; ++i) { - if (i > 0) { - sb.append(':'); - } - sb.append(parts[i]); - } - whitelist.add(sb.toString()); - } - } - } - } - } - } - if (problems != null) { - //TODO: add a config option to just log an error instead - throw new AppCreatorException(problems.toString()); } runnerClassLoader = new URLClassLoader(cpUrls.toArray(new URL[cpUrls.size()]), getClass().getClassLoader()); + final Path wiringClassesDirectory = wiringClassesDir; ClassOutput classOutput = new ClassOutput() { @Override @@ -323,11 +304,9 @@ public void writeResource(String name, byte[] data) throws IOException { if (!bytecodeTransformerBuildItems.isEmpty()) { final Map>> bytecodeTransformers = new HashMap<>( bytecodeTransformerBuildItems.size()); - if (!bytecodeTransformerBuildItems.isEmpty()) { - for (BytecodeTransformerBuildItem i : bytecodeTransformerBuildItems) { - bytecodeTransformers.computeIfAbsent(i.getClassToTransform(), (h) -> new ArrayList<>()) - .add(i.getVisitorFunction()); - } + for (BytecodeTransformerBuildItem i : bytecodeTransformerBuildItems) { + bytecodeTransformers.computeIfAbsent(i.getClassToTransform(), (h) -> new ArrayList<>()) + .add(i.getVisitorFunction()); } // now copy all the contents to the runner jar @@ -345,7 +324,7 @@ public void accept(Path path) { return; } final String pathName = appClassesDir.relativize(path).toString(); - if (!pathName.endsWith(".class") || bytecodeTransformers.isEmpty()) { + if (!pathName.endsWith(".class")) { return; } final String className = pathName.substring(0, pathName.length() - 6).replace('/', '.'); @@ -407,31 +386,6 @@ public FutureEntry call() throws Exception { } } - private static String getDependencyConflictId(AppArtifact coords) { - StringBuilder sb = new StringBuilder(128); - sb.append(coords.getGroupId()); - sb.append(':'); - sb.append(coords.getArtifactId()); - sb.append(':'); - sb.append(coords.getType()); - if (!coords.getClassifier().isEmpty()) { - sb.append(':'); - sb.append(coords.getClassifier()); - } - return sb.toString(); - } - - private ZipFile openZipFile(Path p) { - if (!Files.isReadable(p)) { - throw new RuntimeException("File not existing or not allowed for reading: " + p); - } - try { - return new ZipFile(p.toFile()); - } catch (IOException e) { - throw new RuntimeException("Error opening zip stream from artifact: " + p, e); - } - } - private static final class FutureEntry { final byte[] data; final String location; @@ -458,6 +412,34 @@ public AugmentPhase getTarget() { .map("output", (AugmentPhase t, String value) -> t.setOutputDir(Paths.get(value))) .map("classes", (AugmentPhase t, String value) -> t.setAppClassesDir(Paths.get(value))) .map("transformed-classes", (AugmentPhase t, String value) -> t.setTransformedClassesDir(Paths.get(value))) - .map("wiring-classes", (AugmentPhase t, String value) -> t.setWiringClassesDir(Paths.get(value))); + .map("wiring-classes", (AugmentPhase t, String value) -> t.setWiringClassesDir(Paths.get(value))) + .map("config", (AugmentPhase t, String value) -> t.setConfigDir(Paths.get(value))); + } + + public static class CopyDirVisitor extends SimpleFileVisitor { + private final Path fromPath; + private final Path toPath; + private final CopyOption copyOption; + + public CopyDirVisitor(Path fromPath, Path toPath, CopyOption copyOption) { + this.fromPath = fromPath; + this.toPath = toPath; + this.copyOption = copyOption; + } + + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + Path targetPath = toPath.resolve(fromPath.relativize(dir)); + if (!Files.exists(targetPath)) { + Files.createDirectory(targetPath); + } + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + Files.copy(file, toPath.resolve(fromPath.relativize(file)), copyOption); + return FileVisitResult.CONTINUE; + } } } diff --git a/core/creator/src/main/java/io/quarkus/creator/phase/curate/CurateOutcome.java b/core/creator/src/main/java/io/quarkus/creator/phase/curate/CurateOutcome.java index 0577805a90d89..1c70577d4d528 100644 --- a/core/creator/src/main/java/io/quarkus/creator/phase/curate/CurateOutcome.java +++ b/core/creator/src/main/java/io/quarkus/creator/phase/curate/CurateOutcome.java @@ -27,13 +27,15 @@ import org.apache.maven.model.Repository; import org.jboss.logging.Logger; -import io.quarkus.creator.AppArtifact; -import io.quarkus.creator.AppArtifactResolver; +import io.quarkus.bootstrap.model.AppArtifact; +import io.quarkus.bootstrap.model.AppDependency; +import io.quarkus.bootstrap.model.AppModel; +import io.quarkus.bootstrap.resolver.AppModelResolver; +import io.quarkus.bootstrap.resolver.AppModelResolverException; +import io.quarkus.bootstrap.resolver.BootstrapAppModelResolver; +import io.quarkus.bootstrap.resolver.maven.workspace.ModelUtils; import io.quarkus.creator.AppCreator; import io.quarkus.creator.AppCreatorException; -import io.quarkus.creator.AppDependency; -import io.quarkus.creator.NoOpArtifactResolver; -import io.quarkus.creator.resolver.aether.AetherArtifactResolver; /** * @@ -51,34 +53,28 @@ public class CurateOutcome { public static class Builder { - private AppArtifact appArtifact; private AppArtifact stateArtifact; - private List initialDeps = Collections.emptyList(); + private AppModel appModel; private List updatedDeps = Collections.emptyList(); - private AppArtifactResolver resolver; + private AppModelResolver resolver; private List artifactRepos = Collections.emptyList(); private boolean loadedFromState; private Builder() { } - public Builder setAppArtifact(AppArtifact appArtifact) { - this.appArtifact = appArtifact; - return this; - } - public Builder setStateArtifact(AppArtifact stateArtifact) { this.stateArtifact = stateArtifact; return this; } - public Builder setArtifactResolver(AppArtifactResolver resolver) { + public Builder setAppModelResolver(AppModelResolver resolver) { this.resolver = resolver; return this; } - public Builder setInitialDeps(List deps) { - this.initialDeps = deps; + public Builder setAppModel(AppModel appModel) { + this.appModel = appModel; return this; } @@ -104,38 +100,35 @@ public static Builder builder() { return new Builder(); } - protected final AppArtifact appArtifact; protected final AppArtifact stateArtifact; - protected final List initialDeps; + protected final AppModel initialModel; protected final List updatedDeps; - protected final AppArtifactResolver resolver; + protected final AppModelResolver resolver; protected final List artifactRepos; protected final boolean loadedFromState; - protected List effectiveDeps; + protected AppModel effectiveModel; protected boolean persisted; public CurateOutcome(Builder builder) { - this.appArtifact = builder.appArtifact; this.stateArtifact = builder.stateArtifact; - this.initialDeps = builder.initialDeps.isEmpty() ? builder.initialDeps - : Collections.unmodifiableList(builder.initialDeps); + this.initialModel = builder.appModel; this.updatedDeps = builder.updatedDeps.isEmpty() ? builder.updatedDeps : Collections.unmodifiableList(builder.updatedDeps); - this.resolver = builder.resolver == null ? new NoOpArtifactResolver() : builder.resolver; + this.resolver = builder.resolver; this.artifactRepos = builder.artifactRepos; this.loadedFromState = builder.loadedFromState; } - public AppArtifactResolver getArtifactResolver() { + public AppModelResolver getArtifactResolver() { return resolver; } public AppArtifact getAppArtifact() { - return appArtifact; + return initialModel.getAppArtifact(); } - public List getInitialDeps() { - return initialDeps; + public AppModel getInitialModel() { + return initialModel; } public boolean hasUpdatedDeps() { @@ -146,33 +139,38 @@ public List getUpdatedDeps() { return updatedDeps; } - public List getEffectiveDeps() throws AppCreatorException { - if (effectiveDeps != null) { - return effectiveDeps; + public AppModel getEffectiveModel() throws AppCreatorException { + if (effectiveModel != null) { + return effectiveModel; } if (updatedDeps.isEmpty()) { - return effectiveDeps = initialDeps; + return effectiveModel = initialModel; + } + try { + return effectiveModel = resolver.resolveModel(initialModel.getAppArtifact(), updatedDeps); + } catch (AppModelResolverException e) { + throw new AppCreatorException("Failed to resolve effective application dependencies", e); } - return effectiveDeps = resolver.collectDependencies(appArtifact, updatedDeps); } public boolean isPersisted() { return persisted; } - public void persist(AppCreator ctx) throws AppCreatorException { + public void persist(AppCreator creator) throws AppCreatorException { if (persisted || loadedFromState && !hasUpdatedDeps()) { log.info("Skipping provisioning state persistence"); return; } log.info("Persisting provisioning state"); - final Path stateDir = ctx.createWorkDir("state"); + final Path stateDir = creator.createWorkDir("state"); final Path statePom = stateDir.resolve("pom.xml"); + final AppArtifact appArtifact = initialModel.getAppArtifact(); AppArtifact stateArtifact; if (this.stateArtifact == null) { - stateArtifact = Utils.getStateArtifact(appArtifact); + stateArtifact = ModelUtils.getStateArtifact(appArtifact); } else { stateArtifact = new AppArtifact(this.stateArtifact.getGroupId(), this.stateArtifact.getArtifactId(), @@ -240,9 +238,13 @@ public void persist(AppCreator ctx) throws AppCreatorException { * } * } */ - Utils.persistModel(statePom, model); - ((AetherArtifactResolver) resolver).install(stateArtifact, statePom); + try { + ModelUtils.persistModel(statePom, model); + ((BootstrapAppModelResolver) resolver).install(stateArtifact, statePom); + } catch (Exception e) { + throw new AppCreatorException("Failed to persist application state artifact", e); + } log.info("Persisted provisioning state as " + stateArtifact); //ctx.getArtifactResolver().relink(stateArtifact, statePom); diff --git a/core/creator/src/main/java/io/quarkus/creator/phase/curate/CuratePhase.java b/core/creator/src/main/java/io/quarkus/creator/phase/curate/CuratePhase.java index 8815ca6335cfa..5565faf980a11 100644 --- a/core/creator/src/main/java/io/quarkus/creator/phase/curate/CuratePhase.java +++ b/core/creator/src/main/java/io/quarkus/creator/phase/curate/CuratePhase.java @@ -18,15 +18,13 @@ package io.quarkus.creator.phase.curate; import java.io.IOException; +import java.nio.file.FileSystem; 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.HashSet; +import java.util.Iterator; import java.util.List; -import java.util.Set; import org.apache.maven.model.Dependency; import org.apache.maven.model.Model; @@ -35,17 +33,24 @@ import org.eclipse.aether.repository.RepositoryPolicy; import org.jboss.logging.Logger; -import io.quarkus.creator.AppArtifact; -import io.quarkus.creator.AppArtifactResolver; +import io.quarkus.bootstrap.BootstrapConstants; +import io.quarkus.bootstrap.BootstrapDependencyProcessingException; +import io.quarkus.bootstrap.model.AppArtifact; +import io.quarkus.bootstrap.model.AppDependency; +import io.quarkus.bootstrap.model.AppModel; +import io.quarkus.bootstrap.resolver.AppModelResolver; +import io.quarkus.bootstrap.resolver.AppModelResolverException; +import io.quarkus.bootstrap.resolver.BootstrapAppModelResolver; +import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver; +import io.quarkus.bootstrap.resolver.maven.workspace.ModelUtils; +import io.quarkus.bootstrap.util.ZipUtils; import io.quarkus.creator.AppCreationPhase; import io.quarkus.creator.AppCreator; import io.quarkus.creator.AppCreatorException; -import io.quarkus.creator.AppDependency; import io.quarkus.creator.config.reader.MappedPropertiesHandler; import io.quarkus.creator.config.reader.PropertiesConfigReaderException; import io.quarkus.creator.config.reader.PropertiesHandler; import io.quarkus.creator.outcome.OutcomeProviderRegistration; -import io.quarkus.creator.resolver.aether.AetherArtifactResolver; /** * @@ -58,9 +63,6 @@ public class CuratePhase implements AppCreationPhase { public static final String CONFIG_PROP_LOCAL_REPO = "local-repo"; public static final String CONFIG_PROP_VERSION_UPDATE = "version-update"; public static final String CONFIG_PROP_VERSION_UPDATE_NUMBER = "version-update-number"; - public static final String CONFIG_PROP_UPDATE_GROUP_ID = "update-groupId"; - - private static final String GROUP_ID_SPLIT_EXPR = "\\s*(,|\\s)\\s*"; public static String completePropertyName(String name) { return CONFIG_PROP + '.' + name; @@ -72,7 +74,6 @@ public static String completePropertyName(String name) { private VersionUpdate update = VersionUpdate.NONE; private VersionUpdateNumber updateNumber = VersionUpdateNumber.MICRO; private Path localRepo; - private Set updateGroupIds = Collections.singleton("io.quarkus"); public void setInitialDeps(DependenciesOrigin initialDeps) { this.depsOrigin = initialDeps; @@ -126,9 +127,6 @@ public CuratePhase getTarget() throws PropertiesConfigReaderException { + VersionUpdateNumber.MAJOR + ", " + VersionUpdateNumber.MINOR + " or " + VersionUpdateNumber.MICRO + " but was " + value); } - }) - .map(CONFIG_PROP_UPDATE_GROUP_ID, (target, value) -> { - updateGroupIds = new HashSet<>(Arrays.asList(value.split(GROUP_ID_SPLIT_EXPR))); }); } @@ -153,75 +151,82 @@ public void provideOutcome(AppCreator ctx) throws AppCreatorException { final CurateOutcome.Builder outcome = CurateOutcome.builder(); - final AppArtifact appArtifact = Utils.resolveAppArtifact(appJar); - outcome.setAppArtifact(appArtifact); + AppArtifact appArtifact; + try { + appArtifact = ModelUtils.resolveAppArtifact(appJar); + } catch (IOException e) { + throw new AppCreatorException("Failed to resolve application artifact coordindates from " + appJar, e); + } - AppArtifactResolver resolver = ctx.getArtifactResolver(); - if (resolver == null) { - final AetherArtifactResolver aetherResolver = AetherArtifactResolver - .getInstance(this.localRepo == null ? ctx.getWorkPath("repo") : this.localRepo); - aetherResolver.relink(appArtifact, appJar); - final List artifactRepos = aetherResolver.resolveArtifactRepos(appArtifact); - if (!artifactRepos.isEmpty()) { - aetherResolver.addRemoteRepositories(artifactRepos); - final List modelRepos = new ArrayList<>(artifactRepos.size()); - for (RemoteRepository repo : artifactRepos) { - final Repository modelRepo = new Repository(); - modelRepo.setId(repo.getId()); - modelRepo.setUrl(repo.getUrl()); - modelRepo.setLayout(repo.getContentType()); - RepositoryPolicy policy = repo.getPolicy(true); - if (policy != null) { - modelRepo.setSnapshots(toMavenRepoPolicy(policy)); - } - policy = repo.getPolicy(false); - if (policy != null) { - modelRepo.setReleases(toMavenRepoPolicy(policy)); + AppModelResolver modelResolver = ctx.getArtifactResolver(); + final AppModel initialDepsList; + try { + if (modelResolver == null) { + final BootstrapAppModelResolver bsResolver = new BootstrapAppModelResolver( + MavenArtifactResolver.builder() + .setRepoHome(this.localRepo == null ? ctx.getWorkPath("repo") : this.localRepo) + .build()); + bsResolver.relink(appArtifact, appJar); + final List artifactRepos = bsResolver.resolveArtifactRepos(appArtifact); + if (!artifactRepos.isEmpty()) { + bsResolver.addRemoteRepositories(artifactRepos); + final List modelRepos = new ArrayList<>(artifactRepos.size()); + for (RemoteRepository repo : artifactRepos) { + final Repository modelRepo = new Repository(); + modelRepo.setId(repo.getId()); + modelRepo.setUrl(repo.getUrl()); + modelRepo.setLayout(repo.getContentType()); + RepositoryPolicy policy = repo.getPolicy(true); + if (policy != null) { + modelRepo.setSnapshots(toMavenRepoPolicy(policy)); + } + policy = repo.getPolicy(false); + if (policy != null) { + modelRepo.setReleases(toMavenRepoPolicy(policy)); + } + modelRepos.add(modelRepo); } - modelRepos.add(modelRepo); + outcome.setArtifactRepos(modelRepos); } - outcome.setArtifactRepos(modelRepos); + modelResolver = bsResolver; + } else { + modelResolver.relink(appArtifact, appJar); } - resolver = aetherResolver; - } else { - resolver.relink(appArtifact, appJar); - } - outcome.setArtifactResolver(resolver); + outcome.setAppModelResolver(modelResolver); - final List initialDepsList; - if (depsOrigin == DependenciesOrigin.LAST_UPDATE) { - log.info("Looking for the state of the last update"); - Path statePath = null; - try { - AppArtifact stateArtifact = Utils.getStateArtifact(appArtifact); - final String latest = resolver.getLatestVersion(stateArtifact, null, false); - if (!stateArtifact.getVersion().equals(latest)) { - stateArtifact = new AppArtifact(stateArtifact.getGroupId(), - stateArtifact.getArtifactId(), - stateArtifact.getClassifier(), - stateArtifact.getType(), - latest); + if (depsOrigin == DependenciesOrigin.LAST_UPDATE) { + log.info("Looking for the state of the last update"); + Path statePath = null; + try { + AppArtifact stateArtifact = ModelUtils.getStateArtifact(appArtifact); + final String latest = modelResolver.getLatestVersion(stateArtifact, null, false); + if (!stateArtifact.getVersion().equals(latest)) { + stateArtifact = new AppArtifact(stateArtifact.getGroupId(), stateArtifact.getArtifactId(), + stateArtifact.getClassifier(), stateArtifact.getType(), latest); + } + statePath = modelResolver.resolve(stateArtifact); + outcome.setStateArtifact(stateArtifact); + log.info("- located the state at " + statePath); + } catch (AppModelResolverException e) { + // for now let's assume this means artifact does not exist + // System.out.println(" no state found"); } - statePath = resolver.resolve(stateArtifact); - outcome.setStateArtifact(stateArtifact); - log.info("- located the state at " + statePath); - } catch (AppCreatorException e) { - // for now let's assume this means artifact does not exist - //System.out.println(" no state found"); - } - if (statePath != null) { - try { - final Model model = Utils.readModel(statePath); + if (statePath != null) { + Model model; + try { + model = ModelUtils.readModel(statePath); + } catch (IOException e) { + throw new AppCreatorException("Failed to read application state " + statePath, e); + } /* - * final Properties props = model.getProperties(); - * final String appGroupId = props.getProperty(CurateOutcome.CREATOR_APP_GROUP_ID); - * final String appArtifactId = props.getProperty(CurateOutcome.CREATOR_APP_ARTIFACT_ID); - * final String appClassifier = props.getProperty(CurateOutcome.CREATOR_APP_CLASSIFIER); - * final String appType = props.getProperty(CurateOutcome.CREATOR_APP_TYPE); - * final String appVersion = props.getProperty(CurateOutcome.CREATOR_APP_VERSION); - * final AppArtifact modelAppArtifact = new AppArtifact(appGroupId, appArtifactId, appClassifier, appType, - * appVersion); + * final Properties props = model.getProperties(); final String appGroupId = + * props.getProperty(CurateOutcome.CREATOR_APP_GROUP_ID); final String appArtifactId = + * props.getProperty(CurateOutcome.CREATOR_APP_ARTIFACT_ID); final String appClassifier = + * props.getProperty(CurateOutcome.CREATOR_APP_CLASSIFIER); final String appType = + * props.getProperty(CurateOutcome.CREATOR_APP_TYPE); final String appVersion = + * props.getProperty(CurateOutcome.CREATOR_APP_VERSION); final AppArtifact modelAppArtifact = new + * AppArtifact(appGroupId, appArtifactId, appClassifier, appType, appVersion); */ final List modelStateDeps = model.getDependencies(); final List updatedDeps = new ArrayList<>(modelStateDeps.size()); @@ -231,32 +236,58 @@ public void provideOutcome(AppCreator ctx) throws AppCreatorException { continue; } updatedDeps.add(new AppDependency(new AppArtifact(modelDep.getGroupId(), modelDep.getArtifactId(), - modelDep.getClassifier(), modelDep.getType(), modelDep.getVersion()), modelDep.getScope())); + modelDep.getClassifier(), modelDep.getType(), modelDep.getVersion()), modelDep.getScope(), + modelDep.isOptional())); } - initialDepsList = resolver.collectDependencies(appArtifact, updatedDeps); + initialDepsList = modelResolver.resolveModel(appArtifact, updatedDeps); outcome.setLoadedFromState(); - } catch (IOException e) { - throw new AppCreatorException("Failed to load application state POM " + statePath, e); + } else { + initialDepsList = modelResolver.resolveModel(appArtifact); } } else { - initialDepsList = resolver.collectDependencies(appArtifact); + initialDepsList = modelResolver.resolveModel(appArtifact); } - } else { - initialDepsList = resolver.collectDependencies(appArtifact); + } catch (AppModelResolverException e) { + throw new AppCreatorException("Failed to resolve initial application dependencies", e); } - //logDeps("INITIAL:", initialDepsList); - - outcome.setInitialDeps(initialDepsList); + outcome.setAppModel(initialDepsList); if (update == VersionUpdate.NONE) { ctx.pushOutcome(outcome.build()); return; } log.info("Checking for available updates"); - final List appDeps = Utils.getUpdateCandidates(Utils.readAppModel(appJar, appArtifact).getDependencies(), - initialDepsList, updateGroupIds); - final UpdateDiscovery ud = new DefaultUpdateDiscovery(resolver, updateNumber); + List appDeps; + try { + appDeps = modelResolver.resolveUserDependencies(appArtifact, initialDepsList.getUserDependencies()); + } catch (AppModelResolverException | BootstrapDependencyProcessingException e) { + throw new AppCreatorException("Failed to determine the list of dependencies to update", e); + } + final Iterator depsI = appDeps.iterator(); + while (depsI.hasNext()) { + final AppArtifact appDep = depsI.next().getArtifact(); + if (!appDep.getType().equals(AppArtifact.TYPE_JAR)) { + depsI.remove(); + continue; + } + final Path path = appDep.getPath(); + if (Files.isDirectory(path)) { + if (!Files.exists(path.resolve(BootstrapConstants.DESCRIPTOR_PATH))) { + depsI.remove(); + } + } else { + try (FileSystem artifactFs = ZipUtils.newFileSystem(path)) { + if (!Files.exists(artifactFs.getPath(BootstrapConstants.DESCRIPTOR_PATH))) { + depsI.remove(); + } + } catch (IOException e) { + throw new AppCreatorException("Failed to open " + path, e); + } + } + } + + final UpdateDiscovery ud = new DefaultUpdateDiscovery(modelResolver, updateNumber); List availableUpdates = null; int i = 0; while (i < appDeps.size()) { @@ -264,7 +295,7 @@ public void provideOutcome(AppCreator ctx) throws AppCreatorException { final AppArtifact depArtifact = dep.getArtifact(); final String updatedVersion = update == VersionUpdate.NEXT ? ud.getNextVersion(depArtifact) : ud.getLatestVersion(depArtifact); - if (depArtifact.getVersion().equals(updatedVersion)) { + if (updatedVersion == null || depArtifact.getVersion().equals(updatedVersion)) { continue; } log.info(dep.getArtifact() + " -> " + updatedVersion); @@ -291,16 +322,4 @@ private static org.apache.maven.model.RepositoryPolicy toMavenRepoPolicy(Reposit mvnPolicy.setUpdatePolicy(policy.getUpdatePolicy()); return mvnPolicy; } - - private static void logDeps(String header, List deps) { - final List list = new ArrayList<>(deps.size()); - for (AppDependency dep : deps) { - list.add(dep.toString()); - } - Collections.sort(list); - System.out.println(header); - for (String str : list) { - System.out.println("- " + str); - } - } } diff --git a/core/creator/src/main/java/io/quarkus/creator/phase/curate/DefaultUpdateDiscovery.java b/core/creator/src/main/java/io/quarkus/creator/phase/curate/DefaultUpdateDiscovery.java index df684a70fa9d5..d9e2f3d640863 100644 --- a/core/creator/src/main/java/io/quarkus/creator/phase/curate/DefaultUpdateDiscovery.java +++ b/core/creator/src/main/java/io/quarkus/creator/phase/curate/DefaultUpdateDiscovery.java @@ -19,8 +19,9 @@ import java.util.List; -import io.quarkus.creator.AppArtifact; -import io.quarkus.creator.AppArtifactResolver; +import io.quarkus.bootstrap.model.AppArtifact; +import io.quarkus.bootstrap.resolver.AppModelResolver; +import io.quarkus.bootstrap.resolver.AppModelResolverException; import io.quarkus.creator.AppCreatorException; /** @@ -29,22 +30,30 @@ */ public class DefaultUpdateDiscovery implements UpdateDiscovery { - private final AppArtifactResolver resolver; + private final AppModelResolver resolver; private final VersionUpdateNumber updateNumber; - public DefaultUpdateDiscovery(AppArtifactResolver resolver, VersionUpdateNumber updateNumber) { + public DefaultUpdateDiscovery(AppModelResolver resolver, VersionUpdateNumber updateNumber) { this.resolver = resolver; this.updateNumber = updateNumber; } @Override public List listUpdates(AppArtifact artifact) throws AppCreatorException { - return resolver.listLaterVersions(artifact, resolveUpToVersion(artifact), false); + try { + return resolver.listLaterVersions(artifact, resolveUpToVersion(artifact), false); + } catch (AppModelResolverException e) { + throw new AppCreatorException("Failed to collect later versions", e); + } } @Override public String getNextVersion(AppArtifact artifact) throws AppCreatorException { - return resolver.getNextVersion(artifact, resolveUpToVersion(artifact), false); + try { + return resolver.getNextVersion(artifact, getFromVersion(artifact), true, resolveUpToVersion(artifact), false); + } catch (AppModelResolverException e) { + throw new AppCreatorException("Failed to determine the next available version", e); + } } @Override @@ -62,7 +71,11 @@ public String getLatestVersion(AppArtifact artifact) throws AppCreatorException * } * return latestStr; */ - return resolver.getLatestVersion(artifact, resolveUpToVersion(artifact), false); + try { + return resolver.getLatestVersion(artifact, resolveUpToVersion(artifact), false); + } catch (AppModelResolverException e) { + throw new AppCreatorException("Failed to determine the latest available version", e); + } } private String resolveUpToVersion(AppArtifact artifact) throws AppCreatorException { @@ -103,4 +116,55 @@ private String resolveUpToVersion(AppArtifact artifact) throws AppCreatorExcepti } return majorStr + "." + String.valueOf(minor + 1) + ".alpha"; } + + private String getFromVersion(AppArtifact artifact) throws AppCreatorException { + // here we are looking for the major version which is going to be used + // as the base for the version range to look for the updates + final String version = artifact.getVersion(); + final int majorMinorSep = version.indexOf('.'); + if (majorMinorSep <= 0) { + throw new AppCreatorException("Failed to determine the major version in " + version); + } + final String majorStr = version.substring(0, majorMinorSep); + if (updateNumber == VersionUpdateNumber.MAJOR) { + final long major; + try { + major = Long.parseLong(majorStr); + } catch (NumberFormatException e) { + throw new AppCreatorException( + "The version is expected to start with a number indicating the major version: " + version); + } + return String.valueOf(major + 1) + ".alpha"; + } + + final int minorMicroSep = version.indexOf('.', majorMinorSep + 1); + if (minorMicroSep <= 0) { + throw new AppCreatorException("Failed to determine the minor version in " + version); + } + final String minorStr = version.substring(majorMinorSep + 1, minorMicroSep); + if (updateNumber == VersionUpdateNumber.MINOR) { + final long minor; + try { + minor = Long.parseLong(minorStr); + } catch (NumberFormatException e) { + throw new AppCreatorException( + "Failed to parse the minor number in version: " + version); + } + return majorStr + "." + String.valueOf(minor + 1) + ".alpha"; + } + + if (minorMicroSep == version.length() - 1) { + throw new AppCreatorException("Failed to determine the micro version in " + version); + } + final String microStr = version.substring(minorMicroSep + 1); + + final long micro; + try { + micro = Long.parseLong(microStr); + } catch (NumberFormatException e) { + throw new AppCreatorException( + "Failed to parse the micro number in version: " + version); + } + return majorStr + "." + minorStr + "." + String.valueOf(micro + 1) + ".alpha"; + } } diff --git a/core/creator/src/main/java/io/quarkus/creator/phase/curate/DependenciesOrigin.java b/core/creator/src/main/java/io/quarkus/creator/phase/curate/DependenciesOrigin.java index dd06657edfbca..9c1b4f1bf136f 100644 --- a/core/creator/src/main/java/io/quarkus/creator/phase/curate/DependenciesOrigin.java +++ b/core/creator/src/main/java/io/quarkus/creator/phase/curate/DependenciesOrigin.java @@ -23,7 +23,9 @@ */ public enum DependenciesOrigin { - APPLICATION("application"), LAST_UPDATE("last-update"), UNKNOWN(null); + APPLICATION("application"), + LAST_UPDATE("last-update"), + UNKNOWN(null); private final String name; diff --git a/core/creator/src/main/java/io/quarkus/creator/phase/curate/UpdateDiscovery.java b/core/creator/src/main/java/io/quarkus/creator/phase/curate/UpdateDiscovery.java index 84942e042e815..68c8cee776433 100644 --- a/core/creator/src/main/java/io/quarkus/creator/phase/curate/UpdateDiscovery.java +++ b/core/creator/src/main/java/io/quarkus/creator/phase/curate/UpdateDiscovery.java @@ -19,7 +19,7 @@ import java.util.List; -import io.quarkus.creator.AppArtifact; +import io.quarkus.bootstrap.model.AppArtifact; import io.quarkus.creator.AppCreatorException; /** diff --git a/core/creator/src/main/java/io/quarkus/creator/phase/curate/Utils.java b/core/creator/src/main/java/io/quarkus/creator/phase/curate/Utils.java deleted file mode 100644 index 826e4f37a22df..0000000000000 --- a/core/creator/src/main/java/io/quarkus/creator/phase/curate/Utils.java +++ /dev/null @@ -1,297 +0,0 @@ -/* - * Copyright 2018 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed 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.creator.phase.curate; - -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.IOException; -import java.nio.file.DirectoryStream; -import java.nio.file.FileSystem; -import java.nio.file.FileSystems; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Properties; -import java.util.Set; - -import org.apache.maven.model.Dependency; -import org.apache.maven.model.Model; -import org.apache.maven.model.io.xpp3.MavenXpp3Reader; -import org.apache.maven.model.io.xpp3.MavenXpp3Writer; -import org.codehaus.plexus.util.xml.pull.XmlPullParserException; - -import io.quarkus.creator.AppArtifact; -import io.quarkus.creator.AppCreatorException; -import io.quarkus.creator.AppDependency; - -/** - * - * @author Alexey Loubyansky - */ -public class Utils { - - private static final String STATE_ARTIFACT_INITIAL_VERSION = "1"; - - /** - * Returns the provisioning state artifact for the given application artifact - * - * @param appArtifact application artifact - * @return provisioning state artifact - */ - static AppArtifact getStateArtifact(AppArtifact appArtifact) { - return new AppArtifact(appArtifact.getGroupId() + ".quarkus.curate", - appArtifact.getArtifactId(), - "", - "pom", - STATE_ARTIFACT_INITIAL_VERSION); - } - - /** - * Filters out non-platform from application POM dependencies. - * - * @param deps POM model application dependencies - * @param appDeps resolved application dependencies - * @return dependencies that can be checked for updates - * @throws AppCreatorException in case of a failure - */ - static List getUpdateCandidates(List deps, List appDeps, Set groupIds) - throws AppCreatorException { - final Map appDepMap = new LinkedHashMap<>(appDeps.size()); - for (AppDependency appDep : appDeps) { - appDepMap.put(new ArtifactKey(appDep.getArtifact()), appDep); - } - final List updateCandidates = new ArrayList<>(deps.size()); - // it's critical to preserve the order of the dependencies from the pom - for (Dependency dep : deps) { - if (!groupIds.contains(dep.getGroupId()) || "test".equals(dep.getScope())) { - continue; - } - final AppDependency appDep = appDepMap.remove(new ArtifactKey(dep)); - if (appDep == null) { - // This normally would be a dependency that's missing test in the artifact's pom - // but is marked as such in one of artifact's parent poms - //throw new AppCreatorException("Failed to locate dependency " + new AppArtifact(dep.getGroupId(), dep.getArtifactId(), dep.getClassifier(), dep.getType(), dep.getVersion()) + " present in pom.xml among resolved application dependencies"); - continue; - } - updateCandidates.add(appDep); - } - for (AppDependency appDep : appDepMap.values()) { - if (groupIds.contains(appDep.getArtifact().getGroupId())) { - updateCandidates.add(appDep); - } - } - return updateCandidates; - } - - static AppArtifact resolveAppArtifact(Path appJar) throws AppCreatorException { - try (FileSystem fs = FileSystems.newFileSystem(appJar, null)) { - final Path metaInfMaven = fs.getPath("META-INF", "maven"); - if (Files.exists(metaInfMaven)) { - try (DirectoryStream groupIds = Files.newDirectoryStream(metaInfMaven)) { - for (Path groupIdPath : groupIds) { - if (!Files.isDirectory(groupIdPath)) { - continue; - } - try (DirectoryStream artifactIds = Files.newDirectoryStream(groupIdPath)) { - for (Path artifactIdPath : artifactIds) { - if (!Files.isDirectory(artifactIdPath)) { - continue; - } - final Path propsPath = artifactIdPath.resolve("pom.properties"); - if (Files.exists(propsPath)) { - final Properties props = loadPomProps(appJar, artifactIdPath); - return new AppArtifact(props.getProperty("groupId"), props.getProperty("artifactId"), - props.getProperty("version")); - } - } - } - } - } - } - throw new AppCreatorException( - "Failed to located META-INF/maven///pom.properties in " + appJar); - } catch (IOException e) { - throw new AppCreatorException("Failed to load pom.properties from " + appJar, e); - } - } - - static Model readAppModel(Path appJar, AppArtifact appArtifact) throws AppCreatorException { - try (FileSystem fs = FileSystems.newFileSystem(appJar, null)) { - final Path pomXml = fs.getPath("META-INF", "maven", appArtifact.getGroupId(), appArtifact.getArtifactId(), - "pom.xml"); - if (!Files.exists(pomXml)) { - throw new AppCreatorException("Failed to located META-INF/maven///pom.xml in " + appJar); - } - try { - return readModel(pomXml); - } catch (IOException e) { - throw new AppCreatorException("Failed to read " + pomXml, e); - } - } catch (IOException e) { - throw new AppCreatorException("Failed to load pom.xml from " + appJar, e); - } - } - - static Model readAppModel(Path appJar) throws AppCreatorException { - try (FileSystem fs = FileSystems.newFileSystem(appJar, null)) { - final Path metaInfMaven = fs.getPath("META-INF", "maven"); - if (Files.exists(metaInfMaven)) { - try (DirectoryStream groupIds = Files.newDirectoryStream(metaInfMaven)) { - for (Path groupIdPath : groupIds) { - if (!Files.isDirectory(groupIdPath)) { - continue; - } - try (DirectoryStream artifactIds = Files.newDirectoryStream(groupIdPath)) { - for (Path artifactIdPath : artifactIds) { - if (!Files.isDirectory(artifactIdPath)) { - continue; - } - final Path pomXml = artifactIdPath.resolve("pom.xml"); - if (Files.exists(pomXml)) { - final Model model; - try { - model = readModel(pomXml); - } catch (IOException e) { - throw new AppCreatorException("Failed to read " + pomXml, e); - } - Properties props = null; - if (model.getGroupId() == null) { - props = loadPomProps(appJar, artifactIdPath); - final String groupId = props.getProperty("groupId"); - if (groupId == null) { - throw new AppCreatorException("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 AppCreatorException( - "Failed to determine the artifact version for " + appJar); - } - model.setVersion(version); - } - return model; - } - } - } - } - } - } - throw new AppCreatorException("Failed to located META-INF/maven///pom.xml in " + appJar); - } catch (IOException e) { - throw new AppCreatorException("Failed to load pom.xml from " + appJar, e); - } - } - - private static Properties loadPomProps(Path appJar, Path artifactIdPath) throws AppCreatorException { - final Path propsPath = artifactIdPath.resolve("pom.properties"); - if (!Files.exists(propsPath)) { - throw new AppCreatorException( - "Failed to located META-INF/maven///pom.properties in " + appJar); - } - final Properties props = new Properties(); - try (BufferedReader reader = Files.newBufferedReader(propsPath)) { - props.load(reader); - } catch (IOException e) { - throw new AppCreatorException("Failed to read " + propsPath, e); - } - return props; - } - - static Model readModel(final Path pomXml) throws IOException, AppCreatorException { - try (BufferedReader reader = Files.newBufferedReader(pomXml)) { - final MavenXpp3Reader xpp3Reader = new MavenXpp3Reader(); - return xpp3Reader.read(reader); - } catch (XmlPullParserException e) { - throw new AppCreatorException("Failed to parse application POM model", e); - } - } - - public static void persistModel(Path pomFile, Model model) throws AppCreatorException { - final MavenXpp3Writer xpp3Writer = new MavenXpp3Writer(); - try (BufferedWriter pomFileWriter = Files.newBufferedWriter(pomFile)) { - xpp3Writer.write(pomFileWriter, model); - } catch (IOException e) { - throw new AppCreatorException("Faile to write the pom.xml file", e); - } - } - - private static class ArtifactKey { - final String groupId; - final String artifactId; - final String classifier; - - ArtifactKey(AppArtifact artifact) { - this.groupId = artifact.getGroupId(); - this.artifactId = artifact.getArtifactId(); - this.classifier = artifact.getClassifier(); - } - - ArtifactKey(Dependency artifact) { - this.groupId = artifact.getGroupId(); - this.artifactId = artifact.getArtifactId(); - final String classifier = artifact.getClassifier(); - this.classifier = classifier == null ? "" : classifier; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((artifactId == null) ? 0 : artifactId.hashCode()); - result = prime * result + ((classifier == null) ? 0 : classifier.hashCode()); - result = prime * result + ((groupId == null) ? 0 : groupId.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; - ArtifactKey other = (ArtifactKey) obj; - if (artifactId == null) { - if (other.artifactId != null) - return false; - } else if (!artifactId.equals(other.artifactId)) - return false; - if (classifier == null) { - if (other.classifier != null) - return false; - } else if (!classifier.equals(other.classifier)) - return false; - if (groupId == null) { - if (other.groupId != null) - return false; - } else if (!groupId.equals(other.groupId)) - return false; - return true; - } - } - -} diff --git a/core/creator/src/main/java/io/quarkus/creator/phase/curate/VersionUpdate.java b/core/creator/src/main/java/io/quarkus/creator/phase/curate/VersionUpdate.java index 3e58265ed75e9..71666be83d097 100644 --- a/core/creator/src/main/java/io/quarkus/creator/phase/curate/VersionUpdate.java +++ b/core/creator/src/main/java/io/quarkus/creator/phase/curate/VersionUpdate.java @@ -23,7 +23,10 @@ */ public enum VersionUpdate { - LATEST("latest"), NEXT("next"), NONE("none"), UNKNOWN(null); + LATEST("latest"), + NEXT("next"), + NONE("none"), + UNKNOWN(null); private final String name; diff --git a/core/creator/src/main/java/io/quarkus/creator/phase/curate/VersionUpdateNumber.java b/core/creator/src/main/java/io/quarkus/creator/phase/curate/VersionUpdateNumber.java index 183ae1ccba459..a873776917599 100644 --- a/core/creator/src/main/java/io/quarkus/creator/phase/curate/VersionUpdateNumber.java +++ b/core/creator/src/main/java/io/quarkus/creator/phase/curate/VersionUpdateNumber.java @@ -23,7 +23,10 @@ */ public enum VersionUpdateNumber { - MAJOR("major"), MINOR("minor"), MICRO("micro"), UNKNOWN(null); + MAJOR("major"), + MINOR("minor"), + MICRO("micro"), + UNKNOWN(null); private final String name; diff --git a/core/creator/src/main/java/io/quarkus/creator/phase/nativeimage/NativeImagePhase.java b/core/creator/src/main/java/io/quarkus/creator/phase/nativeimage/NativeImagePhase.java index 3554376ed8509..b350a46b7c019 100644 --- a/core/creator/src/main/java/io/quarkus/creator/phase/nativeimage/NativeImagePhase.java +++ b/core/creator/src/main/java/io/quarkus/creator/phase/nativeimage/NativeImagePhase.java @@ -20,7 +20,8 @@ import java.io.BufferedReader; import java.io.File; import java.io.IOException; -import java.io.PrintStream; +import java.io.InputStream; +import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -38,6 +39,7 @@ import org.eclipse.microprofile.config.Config; import org.jboss.logging.Logger; +import io.quarkus.bootstrap.util.IoUtils; import io.quarkus.creator.AppCreationPhase; import io.quarkus.creator.AppCreator; import io.quarkus.creator.AppCreatorException; @@ -46,7 +48,6 @@ import io.quarkus.creator.outcome.OutcomeProviderRegistration; import io.quarkus.creator.phase.augment.AugmentOutcome; import io.quarkus.creator.phase.runnerjar.RunnerJarOutcome; -import io.quarkus.creator.util.IoUtils; import io.smallrye.config.SmallRyeConfigProviderResolver; /** @@ -63,6 +64,7 @@ public class NativeImagePhase implements AppCreationPhase, Nat private static final String QUARKUS_PREFIX = "quarkus."; private static final boolean IS_LINUX = System.getProperty("os.name").toLowerCase(Locale.ROOT).contains("linux"); + private static final boolean IS_WINDOWS = System.getProperty("os.name").toLowerCase(Locale.ROOT).contains("windows"); private Path outputDir; @@ -86,6 +88,8 @@ public class NativeImagePhase implements AppCreationPhase, Nat private boolean enableIsolates; + private boolean enableFallbackImages; + private String graalvmHome; private boolean enableServer; @@ -98,7 +102,11 @@ public class NativeImagePhase implements AppCreationPhase, Nat private String nativeImageXmx; - private String dockerBuild; + private String builderImage = "quay.io/quarkus/centos-quarkus-native-image:graalvm-1.0.0-rc16"; + + private String containerRuntime = ""; + + private List containerRuntimeOptions = new ArrayList<>(); private boolean enableVMInspection; @@ -170,6 +178,11 @@ public NativeImagePhase setEnableIsolates(boolean enableIsolates) { return this; } + public NativeImagePhase setEnableFallbackImages(boolean enableFallbackImages) { + this.enableFallbackImages = enableFallbackImages; + return this; + } + public NativeImagePhase setGraalvmHome(String graalvmHome) { this.graalvmHome = graalvmHome; return this; @@ -201,7 +214,41 @@ public NativeImagePhase setNativeImageXmx(String nativeImageXmx) { } public NativeImagePhase setDockerBuild(String dockerBuild) { - this.dockerBuild = dockerBuild; + if (dockerBuild == null) { + return this; + } + + if ("false".equals(dockerBuild.toLowerCase())) { + this.containerRuntime = ""; + } else { + this.containerRuntime = "docker"; + + // TODO: use an 'official' image + if (!"true".equals(dockerBuild.toLowerCase())) { + this.builderImage = dockerBuild; + } + } + + return this; + } + + public NativeImagePhase setContainerRuntime(String containerRuntime) { + if (containerRuntime == null) { + return this; + } + if ("podman".equals(containerRuntime) || "docker".equals(containerRuntime)) { + this.containerRuntime = containerRuntime; + } else { + log.warn("container runtime is not docker or podman. fallback to docker"); + this.containerRuntime = "docker"; + } + return this; + } + + public NativeImagePhase setContainerRuntimeOptions(String containerRuntimeOptions) { + if (containerRuntimeOptions != null) { + this.containerRuntimeOptions = Arrays.asList(containerRuntimeOptions.split(",")); + } return this; } @@ -271,19 +318,20 @@ public void provideOutcome(AppCreator ctx) throws AppCreatorException { String noPIE = ""; - if (dockerBuild != null && !dockerBuild.toLowerCase().equals("false")) { - + if (!"".equals(containerRuntime)) { // E.g. "/usr/bin/docker run -v {{PROJECT_DIR}}:/project --rm quarkus/graalvm-native-image" nativeImage = new ArrayList<>(); - //TODO: use an 'official' image - String image; - if (dockerBuild.toLowerCase().equals("true")) { - image = "swd847/centos-graal-native-image-rc13"; - } else { - //allow the use of a custom image - image = dockerBuild; + Collections.addAll(nativeImage, containerRuntime, "run", "-v", outputDir.toAbsolutePath() + ":/project:z", "--rm"); + + if (IS_LINUX & "docker".equals(containerRuntime)) { + String uid = getLinuxID("-ur"); + String gid = getLinuxID("-gr"); + if (uid != null & gid != null & !"".equals(uid) & !"".equals(gid)) { + Collections.addAll(nativeImage, "--user", uid.concat(":").concat(gid)); + } } - Collections.addAll(nativeImage, "docker", "run", "-v", outputDir.toAbsolutePath() + ":/project:z", "--rm", image); + nativeImage.addAll(containerRuntimeOptions); + nativeImage.add(this.builderImage); } else { if (IS_LINUX) { noPIE = detectNoPIE(); @@ -298,12 +346,13 @@ public void provideOutcome(AppCreator ctx) throws AppCreatorException { throw new AppCreatorException("GRAALVM_HOME was not set"); } } - nativeImage = Collections.singletonList(graalvmHome + File.separator + "bin" + File.separator + "native-image"); + String imageName = IS_WINDOWS ? "native-image.cmd" : "native-image"; + nativeImage = Collections.singletonList(graalvmHome + File.separator + "bin" + File.separator + imageName); + } try { - List command = new ArrayList<>(); - command.addAll(nativeImage); + List command = new ArrayList<>(nativeImage); if (cleanupServer) { List cleanup = new ArrayList<>(nativeImage); cleanup.add("--server-shutdown"); @@ -349,13 +398,21 @@ public void provideOutcome(AppCreator ctx) throws AppCreatorException { enableAllSecurityServices = true; } if (additionalBuildArgs != null) { - additionalBuildArgs.forEach(command::add); + command.addAll(additionalBuildArgs); } 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 (enableFallbackImages) { + command.add("-H:FallbackThreshold=5"); + } else { + //Default: be strict as those fallback images aren't very useful + //and tend to cover up real problems. + command.add("-H:FallbackThreshold=0"); + } + if (reportErrorsAtRuntime) { command.add("-H:+ReportUnsupportedElementsAtRuntime"); } @@ -413,7 +470,7 @@ public void provideOutcome(AppCreator ctx) throws AppCreatorException { } else { command.add("-H:-JNI"); } - if (!enableServer) { + if (!enableServer && !IS_WINDOWS) { command.add("--no-server"); } if (enableVMInspection) { @@ -466,13 +523,62 @@ public void provideOutcome(AppCreator ctx) throws AppCreatorException { private boolean isThisGraalVMRCObsolete() { final String vmName = System.getProperty("java.vm.name"); log.info("Running Quarkus native-image plugin on " + vmName); - if (vmName.contains("-rc9") || vmName.contains("-rc10") || vmName.contains("-rc11") || vmName.contains("-rc12")) { - log.error("Out of date RC build of GraalVM detected! Please upgrade to RC13"); + final List obsoleteGraalVmVersions = Arrays.asList("-rc9", "-rc10", "-rc11", "-rc12", "-rc13", "-rc14", + "-rc15"); + final boolean vmVersionIsObsolete = obsoleteGraalVmVersions.stream().anyMatch(vmName::contains); + if (vmVersionIsObsolete) { + log.error("Out of date RC build of GraalVM detected! Please upgrade to GraalVM RC16"); return true; } return false; } + private static String getLinuxID(String option) { + Process process; + + try { + StringBuilder responseBuilder = new StringBuilder(); + String line; + + ProcessBuilder idPB = new ProcessBuilder().command("id", option); + idPB.redirectError(new File("/dev/null")); + idPB.redirectInput(new File("/dev/null")); + + process = idPB.start(); + try (InputStream inputStream = process.getInputStream()) { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { + while ((line = reader.readLine()) != null) { + responseBuilder.append(line); + } + safeWaitFor(process); + return responseBuilder.toString(); + } + } catch (Throwable t) { + safeWaitFor(process); + throw t; + } + } catch (IOException e) { //from process.start() + //swallow and return null id + return null; + } + } + + static void safeWaitFor(Process process) { + boolean intr = false; + try { + for (;;) + try { + process.waitFor(); + return; + } catch (InterruptedException ex) { + intr = true; + } + } finally { + if (intr) + Thread.currentThread().interrupt(); + } + } + private static String detectNoPIE() { String argument = testGCCArgument("-no-pie"); @@ -545,6 +651,9 @@ public boolean set(NativeImagePhase t, PropertyContext ctx) { case "enable-isolates": t.setEnableIsolates(Boolean.parseBoolean(value)); break; + case "enable-fallback-images": + t.setEnableFallbackImages(Boolean.parseBoolean(value)); + break; case "graalvm-home": t.setGraalvmHome(value); break; diff --git a/core/creator/src/main/java/io/quarkus/creator/phase/runnerjar/RunnerJarOutcome.java b/core/creator/src/main/java/io/quarkus/creator/phase/runnerjar/RunnerJarOutcome.java index 7274d409f68f3..879f4ed4bdcd0 100644 --- a/core/creator/src/main/java/io/quarkus/creator/phase/runnerjar/RunnerJarOutcome.java +++ b/core/creator/src/main/java/io/quarkus/creator/phase/runnerjar/RunnerJarOutcome.java @@ -28,4 +28,6 @@ public interface RunnerJarOutcome { Path getRunnerJar(); Path getLibDir(); + + Path getOriginalJar(); } diff --git a/core/creator/src/main/java/io/quarkus/creator/phase/runnerjar/RunnerJarPhase.java b/core/creator/src/main/java/io/quarkus/creator/phase/runnerjar/RunnerJarPhase.java index dbdf1834e74d3..b7a7b8342b1f9 100644 --- a/core/creator/src/main/java/io/quarkus/creator/phase/runnerjar/RunnerJarPhase.java +++ b/core/creator/src/main/java/io/quarkus/creator/phase/runnerjar/RunnerJarPhase.java @@ -31,9 +31,9 @@ import java.nio.file.SimpleFileVisitor; import java.nio.file.StandardCopyOption; import java.nio.file.attribute.BasicFileAttributes; -import java.nio.file.attribute.PosixFilePermissions; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; @@ -47,19 +47,19 @@ import org.jboss.logging.Logger; -import io.quarkus.creator.AppArtifact; -import io.quarkus.creator.AppArtifactResolver; +import io.quarkus.bootstrap.model.AppArtifact; +import io.quarkus.bootstrap.model.AppDependency; +import io.quarkus.bootstrap.resolver.AppModelResolver; +import io.quarkus.bootstrap.util.IoUtils; +import io.quarkus.bootstrap.util.ZipUtils; import io.quarkus.creator.AppCreationPhase; import io.quarkus.creator.AppCreator; import io.quarkus.creator.AppCreatorException; -import io.quarkus.creator.AppDependency; import io.quarkus.creator.config.reader.MappedPropertiesHandler; import io.quarkus.creator.config.reader.PropertiesHandler; import io.quarkus.creator.outcome.OutcomeProviderRegistration; import io.quarkus.creator.phase.augment.AugmentOutcome; import io.quarkus.creator.phase.curate.CurateOutcome; -import io.quarkus.creator.util.IoUtils; -import io.quarkus.creator.util.ZipUtils; /** * Based on the provided {@link io.quarkus.creator.phase.augment.AugmentOutcome}, @@ -70,7 +70,6 @@ public class RunnerJarPhase implements AppCreationPhase, RunnerJarOutcome { private static final String DEFAULT_MAIN_CLASS = "io.quarkus.runner.GeneratedMain"; - private static final String PROVIDED = "provided"; private static final Logger log = Logger.getLogger(RunnerJarPhase.class); @@ -79,20 +78,28 @@ public class RunnerJarPhase implements AppCreationPhase, RunnerJ "META-INF/MANIFEST.MF", "module-info.class", "META-INF/LICENSE", - "META-INF/NOTICE", "META-INF/LICENSE.txt", + "META-INF/LICENSE.md", + "META-INF/NOTICE", "META-INF/NOTICE.txt", - "dependencies.runtime", + "META-INF/NOTICE.md", "META-INF/README", - "META-INF/quarkus-config-roots.list", + "META-INF/README.txt", + "META-INF/README.md", "META-INF/DEPENDENCIES", "META-INF/beans.xml", + "META-INF/quarkus-config-roots.list", "META-INF/quarkus-javadoc.properties", + "META-INF/quarkus-extension.properties", + "META-INF/quarkus-deployment-dependency.graph", "LICENSE"))); + private final Set userConfiguredIgnoredEntries = new HashSet<>(); + private Path outputDir; private Path libDir; private Path runnerJar; + private Path originalJar; private String finalName; @@ -160,6 +167,19 @@ public RunnerJarPhase setUberJar(boolean uberJar) { return this; } + /** + * Entries that should be ignored when creating the runner JAR. The entries + * are relatives to the root of the JAR. I.e. "META-INF/README.MD". + * + * @param ignoredEntries the entries that should be ignored when creating + * the runner JAR + * @return this phase instance + */ + public RunnerJarPhase setUserConfiguredIgnoredEntries(Collection ignoredEntries) { + this.userConfiguredIgnoredEntries.addAll(ignoredEntries); + return this; + } + @Override public Path getRunnerJar() { return runnerJar; @@ -170,6 +190,11 @@ public Path getLibDir() { return libDir; } + @Override + public Path getOriginalJar() { + return originalJar; + } + @Override public void register(OutcomeProviderRegistration registration) throws AppCreatorException { registration.provides(RunnerJarOutcome.class); @@ -184,7 +209,7 @@ public void provideOutcome(AppCreator ctx) throws AppCreatorException { libDir = IoUtils.mkdirs(libDir == null ? outputDir.resolve("lib") : libDir); if (finalName == null) { - final String name = appState.getArtifactResolver().resolve(appState.getAppArtifact()).getFileName().toString(); + final String name = toUri(appState.getAppArtifact().getPath().getFileName()); int i = name.lastIndexOf('.'); if (i > 0) { finalName = name.substring(0, i); @@ -206,39 +231,42 @@ public void provideOutcome(AppCreator ctx) throws AppCreatorException { } // when using uberJar, we rename the standard jar to include the .original suffix - // this greatly aids tools (such as s2i) that look for a single jar in the output directory to work OOTB + // this greatly aids tools (such as s2i) that look for a single jar in the output directory to work OOTB. + // we only do this if the standard jar was present in the output dir in the first place. if (uberJar) { try { - Path originalFile = outputDir.resolve(finalName + ".jar.original"); - Files.deleteIfExists(originalFile); - Files.move(outputDir.resolve(finalName + ".jar"), originalFile); + Path standardJar = outputDir.resolve(finalName + ".jar"); + if (standardJar.toFile().exists()) { + originalJar = outputDir.resolve(finalName + ".jar.original"); + Files.deleteIfExists(originalJar); + Files.move(standardJar, originalJar); + } } catch (IOException e) { throw new AppCreatorException("Unable to build uberjar", e); } + } else { + originalJar = outputDir.resolve(finalName + ".jar"); } ctx.pushOutcome(RunnerJarOutcome.class, this); } - private void buildRunner(FileSystem runnerZipFs, CurateOutcome appState, AugmentOutcome augmentOutcome) throws Exception { + private void buildRunner(FileSystem runnerZipFs, CurateOutcome curateOutcome, AugmentOutcome augmentOutcome) + throws Exception { log.info("Building jar: " + runnerJar); - final AppArtifactResolver depResolver = appState.getArtifactResolver(); - final List appDeps = appState.getEffectiveDeps(); + final AppModelResolver depResolver = curateOutcome.getArtifactResolver(); final Map seen = new HashMap<>(); final Map> duplicateCatcher = new HashMap<>(); final StringBuilder classPath = new StringBuilder(); final Map> services = new HashMap<>(); + Set finalIgnoredEntries = new HashSet<>(IGNORED_ENTRIES); + finalIgnoredEntries.addAll(this.userConfiguredIgnoredEntries); + final List appDeps = curateOutcome.getEffectiveModel().getUserDependencies(); for (AppDependency appDep : appDeps) { - if (appDep.getScope().equals(PROVIDED) && !augmentOutcome.isWhitelisted(appDep)) { - continue; - } final AppArtifact depArtifact = appDep.getArtifact(); - if (depArtifact.getArtifactId().equals("svm") && depArtifact.getGroupId().equals("com.oracle.substratevm")) { - continue; - } final Path resolvedDep = depResolver.resolve(depArtifact); if (uberJar) { try (FileSystem artifactFs = ZipUtils.newFileSystem(resolvedDep)) { @@ -248,9 +276,9 @@ private void buildRunner(FileSystem runnerZipFs, CurateOutcome appState, Augment @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { - final String relativePath = root.relativize(dir).toString(); + final String relativePath = toUri(root.relativize(dir)); if (!relativePath.isEmpty()) { - addDir(runnerZipFs, dir, relativePath); + addDir(runnerZipFs, relativePath); } return FileVisitResult.CONTINUE; } @@ -258,10 +286,11 @@ public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { - final String relativePath = root.relativize(file).toString(); + final String relativePath = toUri(root.relativize(file)); if (relativePath.startsWith("META-INF/services/") && relativePath.length() > 18) { services.computeIfAbsent(relativePath, (u) -> new ArrayList<>()).add(read(file)); - } else if (!IGNORED_ENTRIES.contains(relativePath)) { + return FileVisitResult.CONTINUE; + } else if (!finalIgnoredEntries.contains(relativePath)) { duplicateCatcher.computeIfAbsent(relativePath, (a) -> new HashSet<>()).add(appDep); if (!seen.containsKey(relativePath)) { seen.put(relativePath, appDep.toString()); @@ -302,11 +331,11 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) @Override public void accept(Path path) { try { - final String relativePath = wiringClassesDir.relativize(path).toString(); + final String relativePath = toUri(wiringClassesDir.relativize(path)); if (Files.isDirectory(path)) { if (!seen.containsKey(relativePath + "/") && !relativePath.isEmpty()) { seen.put(relativePath + "/", "Current Application"); - addDir(runnerZipFs, path, relativePath); + addDir(runnerZipFs, relativePath); } return; } @@ -325,16 +354,13 @@ public void accept(Path path) { } }); - final Manifest manifest = new Manifest(); - manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0"); - manifest.getMainAttributes().put(Attributes.Name.CLASS_PATH, classPath.toString()); - manifest.getMainAttributes().put(Attributes.Name.MAIN_CLASS, mainClass); - try (OutputStream os = Files.newOutputStream(runnerZipFs.getPath("META-INF", "MANIFEST.MF"))) { - manifest.write(os); + copyFiles(augmentOutcome.getAppClassesDir(), runnerZipFs, services); + if (Files.exists(augmentOutcome.getConfigDir())) { + copyFiles(augmentOutcome.getConfigDir(), runnerZipFs, services); } + copyFiles(augmentOutcome.getTransformedClassesDir(), runnerZipFs, services); - copyFiles(augmentOutcome.getAppClassesDir(), runnerZipFs); - copyFiles(augmentOutcome.getTransformedClassesDir(), runnerZipFs); + generateManifest(runnerZipFs, classPath.toString()); for (Map.Entry> entry : services.entrySet()) { try (OutputStream os = Files.newOutputStream(runnerZipFs.getPath(entry.getKey()))) { @@ -346,32 +372,95 @@ public void accept(Path path) { } } - private void copyFiles(Path dir, FileSystem fs) throws IOException { - Files.walk(dir).forEach(new Consumer() { - @Override - public void accept(Path path) { - final String relativePath = dir.relativize(path).toString(); - if (relativePath.isEmpty()) { - return; - } - try { - if (Files.isDirectory(path)) { - addDir(fs, path, relativePath); - } else { - Files.copy(path, fs.getPath(relativePath), StandardCopyOption.REPLACE_EXISTING); + /** + * Manifest generation is quite simple : we just have to push some attributes in manifest. + * However, it gets a little more complex if the manifest preexists. + * So we first try to see if a manifest exists, and otherwise create a new one. + * + * BEWARE this method should be invoked after file copy from target/classes and so on. + * Otherwise this manifest manipulation will be useless. + */ + private void generateManifest(FileSystem runnerZipFs, final String classPath) throws IOException { + final Path manifestPath = runnerZipFs.getPath("META-INF", "MANIFEST.MF"); + final Manifest manifest = new Manifest(); + if (Files.exists(manifestPath)) { + try (InputStream is = Files.newInputStream(manifestPath)) { + manifest.read(is); + } + Files.delete(manifestPath); + } + Attributes attributes = manifest.getMainAttributes(); + attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0"); + if (attributes.containsKey(Attributes.Name.CLASS_PATH)) { + log.warn( + "Your MANIFEST.MF already defined a CLASS_PATH entry. Quarkus has overwritten this existing entry."); + } + attributes.put(Attributes.Name.CLASS_PATH, classPath); + if (attributes.containsKey(Attributes.Name.MAIN_CLASS)) { + String existingMainClass = attributes.getValue(Attributes.Name.MAIN_CLASS); + if (!mainClass.equals(existingMainClass)) { + log.warn("Your MANIFEST.MF already defined a MAIN_CLASS entry. Quarkus has overwritten your existing entry."); + } + } + attributes.put(Attributes.Name.MAIN_CLASS, mainClass); + try (OutputStream os = Files.newOutputStream(manifestPath)) { + manifest.write(os); + } + } + + /** + * Copy files from {@code dir} to {@code fs}, filtering out service providers into the given map. + * + * @param dir the source directory + * @param fs the destination filesystem + * @param services the services map + * @throws IOException if an error occurs + */ + private void copyFiles(Path dir, FileSystem fs, Map> services) throws IOException { + try { + Files.walk(dir).forEach(new Consumer() { + @Override + public void accept(Path path) { + final Path file = dir.relativize(path); + final String relativePath = toUri(file); + if (relativePath.isEmpty()) { + return; + } + try { + if (Files.isDirectory(path)) { + addDir(fs, relativePath); + } else { + if (relativePath.startsWith("META-INF/services/") && relativePath.length() > 18) { + final byte[] content; + try { + content = Files.readAllBytes(path); + } catch (IOException e) { + throw new RuntimeException(e); + } + services.computeIfAbsent(relativePath, (u) -> new ArrayList<>()).add(content); + } else { + Files.copy(path, fs.getPath(relativePath), StandardCopyOption.REPLACE_EXISTING); + } + } + } catch (Exception e) { + throw new RuntimeException(e); } - } catch (Exception e) { - throw new RuntimeException(e); } + }); + } catch (RuntimeException re) { + final Throwable cause = re.getCause(); + if (cause instanceof IOException) { + throw (IOException) cause; } - }); + throw re; + } } - private void addDir(FileSystem fs, Path dir, final String relativePath) + private void addDir(FileSystem fs, final String relativePath) throws IOException, FileAlreadyExistsException { final Path targetDir = fs.getPath(relativePath); try { - Files.copy(dir, targetDir); + Files.createDirectory(targetDir); } catch (FileAlreadyExistsException e) { if (!Files.isDirectory(targetDir)) { throw e; @@ -410,4 +499,23 @@ public RunnerJarPhase getTarget() { .map("main-class", RunnerJarPhase::setMainClass) .map("uber-jar", (RunnerJarPhase t, String value) -> t.setUberJar(Boolean.parseBoolean(value))); } + + private static String toUri(Path path) { + if (path.isAbsolute()) { + return path.toUri().getPath(); + } else if (path.getNameCount() == 0) { + return ""; + } else { + return toUri(new StringBuilder(), path, 0).toString(); + } + } + + private static StringBuilder toUri(StringBuilder b, Path path, int seg) { + b.append(path.getName(seg)); + if (seg < path.getNameCount() - 1) { + b.append('/'); + toUri(b, path, seg + 1); + } + return b; + } } diff --git a/core/creator/src/main/java/io/quarkus/creator/resolver/aether/AetherArtifactResolver.java b/core/creator/src/main/java/io/quarkus/creator/resolver/aether/AetherArtifactResolver.java deleted file mode 100644 index dccd262f6fbc7..0000000000000 --- a/core/creator/src/main/java/io/quarkus/creator/resolver/aether/AetherArtifactResolver.java +++ /dev/null @@ -1,344 +0,0 @@ -/* - * Copyright 2018 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed 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.creator.resolver.aether; - -import java.io.File; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import org.apache.maven.settings.Settings; -import org.eclipse.aether.DefaultRepositorySystemSession; -import org.eclipse.aether.RepositorySystem; -import org.eclipse.aether.RepositorySystemSession; -import org.eclipse.aether.artifact.Artifact; -import org.eclipse.aether.artifact.DefaultArtifact; -import org.eclipse.aether.collection.CollectRequest; -import org.eclipse.aether.graph.Dependency; -import org.eclipse.aether.graph.DependencyNode; -import org.eclipse.aether.installation.InstallRequest; -import org.eclipse.aether.installation.InstallationException; -import org.eclipse.aether.repository.LocalRepository; -import org.eclipse.aether.repository.RemoteRepository; -import org.eclipse.aether.resolution.ArtifactDescriptorException; -import org.eclipse.aether.resolution.ArtifactDescriptorRequest; -import org.eclipse.aether.resolution.ArtifactDescriptorResult; -import org.eclipse.aether.resolution.ArtifactRequest; -import org.eclipse.aether.resolution.ArtifactResolutionException; -import org.eclipse.aether.resolution.ArtifactResult; -import org.eclipse.aether.resolution.DependencyRequest; -import org.eclipse.aether.resolution.DependencyResolutionException; -import org.eclipse.aether.resolution.DependencyResult; -import org.eclipse.aether.resolution.VersionRangeRequest; -import org.eclipse.aether.resolution.VersionRangeResolutionException; -import org.eclipse.aether.resolution.VersionRangeResult; -import org.eclipse.aether.version.Version; - -import io.quarkus.creator.AppArtifact; -import io.quarkus.creator.AppArtifactResolverBase; -import io.quarkus.creator.AppCreatorException; -import io.quarkus.creator.AppDependency; - -/** - * - * @author Alexey Loubyansky - */ -public class AetherArtifactResolver extends AppArtifactResolverBase { - - public static AetherArtifactResolver getInstance(Path repoHome) throws AppCreatorException { - final RepositorySystem repoSystem = MavenRepoInitializer.getRepositorySystem(); - final Settings settings = MavenRepoInitializer.getSettings(); - final DefaultRepositorySystemSession repoSession = MavenRepoInitializer.newSession(repoSystem, settings); - final AppCreatorLocalRepositoryManager appCreatorLocalRepoManager = new AppCreatorLocalRepositoryManager( - repoSystem.newLocalRepositoryManager(repoSession, - new LocalRepository(repoHome.toString())), - Paths.get(MavenRepoInitializer.getLocalRepo(settings))); - repoSession.setLocalRepositoryManager(appCreatorLocalRepoManager); - repoSession.setDependencySelector(new AppCreatorDependencySelector(true)); - final AetherArtifactResolver resolver = new AetherArtifactResolver(repoSystem, repoSession, - MavenRepoInitializer.getRemoteRepos(settings)); - resolver.setLocalRepositoryManager(appCreatorLocalRepoManager); - return resolver; - } - - public static AetherArtifactResolver getInstance(Path repoHome, List remoteRepos) - throws AppCreatorException { - final RepositorySystem repoSystem = MavenRepoInitializer.getRepositorySystem(); - final Settings settings = MavenRepoInitializer.getSettings(); - final DefaultRepositorySystemSession repoSession = MavenRepoInitializer.newSession(repoSystem, settings); - final AppCreatorLocalRepositoryManager appCreatorLocalRepoManager = new AppCreatorLocalRepositoryManager( - repoSystem.newLocalRepositoryManager(repoSession, - new LocalRepository(repoHome.toString())), - Paths.get(MavenRepoInitializer.getLocalRepo(settings))); - repoSession.setLocalRepositoryManager(appCreatorLocalRepoManager); - repoSession.setDependencySelector(new AppCreatorDependencySelector(true)); - final AetherArtifactResolver resolver = new AetherArtifactResolver(repoSystem, repoSession, remoteRepos); - resolver.setLocalRepositoryManager(appCreatorLocalRepoManager); - return resolver; - } - - protected final RepositorySystem repoSystem; - protected final RepositorySystemSession repoSession; - protected final List remoteRepos; - protected AppCreatorLocalRepositoryManager localRepoManager; - - public AetherArtifactResolver() throws AppCreatorException { - this(MavenRepoInitializer.getRepositorySystem(), - MavenRepoInitializer.newSession(MavenRepoInitializer.getRepositorySystem()), - MavenRepoInitializer.getRemoteRepos()); - } - - public AetherArtifactResolver(RepositorySystem repoSystem, RepositorySystemSession repoSession, - List remoteRepos) { - super(); - this.repoSystem = repoSystem; - this.repoSession = repoSession; - this.remoteRepos = remoteRepos; - } - - public void setLocalRepositoryManager(AppCreatorLocalRepositoryManager localRepoManager) { - this.localRepoManager = localRepoManager; - } - - public void addRemoteRepositories(List repos) { - remoteRepos.addAll(repos); - } - - @Override - public void relink(AppArtifact artifact, Path path) throws AppCreatorException { - if (localRepoManager == null) { - throw new AppCreatorException("Failed to (re-)link " + artifact + " to " + path - + ": AppCreatorLocalRepositoryManager has not been initialized"); - } - localRepoManager.relink(artifact.getGroupId(), artifact.getArtifactId(), artifact.getClassifier(), artifact.getType(), - artifact.getVersion(), path); - setPath(artifact, path); - } - - @Override - protected void doResolve(AppArtifact artifact) throws AppCreatorException { - final ArtifactRequest artifactRequest = new ArtifactRequest(); - artifactRequest.setArtifact(toAetherArtifact(artifact)); - artifactRequest.setRepositories(remoteRepos); - ArtifactResult artifactResult; - try { - artifactResult = repoSystem.resolveArtifact(repoSession, artifactRequest); - } catch (ArtifactResolutionException e) { - throw new AppCreatorException("Failed to resolve artifact " + artifact, e); - } - setPath(artifact, artifactResult.getArtifact().getFile().toPath()); - } - - @Override - public List collectDependencies(AppArtifact coords) throws AppCreatorException { - final CollectRequest collectRequest = new CollectRequest(); - collectRequest.setRoot(new Dependency(toAetherArtifact(coords), "runtime")); - //collectRequest.setRootArtifact(toAetherArtifact(coords)); - collectRequest.setRepositories(remoteRepos); - - final DependencyRequest dependencyRequest = new DependencyRequest(collectRequest, null); - - final DependencyResult depResult; - try { - depResult = repoSystem.resolveDependencies(repoSession, dependencyRequest); - } catch (DependencyResolutionException e) { - throw new AppCreatorException("Failed to collect dependencies for " + coords, e); - } - - final List depNodes = depResult.getRoot().getChildren(); - if (depNodes.isEmpty()) { - return Collections.emptyList(); - } - - final List appDeps = new ArrayList<>(); - collect(depNodes, appDeps); - return appDeps; - } - - @Override - public List collectDependencies(AppArtifact root, List coords) throws AppCreatorException { - final CollectRequest collectRequest = new CollectRequest(); - collectRequest.setRoot(new Dependency(toAetherArtifact(root), "runtime")); - for (AppDependency dep : coords) { - collectRequest.addDependency(new Dependency(toAetherArtifact(dep.getArtifact()), dep.getScope())); - } - collectRequest.setRepositories(remoteRepos); - - final DependencyRequest dependencyRequest = new DependencyRequest(collectRequest, null); - - final DependencyResult depResult; - try { - depResult = repoSystem.resolveDependencies(repoSession, dependencyRequest); - } catch (DependencyResolutionException e) { - throw new AppCreatorException("Failed to collect dependencies for " + coords, e); - } - - final List depNodes = depResult.getRoot().getChildren(); - if (depNodes.isEmpty()) { - return Collections.emptyList(); - } - - final List appDeps = new ArrayList<>(); - collect(depNodes, appDeps); - return appDeps; - } - - /* - * @Override - * public List collectDependencies(AppArtifact coords) throws AppCreatorException { - * final CollectRequest collectRequest = new CollectRequest(); - * collectRequest.setRoot(new Dependency(toAetherArtifact(coords), "runtime")); - * //collectRequest.setRootArtifact(toAetherArtifact(coords)); - * collectRequest.setRepositories(remoteRepos); - * - * final CollectResult depResult; - * try { - * depResult = repoSystem.collectDependencies(repoSession, collectRequest); - * } catch (DependencyCollectionException e) { - * throw new AppCreatorException("Failed to collect dependencies for " + coords, e); - * } - * - * final List depNodes = depResult.getRoot().getChildren(); - * if(depNodes.isEmpty()) { - * return Collections.emptyList(); - * } - * - * final List appDeps = new ArrayList<>(); - * collect(depNodes, appDeps); - * return appDeps; - * } - */ - @Override - public List listLaterVersions(AppArtifact appArtifact, String upToVersion, boolean inclusive) - throws AppCreatorException { - final VersionRangeResult rangeResult = resolveVersionRangeResult(appArtifact, upToVersion, inclusive); - final List resolvedVersions = rangeResult.getVersions(); - final List versions = new ArrayList<>(resolvedVersions.size()); - for (Version v : resolvedVersions) { - versions.add(v.toString()); - } - return versions; - } - - @Override - public String getNextVersion(AppArtifact appArtifact, String upToVersion, boolean inclusive) throws AppCreatorException { - final VersionRangeResult rangeResult = resolveVersionRangeResult(appArtifact, upToVersion, inclusive); - final List versions = rangeResult.getVersions(); - if (versions.isEmpty()) { - return appArtifact.getVersion(); - } - Version next = versions.get(0); - for (int i = 1; i < versions.size(); ++i) { - final Version candidate = versions.get(i); - if (next.compareTo(candidate) > 0) { - next = candidate; - } - } - return next.toString(); - } - - @Override - public String getLatestVersion(AppArtifact appArtifact, String upToVersion, boolean inclusive) throws AppCreatorException { - final VersionRangeResult rangeResult = resolveVersionRangeResult(appArtifact, upToVersion, inclusive); - final List versions = rangeResult.getVersions(); - if (versions.isEmpty()) { - return appArtifact.getVersion(); - } - Version latest = versions.get(0); - for (int i = 1; i < versions.size(); ++i) { - final Version candidate = versions.get(i); - if (latest.compareTo(candidate) < 0) { - latest = candidate; - } - } - return latest.toString(); - } - - public List resolveArtifactRepos(AppArtifact appArtifact) throws AppCreatorException { - final ArtifactDescriptorRequest request = new ArtifactDescriptorRequest(); - request.setArtifact(new DefaultArtifact(appArtifact.getGroupId(), appArtifact.getArtifactId(), - appArtifact.getClassifier(), appArtifact.getType(), appArtifact.getVersion())); - //request.setRepositories(remoteRepos); - final ArtifactDescriptorResult result; - try { - result = repoSystem.readArtifactDescriptor(repoSession, request); - } catch (ArtifactDescriptorException e) { - throw new AppCreatorException("Failed to resolve descriptor for " + appArtifact, e); - } - return result.getRepositories(); - } - - private VersionRangeResult resolveVersionRangeResult(AppArtifact appArtifact, String upToVersion, boolean inclusive) - throws AppCreatorException { - final Artifact artifact = new DefaultArtifact(appArtifact.getGroupId(), - appArtifact.getArtifactId(), appArtifact.getType(), - '(' + appArtifact.getVersion() + ',' + (upToVersion == null ? ')' : upToVersion + (inclusive ? ']' : ')'))); - //System.out.println("AetherArtifactResolver.listLaterVersions for range " + artifact.getVersion()); - final VersionRangeResult rangeResult = getVersionRange(artifact); - return rangeResult; - } - - public void install(AppArtifact appArtifact, Path localPath) throws AppCreatorException { - final InstallRequest request = new InstallRequest(); - request.addArtifact( - new DefaultArtifact(appArtifact.getGroupId(), appArtifact.getArtifactId(), appArtifact.getClassifier(), - appArtifact.getType(), appArtifact.getVersion(), Collections.emptyMap(), localPath.toFile())); - try { - repoSystem.install(repoSession, request); - } catch (InstallationException ex) { - throw new AppCreatorException("Failed to install " + appArtifact, ex); - } - } - - private VersionRangeResult getVersionRange(Artifact artifact) throws AppCreatorException { - final VersionRangeRequest rangeRequest = new VersionRangeRequest(); - rangeRequest.setArtifact(artifact); - rangeRequest.setRepositories(remoteRepos); - VersionRangeResult rangeResult; - try { - rangeResult = repoSystem.resolveVersionRange(repoSession, rangeRequest); - } catch (VersionRangeResolutionException ex) { - throw new AppCreatorException("Failed to resolve version range for " + artifact, ex); - } - return rangeResult; - } - - private static void collect(List nodes, List appDeps) { - for (DependencyNode node : nodes) { - collect(node.getChildren(), appDeps); - appDeps.add(new AppDependency(toAppArtifact(node.getArtifact()), node.getDependency().getScope())); - } - } - - private static Artifact toAetherArtifact(AppArtifact artifact) { - return new DefaultArtifact(artifact.getGroupId(), artifact.getArtifactId(), artifact.getClassifier(), - artifact.getType(), artifact.getVersion()); - } - - private static AppArtifact toAppArtifact(Artifact artifact) { - final AppArtifact mvn = new AppArtifact(artifact.getGroupId(), artifact.getArtifactId(), artifact.getClassifier(), - artifact.getExtension(), artifact.getVersion()); - final File file = artifact.getFile(); - if (file != null) { - setPath(mvn, file.toPath()); - } - return mvn; - } -} diff --git a/core/creator/src/main/java/io/quarkus/creator/resolver/aether/AppCreatorDependencySelector.java b/core/creator/src/main/java/io/quarkus/creator/resolver/aether/AppCreatorDependencySelector.java deleted file mode 100644 index b4c5ef84ac202..0000000000000 --- a/core/creator/src/main/java/io/quarkus/creator/resolver/aether/AppCreatorDependencySelector.java +++ /dev/null @@ -1,48 +0,0 @@ -/** - * - */ -package io.quarkus.creator.resolver.aether; - -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; - -import org.eclipse.aether.collection.DependencyCollectionContext; -import org.eclipse.aether.collection.DependencySelector; -import org.eclipse.aether.graph.Dependency; - -/** - * - * @author Alexey Loubyansky - */ -public class AppCreatorDependencySelector implements DependencySelector { - - static final String COMPILE = "compile"; - static final String PROVIDED = "provided"; - static final String RUNTIME = "runtime"; - static final String SYSTEM = "system"; - static final String WILDCARD = "*"; - - static final Set APP_SCOPES = new HashSet<>(Arrays.asList(new String[] { COMPILE, SYSTEM, PROVIDED, RUNTIME })); - static final Set TRANSITIVE_SCOPES = new HashSet<>(Arrays.asList(new String[] { COMPILE, SYSTEM, RUNTIME })); - - protected final boolean debug; - - public AppCreatorDependencySelector(boolean debug) { - this.debug = debug; - } - - @Override - public boolean selectDependency(Dependency dependency) { - throw new UnsupportedOperationException(); - } - - @Override - public DependencySelector deriveChildSelector(DependencyCollectionContext context) { - final Dependency dependency = context.getDependency(); - if (dependency != null && (dependency.isOptional() || !APP_SCOPES.contains(dependency.getScope()))) { - return DisabledDependencySelector.INSTANCE; - } - return new DerivedDependencySelector(debug, dependency); - } -} diff --git a/core/creator/src/main/java/io/quarkus/creator/resolver/aether/DerivedDependencySelector.java b/core/creator/src/main/java/io/quarkus/creator/resolver/aether/DerivedDependencySelector.java deleted file mode 100644 index 8fc48217e9419..0000000000000 --- a/core/creator/src/main/java/io/quarkus/creator/resolver/aether/DerivedDependencySelector.java +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Copyright 2018 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed 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.creator.resolver.aether; - -import java.util.Collection; -import java.util.Set; - -import org.eclipse.aether.artifact.Artifact; -import org.eclipse.aether.collection.DependencyCollectionContext; -import org.eclipse.aether.collection.DependencySelector; -import org.eclipse.aether.graph.Dependency; -import org.eclipse.aether.graph.Exclusion; -import org.jboss.logging.Logger; - -/** - * - * @author Alexey Loubyansky - */ -public class DerivedDependencySelector implements DependencySelector { - - private static final Logger log = Logger.getLogger(DerivedDependencySelector.class); - - private static class DepExclusions { - - private final Collection exclusions; - private final DepExclusions parent; - - DepExclusions(DepExclusions parent, Collection exclusions) { - this.parent = parent; - this.exclusions = exclusions; - } - - boolean isExcluded(Dependency dep) { - for (Exclusion excl : exclusions) { - final String exclGroupId = excl.getGroupId(); - final String exclArtifactId = excl.getArtifactId(); - final String exclClassifier = excl.getClassifier(); - final String exclExt = excl.getExtension(); - final Artifact artifact = dep.getArtifact(); - if ((exclGroupId.equals(AppCreatorDependencySelector.WILDCARD) || exclGroupId.equals(artifact.getGroupId())) && - (exclArtifactId.equals(AppCreatorDependencySelector.WILDCARD) - || exclArtifactId.equals(artifact.getArtifactId())) - && - (exclClassifier.equals(AppCreatorDependencySelector.WILDCARD) - || exclClassifier.equals(artifact.getClassifier())) - && - (exclExt.equals(AppCreatorDependencySelector.WILDCARD) || exclExt.equals(artifact.getExtension()))) { - return true; - } - } - if (parent != null) { - DepExclusions parent = this.parent; - while (parent != null) { - if (parent.isExcluded(dep)) { - return true; - } - parent = parent.parent; - } - } - return false; - } - } - - private final int depth; - //private final Set selected; - //private final Set childrenProcessed; - private final Set acceptedScopes; - //private Dependency lastSelected; - private final DepExclusions depExclusions; - //private Map> managedExcl; - - public DerivedDependencySelector(boolean debug) { - this.depth = debug && log.isDebugEnabled() ? 0 : -1; - //this.selected = new HashSet<>(); - //this.childrenProcessed = new HashSet<>(); - this.acceptedScopes = AppCreatorDependencySelector.APP_SCOPES; - this.depExclusions = null; - } - - DerivedDependencySelector(boolean debug, Dependency dependency) { - this.depth = debug && log.isDebugEnabled() ? 0 : -1; - //this.selected = new HashSet<>(); - //this.childrenProcessed = new HashSet<>(); - if (dependency == null) { - depExclusions = null; - } else { - //childrenProcessed.add(dependency.getArtifact().toString()); - this.depExclusions = dependency.getExclusions().isEmpty() ? null - : new DepExclusions(null, dependency.getExclusions()); - } - this.acceptedScopes = AppCreatorDependencySelector.APP_SCOPES; - } - - private DerivedDependencySelector(DerivedDependencySelector parent, Set acceptedScopes, - DepExclusions depExclusions) { - this.depth = parent.depth < 0 ? parent.depth : parent.depth + 1; - //this.selected = parent.selected; - //this.childrenProcessed = parent.childrenProcessed; - //this.managedExcl = parent.managedExcl; - this.acceptedScopes = acceptedScopes; - this.depExclusions = depExclusions; - } - - @Override - public boolean selectDependency(Dependency dependency) { - //System.out.println("selectDependency " + dependency); - - if (dependency.isOptional() - || !acceptedScopes.contains(dependency.getScope()) - || depExclusions != null && depExclusions.isExcluded(dependency)) { - //System.out.println("Filtered out " + dependency); - return false; - } - - if (depth >= 0) { - final StringBuilder buf = new StringBuilder(); - for (int i = 0; i < depth; ++i) { - buf.append(" "); - } - String offset = buf.toString(); - log.debug(offset + dependency); - //System.out.println(offset + dependency); - if (!dependency.getExclusions().isEmpty()) { - log.debug(offset + " Excludes:"); - //System.out.println(offset + " Excludes:"); - buf.append(" - "); - offset = buf.toString(); - for (Exclusion excl : dependency.getExclusions()) { - log.debug(offset + excl.getGroupId() + ":" + excl.getArtifactId() + ":" + excl.getClassifier() + ":" - + excl.getExtension()); - //System.out.println(offset + excl.getGroupId() + ":" + excl.getArtifactId() + ":" + excl.getClassifier() + ":" + excl.getExtension()); - } - } - } - //lastSelected = dependency; - return true; - } - - @Override - public DependencySelector deriveChildSelector(DependencyCollectionContext context) { - //System.out.println("derivedChildSelector " + context.getDependency()); - /* - * doesn't seem to be relevant to look into the managed deps at the root - * if(managedExcl == null && depth <= 0) { - * final List managedDeps = context.getManagedDependencies(); - * if(managedDeps.isEmpty()) { - * managedExcl = Collections.emptyMap(); - * } else { - * System.out.println("Managed dependencies"); - * managedExcl = new HashMap<>(managedDeps.size()); - * for(Dependency dep : managedDeps) { - * final Collection exclusions = dep.getExclusions(); - * if(!exclusions.isEmpty()) { - * managedExcl.put(getKey(dep.getArtifact()), exclusions); - * if(depth == 0) { - * System.out.println(" " + dep); - * System.out.println(" Excludes:"); - * for (Exclusion excl : exclusions) { - * System.out.println(" - " + excl.getGroupId() + ":" + excl.getArtifactId() + ":" - * + excl.getClassifier() + ":" + excl.getExtension()); - * } - * } - * } - * } - * } - * } - */ - final Dependency dependency = context.getDependency(); - /* - * this condition actually leads to a trouble - * while the version might not match, the dependencies will still be of the version selected at the end - * skipping it here will result in the dependency graph transformer removing the dependencies of this artifact from the - * tree - * if(lastSelected != null && !lastSelected.getArtifact().getVersion().equals(dependency.getArtifact().getVersion())) { - * return DisabledDependencySelector.INSTANCE; - * } - */ - - /* - * if (dependency.isOptional() - * / *|| !acceptedScopes.contains(dependency.getScope())* / - * || !childrenProcessed.add(dependency.getArtifact().toString())) { - * //System.out.println("Filtering out children of " + dependency); - * return DisabledDependencySelector.INSTANCE; - * } - */ - - if (acceptedScopes.size() != AppCreatorDependencySelector.TRANSITIVE_SCOPES.size()) { - return new DerivedDependencySelector(this, AppCreatorDependencySelector.TRANSITIVE_SCOPES, - dependency.getExclusions().isEmpty() ? depExclusions - : new DepExclusions(depExclusions, dependency.getExclusions())); - } - return depth < 0 && dependency.getExclusions().isEmpty() ? this - : new DerivedDependencySelector(this, acceptedScopes, - dependency.getExclusions().isEmpty() ? depExclusions - : new DepExclusions(depExclusions, dependency.getExclusions())); - } - /* - * private static String getKey(Artifact artifact) { - * StringBuilder sb = new StringBuilder(128); - * sb.append(artifact.getGroupId()); - * sb.append(':'); - * sb.append(artifact.getArtifactId()); - * sb.append(':'); - * sb.append(artifact.getExtension()); - * if (!artifact.getClassifier().isEmpty()) { - * sb.append(':'); - * sb.append(artifact.getClassifier()); - * } - * return sb.toString(); - * } - */ -} \ No newline at end of file diff --git a/core/creator/src/main/java/io/quarkus/creator/resolver/aether/MavenRepoInitializer.java b/core/creator/src/main/java/io/quarkus/creator/resolver/aether/MavenRepoInitializer.java deleted file mode 100644 index bb0bdda61b958..0000000000000 --- a/core/creator/src/main/java/io/quarkus/creator/resolver/aether/MavenRepoInitializer.java +++ /dev/null @@ -1,211 +0,0 @@ -/* - * Copyright 2018 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed 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.creator.resolver.aether; - -import java.io.File; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import org.apache.maven.repository.internal.MavenRepositorySystemUtils; -import org.apache.maven.settings.Profile; -import org.apache.maven.settings.Repository; -import org.apache.maven.settings.Settings; -import org.apache.maven.settings.building.DefaultSettingsBuilderFactory; -import org.apache.maven.settings.building.DefaultSettingsBuildingRequest; -import org.apache.maven.settings.building.SettingsBuildingException; -import org.apache.maven.settings.building.SettingsBuildingRequest; -import org.apache.maven.settings.building.SettingsBuildingResult; -import org.apache.maven.settings.building.SettingsProblem; -import org.eclipse.aether.DefaultRepositorySystemSession; -import org.eclipse.aether.RepositorySystem; -import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory; -import org.eclipse.aether.impl.DefaultServiceLocator; -import org.eclipse.aether.repository.Authentication; -import org.eclipse.aether.repository.LocalRepository; -import org.eclipse.aether.repository.Proxy; -import org.eclipse.aether.repository.RemoteRepository; -import org.eclipse.aether.repository.RepositoryPolicy; -import org.eclipse.aether.spi.connector.RepositoryConnectorFactory; -import org.eclipse.aether.spi.connector.transport.TransporterFactory; -import org.eclipse.aether.transport.file.FileTransporterFactory; -import org.eclipse.aether.transport.http.HttpTransporterFactory; -import org.eclipse.aether.util.repository.AuthenticationBuilder; -import org.eclipse.aether.util.repository.DefaultProxySelector; -import org.jboss.logging.Logger; - -import io.quarkus.creator.AppCreatorException; -import io.quarkus.creator.util.PropertyUtils; - -/** - * - * @author Alexey Loubyansky - */ -public class MavenRepoInitializer { - - private static final String DOT_M2 = ".m2"; - private static final String MAVEN_HOME = "maven.home"; - private static final String M2_HOME = "M2_HOME"; - private static final String SETTINGS_XML = "settings.xml"; - - public static final String userHome = PropertyUtils.getUserHome(); - public static final File userMavenConfigurationHome = new File(userHome, DOT_M2); - public static final String envM2Home = System.getenv(M2_HOME); - public static final File DEFAULT_USER_SETTINGS_FILE = new File(userMavenConfigurationHome, SETTINGS_XML); - public static final File DEFAULT_GLOBAL_SETTINGS_FILE = new File( - PropertyUtils.getProperty(MAVEN_HOME, envM2Home != null ? envM2Home : ""), "conf/settings.xml"); - - private static RepositorySystem repoSystem; - private static List remoteRepos; - private static Settings settings; - - private static final Logger log = Logger.getLogger(MavenRepoInitializer.class); - - public static RepositorySystem getRepositorySystem() { - if (repoSystem != null) { - return repoSystem; - } - - final DefaultServiceLocator locator = MavenRepositorySystemUtils.newServiceLocator(); - locator.addService(RepositoryConnectorFactory.class, BasicRepositoryConnectorFactory.class); - locator.addService(TransporterFactory.class, FileTransporterFactory.class); - locator.addService(TransporterFactory.class, HttpTransporterFactory.class); - - locator.setErrorHandler(new DefaultServiceLocator.ErrorHandler() { - @Override - public void serviceCreationFailed(Class type, Class impl, Throwable exception) { - System.err.println("Service creation failed"); - exception.printStackTrace(); - } - }); - - return repoSystem = locator.getService(RepositorySystem.class); - } - - public static DefaultRepositorySystemSession newSession(RepositorySystem system) throws AppCreatorException { - return newSession(system, getSettings()); - } - - public static DefaultRepositorySystemSession newSession(RepositorySystem system, Settings settings) { - final DefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession(); - - final org.apache.maven.settings.Proxy proxy = settings.getActiveProxy(); - if (proxy != null) { - Authentication auth = null; - if (proxy.getUsername() != null) { - auth = new AuthenticationBuilder() - .addUsername(proxy.getUsername()) - .addPassword(proxy.getPassword()) - .build(); - } - final Proxy aetherProxy = new Proxy(proxy.getProtocol(), proxy.getHost(), proxy.getPort(), auth); - DefaultProxySelector proxySelector = new DefaultProxySelector(); - proxySelector.add(aetherProxy, proxy.getNonProxyHosts()); - session.setProxySelector(proxySelector); - } - - final String localRepoPath = getLocalRepo(settings); - session.setLocalRepositoryManager(system.newLocalRepositoryManager(session, new LocalRepository(localRepoPath))); - - session.setOffline(settings.isOffline()); - - // uncomment to generate dirty trees - //session.setDependencyGraphTransformer( null ); - - return session; - } - - public static List getRemoteRepos() throws AppCreatorException { - if (remoteRepos != null) { - return remoteRepos; - } - remoteRepos = Collections.unmodifiableList(getRemoteRepos(getSettings())); - return remoteRepos; - } - - public static List getRemoteRepos(Settings settings) throws AppCreatorException { - - final Map profilesMap = settings.getProfilesAsMap(); - final List remotes = new ArrayList<>(); - - for (String profileName : settings.getActiveProfiles()) { - final Profile profile = profilesMap.get(profileName); - final List repositories = profile.getRepositories(); - for (Repository repo : repositories) { - final RemoteRepository.Builder repoBuilder = new RemoteRepository.Builder(repo.getId(), repo.getLayout(), - repo.getUrl()); - org.apache.maven.settings.RepositoryPolicy policy = repo.getReleases(); - if (policy != null) { - repoBuilder.setReleasePolicy( - new RepositoryPolicy(policy.isEnabled(), policy.getUpdatePolicy(), policy.getChecksumPolicy())); - } - policy = repo.getSnapshots(); - if (policy != null) { - repoBuilder.setSnapshotPolicy( - new RepositoryPolicy(policy.isEnabled(), policy.getUpdatePolicy(), policy.getChecksumPolicy())); - } - remotes.add(repoBuilder.build()); - } - } - return remotes; - } - - public static Settings getSettings() throws AppCreatorException { - if (settings != null) { - return settings; - } - final SettingsBuildingRequest settingsBuildingRequest = new DefaultSettingsBuildingRequest(); - settingsBuildingRequest.setSystemProperties(System.getProperties()); - settingsBuildingRequest.setUserSettingsFile(DEFAULT_USER_SETTINGS_FILE); - settingsBuildingRequest.setGlobalSettingsFile(DEFAULT_GLOBAL_SETTINGS_FILE); - - final Settings effectiveSettings; - try { - final SettingsBuildingResult result = new DefaultSettingsBuilderFactory().newInstance() - .build(settingsBuildingRequest); - final List problems = result.getProblems(); - if (!problems.isEmpty()) { - for (SettingsProblem problem : problems) { - switch (problem.getSeverity()) { - case ERROR: - case FATAL: - throw new AppCreatorException("Settings problem encountered at " + problem.getLocation(), - problem.getException()); - default: - log.warn("Settings problem encountered at " + problem.getLocation(), problem.getException()); - } - } - } - effectiveSettings = result.getEffectiveSettings(); - } catch (SettingsBuildingException e) { - throw new AppCreatorException("Failed to initialize Maven repository settings", e); - } - - settings = effectiveSettings; - return effectiveSettings; - } - - public static String getLocalRepo(Settings settings) { - final String localRepo = settings.getLocalRepository(); - return localRepo == null ? getDefaultLocalRepo() : localRepo; - } - - private static String getDefaultLocalRepo() { - return new File(userMavenConfigurationHome, "repository").getAbsolutePath(); - } -} diff --git a/core/creator/src/main/java/io/quarkus/creator/resolver/maven/ResolvedMavenArtifactDeps.java b/core/creator/src/main/java/io/quarkus/creator/resolver/maven/ResolvedMavenArtifactDeps.java deleted file mode 100644 index 88747ffb3da27..0000000000000 --- a/core/creator/src/main/java/io/quarkus/creator/resolver/maven/ResolvedMavenArtifactDeps.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright 2018 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed 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.creator.resolver.maven; - -import java.io.File; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -import org.apache.maven.artifact.Artifact; - -import io.quarkus.creator.AppArtifact; -import io.quarkus.creator.AppArtifactResolverBase; -import io.quarkus.creator.AppCreatorException; -import io.quarkus.creator.AppDependency; - -/** - * - * @author Alexey Loubyansky - */ -public class ResolvedMavenArtifactDeps extends AppArtifactResolverBase { - - private final String groupId; - private final String artifactId; - private final String classifier; - private final String type; - private final String version; - private final List deps; - - public ResolvedMavenArtifactDeps(String groupId, String artifactId, String version, Collection artifacts) { - this(groupId, artifactId, "", version, artifacts); - } - - public ResolvedMavenArtifactDeps(String groupId, String artifactId, String classifier, String version, - Collection artifacts) { - this(groupId, artifactId, "", "jar", version, artifacts); - } - - public ResolvedMavenArtifactDeps(String groupId, String artifactId, String classifier, String type, String version, - Collection artifacts) { - this.groupId = groupId; - this.artifactId = artifactId; - this.classifier = classifier; - this.type = type; - this.version = version; - final List tmp = new ArrayList<>(artifacts.size()); - for (Artifact artifact : artifacts) { - tmp.add(new AppDependency(toMvnArtifact(artifact), artifact.getScope())); - } - deps = Collections.unmodifiableList(tmp); - } - - @Override - public void relink(AppArtifact artifact, Path path) throws AppCreatorException { - throw new UnsupportedOperationException(); - } - - @Override - protected void doResolve(AppArtifact coords) throws AppCreatorException { - throw new UnsupportedOperationException(); - } - - @Override - public List collectDependencies(AppArtifact coords) throws AppCreatorException { - if (!coords.getGroupId().equals(groupId) || - !coords.getArtifactId().equals(artifactId) || - !coords.getClassifier().equals(classifier) || - !coords.getType().equals(type) || - !coords.getVersion().equals(version)) { - throw new AppCreatorException("The resolve can only resolve dependencies for " + groupId + ':' + artifactId + ':' - + classifier + ':' + type + ':' + version + ": " + coords); - } - return deps; - } - - @Override - public List collectDependencies(AppArtifact root, List deps) throws AppCreatorException { - throw new UnsupportedOperationException(); - } - - private static AppArtifact toMvnArtifact(Artifact artifact) { - final AppArtifact mvn = new AppArtifact(artifact.getGroupId(), artifact.getArtifactId(), artifact.getClassifier(), - artifact.getType(), artifact.getVersion()); - final File file = artifact.getFile(); - if (file != null) { - setPath(mvn, file.toPath()); - } - return mvn; - } - - @Override - public List listLaterVersions(AppArtifact artifact, String upToVersion, boolean inclusive) - throws AppCreatorException { - throw new UnsupportedOperationException(); - } - - @Override - public String getNextVersion(AppArtifact artifact, String upToVersion, boolean inclusive) throws AppCreatorException { - throw new UnsupportedOperationException(); - } - - @Override - public String getLatestVersion(AppArtifact artifact, String upToVersion, boolean inclusive) throws AppCreatorException { - throw new UnsupportedOperationException(); - } -} diff --git a/core/creator/src/main/java/io/quarkus/creator/util/PropertyUtils.java b/core/creator/src/main/java/io/quarkus/creator/util/PropertyUtils.java deleted file mode 100644 index d146d274c1c2c..0000000000000 --- a/core/creator/src/main/java/io/quarkus/creator/util/PropertyUtils.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2018 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed 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.creator.util; - -import java.security.AccessController; -import java.security.PrivilegedAction; -import java.util.Locale; - -/** - * - * @author Alexey Loubyansky - */ -public class PropertyUtils { - - private static final String OS_NAME = "os.name"; - private static final String USER_HOME = "user.home"; - private static final String WINDOWS = "windows"; - - private PropertyUtils() { - } - - public static boolean isWindows() { - return getProperty(OS_NAME).toLowerCase(Locale.ENGLISH).indexOf(WINDOWS) >= 0; - } - - public static String getUserHome() { - return getProperty(USER_HOME); - } - - public static String getProperty(final String name, String defValue) { - final String value = getProperty(name); - return value == null ? defValue : value; - } - - public static String getProperty(final String name) { - assert name != null : "name is null"; - final SecurityManager sm = System.getSecurityManager(); - if (sm != null) { - return AccessController.doPrivileged(new PrivilegedAction() { - @Override - public String run() { - return System.getProperty(name); - } - }); - } else { - return System.getProperty(name); - } - } -} diff --git a/core/creator/src/test/java/io/quarkus/creator/config/test/PersonAddressTestBase.java b/core/creator/src/test/java/io/quarkus/creator/config/test/PersonAddressTestBase.java index 7d38e20fd440e..c709cbd54e365 100644 --- a/core/creator/src/test/java/io/quarkus/creator/config/test/PersonAddressTestBase.java +++ b/core/creator/src/test/java/io/quarkus/creator/config/test/PersonAddressTestBase.java @@ -28,10 +28,10 @@ import org.junit.Test; +import io.quarkus.bootstrap.util.IoUtils; import io.quarkus.creator.config.reader.PropertiesConfigReader; import io.quarkus.creator.config.reader.PropertiesHandler; import io.quarkus.creator.config.reader.PropertyLine; -import io.quarkus.creator.util.IoUtils; /** * diff --git a/core/creator/src/test/java/io/quarkus/creator/phase/curate/test/CheckForLatestMajorUpdatesTest.java b/core/creator/src/test/java/io/quarkus/creator/phase/curate/test/CheckForLatestMajorUpdatesTest.java new file mode 100644 index 0000000000000..c35d6e9a9504a --- /dev/null +++ b/core/creator/src/test/java/io/quarkus/creator/phase/curate/test/CheckForLatestMajorUpdatesTest.java @@ -0,0 +1,69 @@ +package io.quarkus.creator.phase.curate.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.Properties; + +import io.quarkus.bootstrap.model.AppDependency; +import io.quarkus.bootstrap.model.AppModel; +import io.quarkus.bootstrap.resolver.TsArtifact; +import io.quarkus.bootstrap.resolver.TsQuarkusExt; +import io.quarkus.creator.AppCreator; +import io.quarkus.creator.phase.curate.CurateOutcome; +import io.quarkus.creator.phase.curate.CuratePhase; +import io.quarkus.creator.phase.curate.VersionUpdate; +import io.quarkus.creator.phase.curate.VersionUpdateNumber; +import io.quarkus.creator.phase.runnerjar.test.CreatorOutcomeTestBase; + +public class CheckForLatestMajorUpdatesTest extends CreatorOutcomeTestBase { + + @Override + protected void initProps(Properties props) { + props.setProperty(CuratePhase.completePropertyName(CuratePhase.CONFIG_PROP_VERSION_UPDATE), + VersionUpdate.LATEST.getName()); // NONE, next, latest + props.setProperty(CuratePhase.completePropertyName(CuratePhase.CONFIG_PROP_VERSION_UPDATE_NUMBER), + VersionUpdateNumber.MAJOR.getName()); // major, minor, MICRO + } + + @Override + protected TsArtifact modelApp() throws Exception { + new TsQuarkusExt("ext1", "1.0.1").install(repo); + new TsQuarkusExt("ext1", "1.1.0").install(repo); + new TsQuarkusExt("ext1", "2.0.0").install(repo); + new TsQuarkusExt("ext1", "3.0.0").install(repo); + new TsQuarkusExt("ext1", "3.0.1").install(repo); + new TsQuarkusExt("ext1", "3.1.0").install(repo); + new TsQuarkusExt("ext1", "3.1.1").install(repo); + + return TsArtifact.jar("app") + .addDependency(new TsQuarkusExt("ext1", "1.0.0")) + .addDependency(TsArtifact.jar("random")) + .addDependency(new TsQuarkusExt("ext2", "1.0.0")); + } + + @Override + protected void testCreator(AppCreator creator) throws Exception { + final CurateOutcome outcome = creator.resolveOutcome(CurateOutcome.class); + + assertTrue(outcome.hasUpdatedDeps()); + + assertEquals(Arrays.asList(new AppDependency[] { + new AppDependency(TsArtifact.jar("ext1", "3.1.1").toAppArtifact(), "compile") + }), outcome.getUpdatedDeps()); + + final AppModel effectiveModel = outcome.getEffectiveModel(); + assertEquals(Arrays.asList(new AppDependency[] { + new AppDependency(TsArtifact.jar("ext1", "3.1.1").toAppArtifact(), "compile"), + new AppDependency(TsArtifact.jar("random").toAppArtifact(), "compile"), + new AppDependency(TsArtifact.jar("ext2", "1.0.0").toAppArtifact(), "compile") + }), effectiveModel.getUserDependencies()); + + assertEquals(Arrays.asList(new AppDependency[] { + new AppDependency(TsArtifact.jar("ext1-deployment", "3.1.1").toAppArtifact(), "compile"), + new AppDependency(TsArtifact.jar("ext2-deployment", "1.0.0").toAppArtifact(), "compile") + }), effectiveModel.getDeploymentDependencies()); + + } +} diff --git a/core/creator/src/test/java/io/quarkus/creator/phase/curate/test/CheckForLatestMicroUpdatesTest.java b/core/creator/src/test/java/io/quarkus/creator/phase/curate/test/CheckForLatestMicroUpdatesTest.java new file mode 100644 index 0000000000000..079189ce4fef0 --- /dev/null +++ b/core/creator/src/test/java/io/quarkus/creator/phase/curate/test/CheckForLatestMicroUpdatesTest.java @@ -0,0 +1,66 @@ +package io.quarkus.creator.phase.curate.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.Properties; + +import io.quarkus.bootstrap.model.AppDependency; +import io.quarkus.bootstrap.model.AppModel; +import io.quarkus.bootstrap.resolver.TsArtifact; +import io.quarkus.bootstrap.resolver.TsQuarkusExt; +import io.quarkus.creator.AppCreator; +import io.quarkus.creator.phase.curate.CurateOutcome; +import io.quarkus.creator.phase.curate.CuratePhase; +import io.quarkus.creator.phase.curate.VersionUpdate; +import io.quarkus.creator.phase.curate.VersionUpdateNumber; +import io.quarkus.creator.phase.runnerjar.test.CreatorOutcomeTestBase; + +public class CheckForLatestMicroUpdatesTest extends CreatorOutcomeTestBase { + + @Override + protected void initProps(Properties props) { + props.setProperty(CuratePhase.completePropertyName(CuratePhase.CONFIG_PROP_VERSION_UPDATE), + VersionUpdate.LATEST.getName()); // NONE, next, latest + props.setProperty(CuratePhase.completePropertyName(CuratePhase.CONFIG_PROP_VERSION_UPDATE_NUMBER), + VersionUpdateNumber.MICRO.getName()); // major, minor, MICRO + } + + @Override + protected TsArtifact modelApp() throws Exception { + new TsQuarkusExt("ext1", "1.0.1").install(repo); + new TsQuarkusExt("ext1", "1.0.2").install(repo); + new TsQuarkusExt("ext1", "1.1.0").install(repo); + new TsQuarkusExt("ext1", "2.0.0").install(repo); + + return TsArtifact.jar("app") + .addDependency(new TsQuarkusExt("ext1", "1.0.0")) + .addDependency(TsArtifact.jar("random")) + .addDependency(new TsQuarkusExt("ext2", "1.0.0")); + } + + @Override + protected void testCreator(AppCreator creator) throws Exception { + final CurateOutcome outcome = creator.resolveOutcome(CurateOutcome.class); + + assertTrue(outcome.hasUpdatedDeps()); + + assertEquals(Arrays.asList(new AppDependency[] { + new AppDependency(TsArtifact.jar("ext1", "1.0.2").toAppArtifact(), "compile") + }), outcome.getUpdatedDeps()); + + final AppModel effectiveModel = outcome.getEffectiveModel(); + assertEquals(Arrays.asList(new AppDependency[] { + new AppDependency(TsArtifact.jar("ext1", "1.0.2").toAppArtifact(), "compile"), + new AppDependency(TsArtifact.jar("random").toAppArtifact(), "compile"), + new AppDependency(TsArtifact.jar("ext2", "1.0.0").toAppArtifact(), "compile") + }), effectiveModel.getUserDependencies()); + + assertEquals(Arrays.asList(new AppDependency[] { + new AppDependency(TsArtifact.jar("ext1-deployment", "1.0.2").toAppArtifact(), "compile"), + new AppDependency(TsArtifact.jar("ext2-deployment", "1.0.0").toAppArtifact(), "compile") + }), effectiveModel.getDeploymentDependencies()); + + } +} diff --git a/core/creator/src/test/java/io/quarkus/creator/phase/curate/test/CheckForLatestMinorUpdatesTest.java b/core/creator/src/test/java/io/quarkus/creator/phase/curate/test/CheckForLatestMinorUpdatesTest.java new file mode 100644 index 0000000000000..153c79892a3d8 --- /dev/null +++ b/core/creator/src/test/java/io/quarkus/creator/phase/curate/test/CheckForLatestMinorUpdatesTest.java @@ -0,0 +1,67 @@ +package io.quarkus.creator.phase.curate.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.Properties; + +import io.quarkus.bootstrap.model.AppDependency; +import io.quarkus.bootstrap.model.AppModel; +import io.quarkus.bootstrap.resolver.TsArtifact; +import io.quarkus.bootstrap.resolver.TsQuarkusExt; +import io.quarkus.creator.AppCreator; +import io.quarkus.creator.phase.curate.CurateOutcome; +import io.quarkus.creator.phase.curate.CuratePhase; +import io.quarkus.creator.phase.curate.VersionUpdate; +import io.quarkus.creator.phase.curate.VersionUpdateNumber; +import io.quarkus.creator.phase.runnerjar.test.CreatorOutcomeTestBase; + +public class CheckForLatestMinorUpdatesTest extends CreatorOutcomeTestBase { + + @Override + protected void initProps(Properties props) { + props.setProperty(CuratePhase.completePropertyName(CuratePhase.CONFIG_PROP_VERSION_UPDATE), + VersionUpdate.LATEST.getName()); // NONE, next, latest + props.setProperty(CuratePhase.completePropertyName(CuratePhase.CONFIG_PROP_VERSION_UPDATE_NUMBER), + VersionUpdateNumber.MINOR.getName()); // major, minor, MICRO + } + + @Override + protected TsArtifact modelApp() throws Exception { + new TsQuarkusExt("ext1", "1.0.1").install(repo); + new TsQuarkusExt("ext1", "1.1.0").install(repo); + new TsQuarkusExt("ext1", "1.2.0").install(repo); + new TsQuarkusExt("ext1", "1.2.1").install(repo); + new TsQuarkusExt("ext1", "2.0.0").install(repo); + + return TsArtifact.jar("app") + .addDependency(new TsQuarkusExt("ext1", "1.0.0")) + .addDependency(TsArtifact.jar("random")) + .addDependency(new TsQuarkusExt("ext2", "1.0.0")); + } + + @Override + protected void testCreator(AppCreator creator) throws Exception { + final CurateOutcome outcome = creator.resolveOutcome(CurateOutcome.class); + + assertTrue(outcome.hasUpdatedDeps()); + + assertEquals(Arrays.asList(new AppDependency[] { + new AppDependency(TsArtifact.jar("ext1", "1.2.1").toAppArtifact(), "compile") + }), outcome.getUpdatedDeps()); + + final AppModel effectiveModel = outcome.getEffectiveModel(); + assertEquals(Arrays.asList(new AppDependency[] { + new AppDependency(TsArtifact.jar("ext1", "1.2.1").toAppArtifact(), "compile"), + new AppDependency(TsArtifact.jar("random").toAppArtifact(), "compile"), + new AppDependency(TsArtifact.jar("ext2", "1.0.0").toAppArtifact(), "compile") + }), effectiveModel.getUserDependencies()); + + assertEquals(Arrays.asList(new AppDependency[] { + new AppDependency(TsArtifact.jar("ext1-deployment", "1.2.1").toAppArtifact(), "compile"), + new AppDependency(TsArtifact.jar("ext2-deployment", "1.0.0").toAppArtifact(), "compile") + }), effectiveModel.getDeploymentDependencies()); + + } +} diff --git a/core/creator/src/test/java/io/quarkus/creator/phase/curate/test/CheckForNextMajorUpdatesTest.java b/core/creator/src/test/java/io/quarkus/creator/phase/curate/test/CheckForNextMajorUpdatesTest.java new file mode 100644 index 0000000000000..aed6e2cc7fc65 --- /dev/null +++ b/core/creator/src/test/java/io/quarkus/creator/phase/curate/test/CheckForNextMajorUpdatesTest.java @@ -0,0 +1,68 @@ +package io.quarkus.creator.phase.curate.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.Properties; + +import io.quarkus.bootstrap.model.AppDependency; +import io.quarkus.bootstrap.model.AppModel; +import io.quarkus.bootstrap.resolver.TsArtifact; +import io.quarkus.bootstrap.resolver.TsQuarkusExt; +import io.quarkus.creator.AppCreator; +import io.quarkus.creator.phase.curate.CurateOutcome; +import io.quarkus.creator.phase.curate.CuratePhase; +import io.quarkus.creator.phase.curate.VersionUpdate; +import io.quarkus.creator.phase.curate.VersionUpdateNumber; +import io.quarkus.creator.phase.runnerjar.test.CreatorOutcomeTestBase; + +public class CheckForNextMajorUpdatesTest extends CreatorOutcomeTestBase { + + @Override + protected void initProps(Properties props) { + props.setProperty(CuratePhase.completePropertyName(CuratePhase.CONFIG_PROP_VERSION_UPDATE), + VersionUpdate.NEXT.getName()); // NONE, next, latest + props.setProperty(CuratePhase.completePropertyName(CuratePhase.CONFIG_PROP_VERSION_UPDATE_NUMBER), + VersionUpdateNumber.MAJOR.getName()); // major, minor, MICRO + } + + @Override + protected TsArtifact modelApp() throws Exception { + new TsQuarkusExt("ext1", "1.0.1").install(repo); + new TsQuarkusExt("ext1", "1.1.0").install(repo); + new TsQuarkusExt("ext1", "2.0.0").install(repo); + new TsQuarkusExt("ext1", "2.0.1").install(repo); + new TsQuarkusExt("ext1", "2.1.0").install(repo); + new TsQuarkusExt("ext1", "3.0.0").install(repo); + + return TsArtifact.jar("app") + .addDependency(new TsQuarkusExt("ext1", "1.0.0")) + .addDependency(TsArtifact.jar("random")) + .addDependency(new TsQuarkusExt("ext2", "1.0.0")); + } + + @Override + protected void testCreator(AppCreator creator) throws Exception { + final CurateOutcome outcome = creator.resolveOutcome(CurateOutcome.class); + + assertTrue(outcome.hasUpdatedDeps()); + + assertEquals(Arrays.asList(new AppDependency[] { + new AppDependency(TsArtifact.jar("ext1", "2.0.0").toAppArtifact(), "compile") + }), outcome.getUpdatedDeps()); + + final AppModel effectiveModel = outcome.getEffectiveModel(); + assertEquals(Arrays.asList(new AppDependency[] { + new AppDependency(TsArtifact.jar("ext1", "2.0.0").toAppArtifact(), "compile"), + new AppDependency(TsArtifact.jar("random").toAppArtifact(), "compile"), + new AppDependency(TsArtifact.jar("ext2", "1.0.0").toAppArtifact(), "compile") + }), effectiveModel.getUserDependencies()); + + assertEquals(Arrays.asList(new AppDependency[] { + new AppDependency(TsArtifact.jar("ext1-deployment", "2.0.0").toAppArtifact(), "compile"), + new AppDependency(TsArtifact.jar("ext2-deployment", "1.0.0").toAppArtifact(), "compile") + }), effectiveModel.getDeploymentDependencies()); + + } +} diff --git a/core/creator/src/test/java/io/quarkus/creator/phase/curate/test/CheckForNextMicroUpdatesTest.java b/core/creator/src/test/java/io/quarkus/creator/phase/curate/test/CheckForNextMicroUpdatesTest.java new file mode 100644 index 0000000000000..66784eaddf9ff --- /dev/null +++ b/core/creator/src/test/java/io/quarkus/creator/phase/curate/test/CheckForNextMicroUpdatesTest.java @@ -0,0 +1,66 @@ +package io.quarkus.creator.phase.curate.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.Properties; + +import io.quarkus.bootstrap.model.AppDependency; +import io.quarkus.bootstrap.model.AppModel; +import io.quarkus.bootstrap.resolver.TsArtifact; +import io.quarkus.bootstrap.resolver.TsQuarkusExt; +import io.quarkus.creator.AppCreator; +import io.quarkus.creator.phase.curate.CurateOutcome; +import io.quarkus.creator.phase.curate.CuratePhase; +import io.quarkus.creator.phase.curate.VersionUpdate; +import io.quarkus.creator.phase.curate.VersionUpdateNumber; +import io.quarkus.creator.phase.runnerjar.test.CreatorOutcomeTestBase; + +public class CheckForNextMicroUpdatesTest extends CreatorOutcomeTestBase { + + @Override + protected void initProps(Properties props) { + props.setProperty(CuratePhase.completePropertyName(CuratePhase.CONFIG_PROP_VERSION_UPDATE), + VersionUpdate.NEXT.getName()); // NONE, next, latest + props.setProperty(CuratePhase.completePropertyName(CuratePhase.CONFIG_PROP_VERSION_UPDATE_NUMBER), + VersionUpdateNumber.MICRO.getName()); // major, minor, MICRO + } + + @Override + protected TsArtifact modelApp() throws Exception { + new TsQuarkusExt("ext1", "1.0.1").install(repo); + new TsQuarkusExt("ext1", "1.0.2").install(repo); + new TsQuarkusExt("ext1", "1.1.0").install(repo); + new TsQuarkusExt("ext1", "2.0.0").install(repo); + + return TsArtifact.jar("app") + .addDependency(new TsQuarkusExt("ext1", "1.0.0")) + .addDependency(TsArtifact.jar("random")) + .addDependency(new TsQuarkusExt("ext2", "1.0.0")); + } + + @Override + protected void testCreator(AppCreator creator) throws Exception { + final CurateOutcome outcome = creator.resolveOutcome(CurateOutcome.class); + + assertTrue(outcome.hasUpdatedDeps()); + + assertEquals(Arrays.asList(new AppDependency[] { + new AppDependency(TsArtifact.jar("ext1", "1.0.1").toAppArtifact(), "compile") + }), outcome.getUpdatedDeps()); + + final AppModel effectiveModel = outcome.getEffectiveModel(); + assertEquals(Arrays.asList(new AppDependency[] { + new AppDependency(TsArtifact.jar("ext1", "1.0.1").toAppArtifact(), "compile"), + new AppDependency(TsArtifact.jar("random").toAppArtifact(), "compile"), + new AppDependency(TsArtifact.jar("ext2", "1.0.0").toAppArtifact(), "compile") + }), effectiveModel.getUserDependencies()); + + assertEquals(Arrays.asList(new AppDependency[] { + new AppDependency(TsArtifact.jar("ext1-deployment", "1.0.1").toAppArtifact(), "compile"), + new AppDependency(TsArtifact.jar("ext2-deployment", "1.0.0").toAppArtifact(), "compile") + }), effectiveModel.getDeploymentDependencies()); + + } +} diff --git a/core/creator/src/test/java/io/quarkus/creator/phase/curate/test/CheckForNextMinorUpdatesTest.java b/core/creator/src/test/java/io/quarkus/creator/phase/curate/test/CheckForNextMinorUpdatesTest.java new file mode 100644 index 0000000000000..0a0b5745e31c2 --- /dev/null +++ b/core/creator/src/test/java/io/quarkus/creator/phase/curate/test/CheckForNextMinorUpdatesTest.java @@ -0,0 +1,67 @@ +package io.quarkus.creator.phase.curate.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.Properties; + +import io.quarkus.bootstrap.model.AppDependency; +import io.quarkus.bootstrap.model.AppModel; +import io.quarkus.bootstrap.resolver.TsArtifact; +import io.quarkus.bootstrap.resolver.TsQuarkusExt; +import io.quarkus.creator.AppCreator; +import io.quarkus.creator.phase.curate.CurateOutcome; +import io.quarkus.creator.phase.curate.CuratePhase; +import io.quarkus.creator.phase.curate.VersionUpdate; +import io.quarkus.creator.phase.curate.VersionUpdateNumber; +import io.quarkus.creator.phase.runnerjar.test.CreatorOutcomeTestBase; + +public class CheckForNextMinorUpdatesTest extends CreatorOutcomeTestBase { + + @Override + protected void initProps(Properties props) { + props.setProperty(CuratePhase.completePropertyName(CuratePhase.CONFIG_PROP_VERSION_UPDATE), + VersionUpdate.NEXT.getName()); // NONE, next, latest + props.setProperty(CuratePhase.completePropertyName(CuratePhase.CONFIG_PROP_VERSION_UPDATE_NUMBER), + VersionUpdateNumber.MINOR.getName()); // major, minor, MICRO + } + + @Override + protected TsArtifact modelApp() throws Exception { + new TsQuarkusExt("ext1", "1.0.1").install(repo); + new TsQuarkusExt("ext1", "1.1.0").install(repo); + new TsQuarkusExt("ext1", "1.1.1").install(repo); + new TsQuarkusExt("ext1", "1.2.0").install(repo); + new TsQuarkusExt("ext1", "2.0.0").install(repo); + + return TsArtifact.jar("app") + .addDependency(new TsQuarkusExt("ext1", "1.0.0")) + .addDependency(TsArtifact.jar("random")) + .addDependency(new TsQuarkusExt("ext2", "1.0.0")); + } + + @Override + protected void testCreator(AppCreator creator) throws Exception { + final CurateOutcome outcome = creator.resolveOutcome(CurateOutcome.class); + + assertTrue(outcome.hasUpdatedDeps()); + + assertEquals(Arrays.asList(new AppDependency[] { + new AppDependency(TsArtifact.jar("ext1", "1.1.0").toAppArtifact(), "compile") + }), outcome.getUpdatedDeps()); + + final AppModel effectiveModel = outcome.getEffectiveModel(); + assertEquals(Arrays.asList(new AppDependency[] { + new AppDependency(TsArtifact.jar("ext1", "1.1.0").toAppArtifact(), "compile"), + new AppDependency(TsArtifact.jar("random").toAppArtifact(), "compile"), + new AppDependency(TsArtifact.jar("ext2", "1.0.0").toAppArtifact(), "compile") + }), effectiveModel.getUserDependencies()); + + assertEquals(Arrays.asList(new AppDependency[] { + new AppDependency(TsArtifact.jar("ext1-deployment", "1.1.0").toAppArtifact(), "compile"), + new AppDependency(TsArtifact.jar("ext2-deployment", "1.0.0").toAppArtifact(), "compile") + }), effectiveModel.getDeploymentDependencies()); + + } +} diff --git a/core/creator/src/test/java/io/quarkus/creator/phase/curate/test/CheckUpdatesDisableTest.java b/core/creator/src/test/java/io/quarkus/creator/phase/curate/test/CheckUpdatesDisableTest.java new file mode 100644 index 0000000000000..23f9a80cb0647 --- /dev/null +++ b/core/creator/src/test/java/io/quarkus/creator/phase/curate/test/CheckUpdatesDisableTest.java @@ -0,0 +1,70 @@ +package io.quarkus.creator.phase.curate.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Properties; + +import io.quarkus.bootstrap.model.AppDependency; +import io.quarkus.bootstrap.model.AppModel; +import io.quarkus.bootstrap.resolver.TsArtifact; +import io.quarkus.bootstrap.resolver.TsQuarkusExt; +import io.quarkus.creator.AppCreator; +import io.quarkus.creator.phase.curate.CurateOutcome; +import io.quarkus.creator.phase.curate.CuratePhase; +import io.quarkus.creator.phase.curate.VersionUpdate; +import io.quarkus.creator.phase.curate.VersionUpdateNumber; +import io.quarkus.creator.phase.runnerjar.test.CreatorOutcomeTestBase; + +public class CheckUpdatesDisableTest extends CreatorOutcomeTestBase { + + @Override + protected void initProps(Properties props) { + props.setProperty(CuratePhase.completePropertyName(CuratePhase.CONFIG_PROP_VERSION_UPDATE), + VersionUpdate.NONE.getName()); // NONE, next, latest + props.setProperty(CuratePhase.completePropertyName(CuratePhase.CONFIG_PROP_VERSION_UPDATE_NUMBER), + VersionUpdateNumber.MAJOR.getName()); // major, minor, MICRO + } + + @Override + protected TsArtifact modelApp() throws Exception { + new TsQuarkusExt("ext1", "1.0.1").install(repo); + new TsQuarkusExt("ext1", "1.0.2").install(repo); + new TsQuarkusExt("ext1", "1.1.0").install(repo); + new TsQuarkusExt("ext1", "1.2.0").install(repo); + new TsQuarkusExt("ext1", "2.0.0").install(repo); + new TsQuarkusExt("ext1", "3.0.0").install(repo); + new TsQuarkusExt("ext1", "3.0.1").install(repo); + new TsQuarkusExt("ext1", "3.1.0").install(repo); + new TsQuarkusExt("ext1", "3.1.1").install(repo); + + return TsArtifact.jar("app") + .addDependency(new TsQuarkusExt("ext1", "1.0.0")) + .addDependency(TsArtifact.jar("random")) + .addDependency(new TsQuarkusExt("ext2", "1.0.0")); + } + + @Override + protected void testCreator(AppCreator creator) throws Exception { + final CurateOutcome outcome = creator.resolveOutcome(CurateOutcome.class); + + assertFalse(outcome.hasUpdatedDeps()); + + assertEquals(Collections.emptyList(), outcome.getUpdatedDeps()); + + final AppModel effectiveModel = outcome.getEffectiveModel(); + assertEquals(Arrays.asList(new AppDependency[] { + new AppDependency(TsArtifact.jar("ext1", "1.0.0").toAppArtifact(), "compile"), + new AppDependency(TsArtifact.jar("random").toAppArtifact(), "compile"), + new AppDependency(TsArtifact.jar("ext2", "1.0.0").toAppArtifact(), "compile") + }), effectiveModel.getUserDependencies()); + + assertEquals(Arrays.asList(new AppDependency[] { + new AppDependency(TsArtifact.jar("ext1-deployment", "1.0.0").toAppArtifact(), "compile"), + new AppDependency(TsArtifact.jar("ext2-deployment", "1.0.0").toAppArtifact(), "compile") + }), effectiveModel.getDeploymentDependencies()); + + } +} diff --git a/core/creator/src/test/java/io/quarkus/creator/phase/curate/test/UpdateToNextMicroAndPersistStateTest.java b/core/creator/src/test/java/io/quarkus/creator/phase/curate/test/UpdateToNextMicroAndPersistStateTest.java new file mode 100644 index 0000000000000..40bc331cc06a9 --- /dev/null +++ b/core/creator/src/test/java/io/quarkus/creator/phase/curate/test/UpdateToNextMicroAndPersistStateTest.java @@ -0,0 +1,89 @@ +package io.quarkus.creator.phase.curate.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Properties; + +import io.quarkus.bootstrap.model.AppDependency; +import io.quarkus.bootstrap.model.AppModel; +import io.quarkus.bootstrap.resolver.TsArtifact; +import io.quarkus.bootstrap.resolver.TsQuarkusExt; +import io.quarkus.creator.AppCreator; +import io.quarkus.creator.phase.curate.CurateOutcome; +import io.quarkus.creator.phase.curate.CuratePhase; +import io.quarkus.creator.phase.curate.DependenciesOrigin; +import io.quarkus.creator.phase.curate.VersionUpdate; +import io.quarkus.creator.phase.curate.VersionUpdateNumber; +import io.quarkus.creator.phase.runnerjar.test.CreatorOutcomeTestBase; + +public class UpdateToNextMicroAndPersistStateTest extends CreatorOutcomeTestBase { + + private static final String[] EXPECTED_UPDATES = new String[] { "1.0.1", "1.0.2" }; + + private int buildNo; + + @Override + protected void initProps(Properties props) { + props.setProperty(CuratePhase.completePropertyName(CuratePhase.CONFIG_PROP_DEPS_ORIGIN), + DependenciesOrigin.LAST_UPDATE.getName()); // APPLICATION, last-update + props.setProperty(CuratePhase.completePropertyName(CuratePhase.CONFIG_PROP_VERSION_UPDATE), + VersionUpdate.NEXT.getName()); // NONE, next, latest + props.setProperty(CuratePhase.completePropertyName(CuratePhase.CONFIG_PROP_VERSION_UPDATE_NUMBER), + VersionUpdateNumber.MICRO.getName()); // major, minor, MICRO + } + + @Override + protected TsArtifact modelApp() throws Exception { + new TsQuarkusExt("ext1", "1.0.1").install(repo); + new TsQuarkusExt("ext1", "1.0.2").install(repo); + new TsQuarkusExt("ext1", "1.1.0").install(repo); + new TsQuarkusExt("ext1", "2.0.0").install(repo); + + return TsArtifact.jar("app") + .addDependency(new TsQuarkusExt("ext1", "1.0.0")) + .addDependency(TsArtifact.jar("random")) + .addDependency(new TsQuarkusExt("ext2", "1.0.0")); + } + + @Override + protected void testCreator(AppCreator creator) throws Exception { + + final CurateOutcome outcome = creator.resolveOutcome(CurateOutcome.class); + + final String expectedVersion; + + if (buildNo < EXPECTED_UPDATES.length) { + expectedVersion = EXPECTED_UPDATES[buildNo]; + assertTrue(outcome.hasUpdatedDeps()); + assertEquals(Arrays.asList(new AppDependency[] { + new AppDependency(TsArtifact.jar("ext1", expectedVersion).toAppArtifact(), "compile") + }), outcome.getUpdatedDeps()); + } else { + expectedVersion = EXPECTED_UPDATES[buildNo - 1]; + assertFalse(outcome.hasUpdatedDeps()); + assertEquals(Collections.emptyList(), outcome.getUpdatedDeps()); + } + + final AppModel effectiveModel = outcome.getEffectiveModel(); + assertEquals(Arrays.asList(new AppDependency[] { + new AppDependency(TsArtifact.jar("ext1", expectedVersion).toAppArtifact(), "compile"), + new AppDependency(TsArtifact.jar("random").toAppArtifact(), "compile"), + new AppDependency(TsArtifact.jar("ext2", "1.0.0").toAppArtifact(), "compile") + }), effectiveModel.getUserDependencies()); + + assertEquals(Arrays.asList(new AppDependency[] { + new AppDependency(TsArtifact.jar("ext1-deployment", expectedVersion).toAppArtifact(), "compile"), + new AppDependency(TsArtifact.jar("ext2-deployment", "1.0.0").toAppArtifact(), "compile") + }), effectiveModel.getDeploymentDependencies()); + + outcome.persist(creator); + + if (++buildNo <= EXPECTED_UPDATES.length) { + rebuild(); + } + } +} diff --git a/core/creator/src/test/java/io/quarkus/creator/phase/runnerjar/test/BasicRunnerJarOutcomeTest.java b/core/creator/src/test/java/io/quarkus/creator/phase/runnerjar/test/BasicRunnerJarOutcomeTest.java new file mode 100644 index 0000000000000..230cdb26814c6 --- /dev/null +++ b/core/creator/src/test/java/io/quarkus/creator/phase/runnerjar/test/BasicRunnerJarOutcomeTest.java @@ -0,0 +1,48 @@ +package io.quarkus.creator.phase.runnerjar.test; + +import io.quarkus.bootstrap.resolver.TsArtifact; +import io.quarkus.bootstrap.resolver.TsDependency; +import io.quarkus.bootstrap.resolver.TsQuarkusExt; + +public class BasicRunnerJarOutcomeTest extends RunnerJarOutcomeTestBase { + + @Override + protected TsArtifact modelApp() { + final TsQuarkusExt coreExt = new TsQuarkusExt("core-ext"); + addToExpectedLib(coreExt.getRuntime()); + + final TsQuarkusExt ext1 = new TsQuarkusExt("ext1"); + ext1.addDependency(coreExt); + addToExpectedLib(ext1.getRuntime()); + + final TsQuarkusExt ext2 = new TsQuarkusExt("ext2"); + addToExpectedLib(ext2.getRuntime()); + + final TsArtifact transitiveDep1 = TsArtifact.jar("transitive1"); + addToExpectedLib(transitiveDep1); + + final TsArtifact optionalTransitiveDep = TsArtifact.jar("optional-transitive-dep"); + + final TsArtifact compileDep = TsArtifact.jar("compile-dep") + .addDependency(transitiveDep1) + .addDependency(new TsDependency(optionalTransitiveDep, true)); + addToExpectedLib(compileDep); + + final TsArtifact providedDep = TsArtifact.jar("provided-dep"); + + final TsArtifact optionalDep = TsArtifact.jar("optional-dep"); + addToExpectedLib(optionalDep); // TODO should direct optional deps be included? + + final TsArtifact directRtDep = TsArtifact.jar("runtime-dep"); + addToExpectedLib(directRtDep); + + final TsArtifact appJar = TsArtifact.jar("app") + .addDependency(ext1) + .addDependency(ext2) + .addDependency(compileDep) + .addDependency(new TsDependency(providedDep, "provided")) + .addDependency(new TsDependency(optionalDep, true)) + .addDependency(new TsDependency(directRtDep, "runtime")); + return appJar; + } +} diff --git a/core/creator/src/test/java/io/quarkus/creator/phase/runnerjar/test/CreatorOutcomeTestBase.java b/core/creator/src/test/java/io/quarkus/creator/phase/runnerjar/test/CreatorOutcomeTestBase.java new file mode 100644 index 0000000000000..20dd0e647eea6 --- /dev/null +++ b/core/creator/src/test/java/io/quarkus/creator/phase/runnerjar/test/CreatorOutcomeTestBase.java @@ -0,0 +1,58 @@ +package io.quarkus.creator.phase.runnerjar.test; + +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Properties; + +import org.junit.Test; + +import io.quarkus.bootstrap.resolver.ResolverSetupCleanup; +import io.quarkus.bootstrap.resolver.TsArtifact; +import io.quarkus.creator.AppCreator; +import io.quarkus.creator.config.reader.PropertiesConfigReader; +import io.quarkus.creator.config.reader.PropertiesHandler; + +public abstract class CreatorOutcomeTestBase extends ResolverSetupCleanup { + + protected TsArtifact appJar; + protected Path propsFile; + + @Test + public void test() throws Exception { + appJar = modelApp(); + appJar.install(repo); + + final Properties props = getProperties(); + propsFile = workDir.resolve("app-creator.properties"); + try (OutputStream out = Files.newOutputStream(propsFile)) { + props.store(out, "Example AppCreator properties"); + } + + rebuild(); + } + + protected void rebuild() throws Exception { + final PropertiesHandler propsHandler = AppCreator.builder() + .setModelResolver(resolver) + .setWorkDir(workDir) + .setAppJar(resolver.resolve(appJar.toAppArtifact())) + .getPropertiesHandler(); + try (final AppCreator appCreator = PropertiesConfigReader.getInstance(propsHandler).read(propsFile)) { + testCreator(appCreator); + } + } + + protected abstract TsArtifact modelApp() throws Exception; + + protected abstract void testCreator(AppCreator creator) throws Exception; + + private Properties getProperties() { + final Properties props = new Properties(); + initProps(props); + return props; + } + + protected void initProps(Properties props) { + } +} diff --git a/core/creator/src/test/java/io/quarkus/creator/phase/runnerjar/test/DirectUserDepsOverrideTransitiveExtDepsTest.java b/core/creator/src/test/java/io/quarkus/creator/phase/runnerjar/test/DirectUserDepsOverrideTransitiveExtDepsTest.java new file mode 100644 index 0000000000000..078a2c2407f75 --- /dev/null +++ b/core/creator/src/test/java/io/quarkus/creator/phase/runnerjar/test/DirectUserDepsOverrideTransitiveExtDepsTest.java @@ -0,0 +1,32 @@ +package io.quarkus.creator.phase.runnerjar.test; + +import io.quarkus.bootstrap.resolver.TsArtifact; +import io.quarkus.bootstrap.resolver.TsQuarkusExt; + +public class DirectUserDepsOverrideTransitiveExtDepsTest extends RunnerJarOutcomeTestBase { + + @Override + protected TsArtifact modelApp() { + + final TsArtifact common1 = TsArtifact.jar("common", "1"); + + final TsQuarkusExt ext1 = new TsQuarkusExt("ext1"); + ext1.getRuntime().addDependency(common1); + addToExpectedLib(ext1.getRuntime()); + + final TsArtifact common2 = TsArtifact.jar("common", "2"); + + final TsQuarkusExt ext2 = new TsQuarkusExt("ext2"); + ext2.getRuntime().addDependency(common2); + addToExpectedLib(ext2.getRuntime()); + + final TsArtifact common3 = TsArtifact.jar("common", "3"); + addToExpectedLib(common3); + + final TsArtifact appJar = TsArtifact.jar("app") + .addDependency(ext1) + .addDependency(common3) + .addDependency(ext2); + return appJar; + } +} diff --git a/core/creator/src/test/java/io/quarkus/creator/phase/runnerjar/test/FirstTransitiveDepVersionIsTheEffectiveOneTest.java b/core/creator/src/test/java/io/quarkus/creator/phase/runnerjar/test/FirstTransitiveDepVersionIsTheEffectiveOneTest.java new file mode 100644 index 0000000000000..4ba55f032be78 --- /dev/null +++ b/core/creator/src/test/java/io/quarkus/creator/phase/runnerjar/test/FirstTransitiveDepVersionIsTheEffectiveOneTest.java @@ -0,0 +1,35 @@ +package io.quarkus.creator.phase.runnerjar.test; + +import io.quarkus.bootstrap.resolver.TsArtifact; +import io.quarkus.bootstrap.resolver.TsQuarkusExt; + +public class FirstTransitiveDepVersionIsTheEffectiveOneTest extends RunnerJarOutcomeTestBase { + + @Override + protected TsArtifact modelApp() { + + final TsArtifact common1 = TsArtifact.jar("common", "1"); + addToExpectedLib(common1); + + final TsQuarkusExt ext1 = new TsQuarkusExt("ext1"); + ext1.getRuntime().addDependency(common1); + addToExpectedLib(ext1.getRuntime()); + + final TsArtifact common2 = TsArtifact.jar("common", "2"); + + final TsQuarkusExt ext2 = new TsQuarkusExt("ext2"); + ext2.getRuntime().addDependency(common2); + addToExpectedLib(ext2.getRuntime()); + + final TsArtifact common3 = TsArtifact.jar("common", "3"); + + final TsArtifact directAppDep = TsArtifact.jar("direct-dep").addDependency(common3); + addToExpectedLib(directAppDep); + + final TsArtifact appJar = TsArtifact.jar("app") + .addDependency(ext1) + .addDependency(directAppDep) + .addDependency(ext2); + return appJar; + } +} diff --git a/core/creator/src/test/java/io/quarkus/creator/phase/runnerjar/test/RunnerJarOutcomeTestBase.java b/core/creator/src/test/java/io/quarkus/creator/phase/runnerjar/test/RunnerJarOutcomeTestBase.java new file mode 100644 index 0000000000000..bf4bcada56484 --- /dev/null +++ b/core/creator/src/test/java/io/quarkus/creator/phase/runnerjar/test/RunnerJarOutcomeTestBase.java @@ -0,0 +1,102 @@ +package io.quarkus.creator.phase.runnerjar.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.jar.Attributes; +import java.util.jar.JarFile; +import java.util.stream.Stream; + +import io.quarkus.bootstrap.resolver.TsArtifact; +import io.quarkus.creator.AppCreator; +import io.quarkus.creator.phase.runnerjar.RunnerJarOutcome; + +public abstract class RunnerJarOutcomeTestBase extends CreatorOutcomeTestBase { + + private static final String LIB_PREFIX = "lib/"; + private static final String MAIN_CLS = "io.quarkus.runner.GeneratedMain"; + + protected List expectedLib = new ArrayList<>(); + + protected void addToExpectedLib(TsArtifact entry) { + expectedLib.add(entry.getGroupId() + '.' + entry.getArtifactId() + '-' + entry.getVersion() + '.' + entry.getType()); + } + + @Override + protected void testCreator(AppCreator creator) throws Exception { + final RunnerJarOutcome outcome = creator.resolveOutcome(RunnerJarOutcome.class); + + final Path libDir = outcome.getLibDir(); + assertTrue(Files.isDirectory(libDir)); + final Set actualLib = new HashSet<>(); + try (Stream stream = Files.list(libDir)) { + final Iterator i = stream.iterator(); + while (i.hasNext()) { + actualLib.add(i.next().getFileName().toString()); + } + } + + final Path runnerJar = outcome.getRunnerJar(); + assertTrue(Files.exists(runnerJar)); + try (JarFile jar = new JarFile(runnerJar.toFile())) { + final Attributes mainAttrs = jar.getManifest().getMainAttributes(); + + // assert the main class + assertEquals(MAIN_CLS, mainAttrs.getValue("Main-Class")); + + // assert the Class-Path contains all the entries in the lib dir + final String cp = mainAttrs.getValue("Class-Path"); + assertNotNull(cp); + String[] cpEntries = cp.trim().split("\\s+"); + assertEquals(actualLib.size(), cpEntries.length); + for (String entry : cpEntries) { + assertTrue(entry.startsWith(LIB_PREFIX)); + assertTrue(actualLib.contains(entry.substring(LIB_PREFIX.length()))); + } + } + + List missingEntries = Collections.emptyList(); + for (String entry : expectedLib) { + if (!actualLib.remove(entry)) { + if (missingEntries.isEmpty()) { + missingEntries = new ArrayList<>(); + } + missingEntries.add(entry); + } + } + + StringBuilder buf = null; + if (!missingEntries.isEmpty()) { + buf = new StringBuilder(); + buf.append("Missing entries: ").append(missingEntries.get(0)); + for (int i = 1; i < missingEntries.size(); ++i) { + buf.append(", ").append(missingEntries.get(i)); + } + } + if (!actualLib.isEmpty()) { + if (buf == null) { + buf = new StringBuilder(); + } else { + buf.append("; "); + } + final Iterator i = actualLib.iterator(); + buf.append("Extra entries: ").append(i.next()); + while (i.hasNext()) { + buf.append(", ").append(i.next()); + } + } + if (buf != null) { + fail(buf.toString()); + } + } +} \ No newline at end of file diff --git a/core/creator/src/test/java/io/quarkus/creator/phase/runnerjar/test/SimpleExtAndAppCompileDepsTest.java b/core/creator/src/test/java/io/quarkus/creator/phase/runnerjar/test/SimpleExtAndAppCompileDepsTest.java new file mode 100644 index 0000000000000..1a23ced961708 --- /dev/null +++ b/core/creator/src/test/java/io/quarkus/creator/phase/runnerjar/test/SimpleExtAndAppCompileDepsTest.java @@ -0,0 +1,57 @@ +package io.quarkus.creator.phase.runnerjar.test; + +import io.quarkus.bootstrap.resolver.TsArtifact; +import io.quarkus.bootstrap.resolver.TsQuarkusExt; + +public class SimpleExtAndAppCompileDepsTest extends RunnerJarOutcomeTestBase { + + @Override + protected TsArtifact modelApp() { + + final TsArtifact coreExtRtTransitiveDep = TsArtifact.jar("core-ext-rt-transitive-dep"); + addToExpectedLib(coreExtRtTransitiveDep); + + final TsArtifact coreExtRtDirectDep = TsArtifact.jar("core-ext-rt-direct-dep").addDependency(coreExtRtTransitiveDep); + addToExpectedLib(coreExtRtDirectDep); + + final TsArtifact coreExtDepTransitiveDep = TsArtifact.jar("core-ext-dep-transitive-dep"); + + final TsArtifact coreExtDepDirectDep = TsArtifact.jar("core-ext-dep-direct-dep").addDependency(coreExtDepTransitiveDep); + + final TsQuarkusExt coreExt = new TsQuarkusExt("core-ext"); + coreExt.getRuntime().addDependency(coreExtRtDirectDep); + coreExt.getDeployment().addDependency(coreExtDepDirectDep); + addToExpectedLib(coreExt.getRuntime()); + + final TsArtifact ext1RtTransitiveDep = TsArtifact.jar("ext1-rt-transitive-dep"); + addToExpectedLib(ext1RtTransitiveDep); + + final TsArtifact ext1RtDirectDep = TsArtifact.jar("ext1-rt-direct-dep").addDependency(ext1RtTransitiveDep); + addToExpectedLib(ext1RtDirectDep); + + final TsArtifact ext1DepTransitiveDep = TsArtifact.jar("ext1-dep-transitive-dep"); + + final TsArtifact ext1DepDirectDep = TsArtifact.jar("ext1-dep-direct-dep").addDependency(ext1DepTransitiveDep); + + final TsQuarkusExt ext1 = new TsQuarkusExt("ext1"); + ext1.addDependency(coreExt); + ext1.getRuntime().addDependency(ext1RtDirectDep); + ext1.getDeployment().addDependency(ext1DepDirectDep); + addToExpectedLib(ext1.getRuntime()); + + final TsQuarkusExt ext2 = new TsQuarkusExt("ext2"); + addToExpectedLib(ext2.getRuntime()); + + final TsArtifact appTransitiveDep = TsArtifact.jar("app-transitive-dep"); + addToExpectedLib(appTransitiveDep); + + final TsArtifact appDirectDep = TsArtifact.jar("app-direct-dep").addDependency(appTransitiveDep); + addToExpectedLib(appDirectDep); + + final TsArtifact appJar = TsArtifact.jar("app") + .addDependency(ext1) + .addDependency(ext2) + .addDependency(appDirectDep); + return appJar; + } +} diff --git a/core/creator/src/test/java/io/quarkus/creator/resolver/test/TsArtifact.java b/core/creator/src/test/java/io/quarkus/creator/resolver/test/TsArtifact.java deleted file mode 100644 index a5afc0dfa04fb..0000000000000 --- a/core/creator/src/test/java/io/quarkus/creator/resolver/test/TsArtifact.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright 2019 Red Hat, Inc. - * - * Licensed 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.creator.resolver.test; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import org.apache.maven.model.Model; - -import io.quarkus.creator.AppArtifact; - -/** - * - * @author Alexey Loubyansky - */ -public class TsArtifact { - - static final String DEFAULT_GROUP_ID = "org.quarkus.creator.test"; - - public static TsArtifact getGa(String groupId, String artifactId) { - return new TsArtifact(groupId, artifactId, null); - } - - public static TsArtifact getGa(String artifactId) { - return getGa(DEFAULT_GROUP_ID, artifactId); - } - - protected final String groupId; - protected final String artifactId; - protected final String classifier; - protected final String type; - protected final String version; - - private List deps = Collections.emptyList(); - - public TsArtifact(String artifactId) { - this(artifactId, "1"); - } - - public TsArtifact(String artifactId, String version) { - this(DEFAULT_GROUP_ID, artifactId, "", "txt", version); - } - - public TsArtifact(String groupId, String artifactId, String version) { - this(groupId, artifactId, "", "txt", version); - } - - public TsArtifact(String groupId, String artifactId, String classifier, String type, String version) { - this.groupId = groupId; - this.artifactId = artifactId; - this.classifier = classifier; - this.type = type; - this.version = version; - } - - public TsArtifact addDependency(TsArtifact dep) { - return addDependency(new TsDependency(dep)); - } - - public TsArtifact addDependency(TsDependency dep) { - if (deps.isEmpty()) { - deps = new ArrayList<>(); - } - deps.add(dep); - return this; - } - - public String getArtifactFileName() { - if (artifactId == null) { - throw new IllegalArgumentException("artifactId is missing"); - } - if (version == null) { - throw new IllegalArgumentException("version is missing"); - } - if (type == null) { - throw new IllegalArgumentException("type is missing"); - } - final StringBuilder fileName = new StringBuilder(); - fileName.append(artifactId).append('-').append(version); - if (classifier != null && !classifier.isEmpty()) { - fileName.append('-').append(classifier); - } - fileName.append('.').append(type); - return fileName.toString(); - } - - public TsArtifact toPomArtifact() { - return new TsArtifact(groupId, artifactId, "", "pom", version); - } - - public Model getPomModel() { - final Model model = new Model(); - model.setModelVersion("4.0.0"); - - model.setGroupId(groupId); - model.setArtifactId(artifactId); - model.setPackaging(type); - model.setVersion(version); - - if (!deps.isEmpty()) { - for (TsDependency dep : deps) { - model.addDependency(dep.toPomDependency()); - } - } - - return model; - } - - public AppArtifact toAppArtifact() { - return new AppArtifact(groupId, artifactId, classifier, type, version); - } -} diff --git a/core/creator/src/test/java/io/quarkus/creator/resolver/test/TsRepoBuilder.java b/core/creator/src/test/java/io/quarkus/creator/resolver/test/TsRepoBuilder.java deleted file mode 100644 index 9dd5850ac8d5e..0000000000000 --- a/core/creator/src/test/java/io/quarkus/creator/resolver/test/TsRepoBuilder.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2019 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed 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.creator.resolver.test; - -import java.io.BufferedWriter; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; - -import io.quarkus.creator.AppArtifact; -import io.quarkus.creator.AppCreatorException; -import io.quarkus.creator.phase.curate.Utils; -import io.quarkus.creator.resolver.aether.AetherArtifactResolver; - -/** - * - * @author Alexey Loubyansky - */ -public class TsRepoBuilder { - - private static void error(String message, Throwable t) { - throw new IllegalStateException(message, t); - } - - public static TsRepoBuilder getInstance(AetherArtifactResolver resolver, Path workDir) { - return new TsRepoBuilder(resolver, workDir); - } - - private final Path workDir; - private final AetherArtifactResolver resolver; - - private TsRepoBuilder(AetherArtifactResolver resolver, Path workDir) { - this.resolver = resolver; - this.workDir = workDir; - } - - public void install(TsArtifact artifact) { - final Path pomXml = workDir.resolve(artifact.getArtifactFileName() + ".pom"); - try { - Utils.persistModel(pomXml, artifact.getPomModel()); - } catch (AppCreatorException e) { - error("Failed to persist pom.xml for " + artifact, e); - } - install(artifact.toPomArtifact().toAppArtifact(), pomXml); - install(artifact.toAppArtifact(), newTxt(artifact)); - } - - protected void install(AppArtifact artifact, Path file) { - try { - resolver.install(artifact, file); - } catch (AppCreatorException e) { - error("Failed to install " + artifact, e); - } - } - - protected Path newTxt(TsArtifact artifact) { - final Path tmpFile = workDir.resolve(artifact.getArtifactFileName()); - if (Files.exists(tmpFile)) { - throw new IllegalStateException("File already exists " + tmpFile); - } - try (BufferedWriter writer = Files.newBufferedWriter(tmpFile)) { - writer.write(tmpFile.getFileName().toString()); - } catch (IOException e) { - throw new IllegalStateException("Failed to create file " + tmpFile, e); - } - return tmpFile; - } - -} diff --git a/core/deployment/pom.xml b/core/deployment/pom.xml index e71ce260d8f1b..022d3a3b97453 100644 --- a/core/deployment/pom.xml +++ b/core/deployment/pom.xml @@ -27,13 +27,13 @@ ../ - quarkus-core + quarkus-core-deployment Quarkus - Core - Deployment commons-beanutils - commons-beanutils-core + commons-beanutils org.jboss.invocation @@ -67,7 +67,12 @@ io.quarkus - quarkus-core-runtime + quarkus-bootstrap-core + provided + + + io.quarkus + quarkus-core io.quarkus.gizmo @@ -78,6 +83,7 @@ junit junit + test io.quarkus diff --git a/core/deployment/src/main/java/io/quarkus/deployment/ApplicationArchiveImpl.java b/core/deployment/src/main/java/io/quarkus/deployment/ApplicationArchiveImpl.java index 00de7a837e984..12b21b20b21c0 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/ApplicationArchiveImpl.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/ApplicationArchiveImpl.java @@ -20,9 +20,10 @@ import java.io.IOException; import java.nio.file.Path; -import org.jboss.builder.item.MultiBuildItem; import org.jboss.jandex.IndexView; +import io.quarkus.builder.item.MultiBuildItem; + public final class ApplicationArchiveImpl extends MultiBuildItem implements ApplicationArchive, Closeable { private final IndexView indexView; diff --git a/core/deployment/src/main/java/io/quarkus/deployment/ApplicationConfig.java b/core/deployment/src/main/java/io/quarkus/deployment/ApplicationConfig.java new file mode 100644 index 0000000000000..180ae5ad3a113 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/ApplicationConfig.java @@ -0,0 +1,23 @@ +package io.quarkus.deployment; + +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 ApplicationConfig { + + /** + * The name of the application. + * If not set, defaults to the name of the project. + */ + @ConfigItem + public String name; + + /** + * The version of the application. + * If not set, defaults to the version of the project + */ + @ConfigItem + public String version; +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/ApplicationInfoUtil.java b/core/deployment/src/main/java/io/quarkus/deployment/ApplicationInfoUtil.java new file mode 100644 index 0000000000000..31675942b20aa --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/ApplicationInfoUtil.java @@ -0,0 +1,37 @@ +package io.quarkus.deployment; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Path; +import java.util.Properties; + +import io.quarkus.bootstrap.model.AppArtifact; + +public final class ApplicationInfoUtil { + + public static final String APPLICATION_INFO_PROPERTIES = "application-info.properties"; + public static final String META_INF = "META-INF"; + + public static final String ARTIFACT_ID_KEY = "artifactId"; + public static final String VERSION_KEY = "version"; + + private ApplicationInfoUtil() { + } + + // these properties are used as default values for ApplicationInfoBuildItem + public static void writeApplicationInfoProperties(AppArtifact appArtifact, Path appClassesDir) { + Properties properties = new Properties(); + if (appArtifact != null) { + properties.setProperty("artifactId", appArtifact.getArtifactId()); + properties.setProperty("version", appArtifact.getVersion()); + } + try { + appClassesDir.resolve(META_INF).toFile().mkdirs(); + File file = appClassesDir.resolve(META_INF).resolve(APPLICATION_INFO_PROPERTIES).toFile(); + properties.store(new FileOutputStream(file), "Generated file; do not edit manually"); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/BuildProducerImpl.java b/core/deployment/src/main/java/io/quarkus/deployment/BuildProducerImpl.java index 237ce2b12fe73..779324933906d 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/BuildProducerImpl.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/BuildProducerImpl.java @@ -16,9 +16,8 @@ package io.quarkus.deployment; -import org.jboss.builder.BuildContext; -import org.jboss.builder.item.BuildItem; - +import io.quarkus.builder.BuildContext; +import io.quarkus.builder.item.BuildItem; import io.quarkus.deployment.annotations.BuildProducer; /** 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 c242c84c40122..2270b6abf7fe1 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/Capabilities.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/Capabilities.java @@ -18,7 +18,7 @@ import java.util.Set; -import org.jboss.builder.item.SimpleBuildItem; +import io.quarkus.builder.item.SimpleBuildItem; /** * The list of capabilities. 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 41d7f6d2aba9a..4c2f58cd7837e 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/ExtensionLoader.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/ExtensionLoader.java @@ -23,6 +23,7 @@ import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collections; +import java.util.EnumSet; import java.util.List; import java.util.Optional; import java.util.concurrent.Executor; @@ -32,31 +33,34 @@ import java.util.function.Function; import java.util.function.Supplier; -import org.jboss.builder.BuildChainBuilder; -import org.jboss.builder.BuildContext; -import org.jboss.builder.BuildStepBuilder; -import org.jboss.builder.ConsumeFlag; -import org.jboss.builder.ConsumeFlags; -import org.jboss.builder.ProduceFlag; -import org.jboss.builder.ProduceFlags; -import org.jboss.builder.item.BuildItem; -import org.jboss.builder.item.MultiBuildItem; -import org.jboss.builder.item.SimpleBuildItem; import org.wildfly.common.function.Functions; +import io.quarkus.builder.BuildChainBuilder; +import io.quarkus.builder.BuildContext; +import io.quarkus.builder.BuildStepBuilder; +import io.quarkus.builder.ConsumeFlag; +import io.quarkus.builder.ConsumeFlags; +import io.quarkus.builder.ProduceFlag; +import io.quarkus.builder.ProduceFlags; +import io.quarkus.builder.item.BuildItem; +import io.quarkus.builder.item.MultiBuildItem; +import io.quarkus.builder.item.SimpleBuildItem; 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.AdditionalApplicationArchiveMarkerBuildItem; +import io.quarkus.deployment.builditem.BuildTimeConfigurationBuildItem; +import io.quarkus.deployment.builditem.BuildTimeRunTimeFixedConfigurationBuildItem; import io.quarkus.deployment.builditem.CapabilityBuildItem; -import io.quarkus.deployment.builditem.ConfigurationBuildItem; import io.quarkus.deployment.builditem.MainBytecodeRecorderBuildItem; +import io.quarkus.deployment.builditem.RunTimeConfigurationBuildItem; import io.quarkus.deployment.builditem.StaticBytecodeRecorderBuildItem; import io.quarkus.deployment.recording.BytecodeRecorderImpl; import io.quarkus.deployment.recording.RecorderContext; import io.quarkus.deployment.util.ReflectUtil; import io.quarkus.deployment.util.ServiceUtil; +import io.quarkus.runtime.annotations.ConfigPhase; import io.quarkus.runtime.annotations.ConfigRoot; import io.quarkus.runtime.annotations.Template; @@ -107,7 +111,7 @@ public static Consumer loadStepsFrom(Class clazz) { throw reportError(clazz, "Build step classes must have exactly one constructor"); } - boolean consumingConfig = false; + EnumSet consumingConfigPhases = EnumSet.noneOf(ConfigPhase.class); final Constructor constructor = constructors[0]; if (!(Modifier.isPublic(constructor.getModifiers()))) @@ -160,9 +164,22 @@ public static Consumer loadStepsFrom(Class clazz) { } else if (rawTypeOf(parameterType) == Executor.class) { ctorParamFns.add(BuildContext::getExecutor); } else if (parameterClass.isAnnotationPresent(ConfigRoot.class)) { - consumingConfig = true; - ctorParamFns.add(bc -> bc.consume(ConfigurationBuildItem.class).getConfigDefinition() - .getRealizedInstance(parameterClass)); + final ConfigRoot annotation = parameterClass.getAnnotation(ConfigRoot.class); + 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)); + } else if (phase == ConfigPhase.RUN_TIME) { + ctorParamFns.add(bc -> bc.consume(RunTimeConfigurationBuildItem.class).getConfigDefinition() + .getRealizedInstance(parameterClass)); + } else { + throw reportError(parameterClass, "Unknown value for ConfigPhase"); + } } else if (isTemplate(parameterClass)) { throw reportError(parameter, "Bytecode recording templates disallowed on constructor parameters"); } else { @@ -231,12 +248,34 @@ public static Consumer loadStepsFrom(Class clazz) { } else if (fieldClass == Executor.class) { stepInstanceSetup = stepInstanceSetup.andThen((bc, o) -> ReflectUtil.setFieldVal(field, o, bc.getExecutor())); } else if (fieldClass.isAnnotationPresent(ConfigRoot.class)) { - consumingConfig = true; - stepInstanceSetup = stepInstanceSetup.andThen((bc, o) -> { - final ConfigurationBuildItem configurationBuildItem = bc.consume(ConfigurationBuildItem.class); - ReflectUtil.setFieldVal(field, o, - configurationBuildItem.getConfigDefinition().getRealizedInstance(fieldClass)); - }); + final ConfigRoot annotation = fieldClass.getAnnotation(ConfigRoot.class); + 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) { + stepInstanceSetup = stepInstanceSetup.andThen((bc, o) -> { + final BuildTimeRunTimeFixedConfigurationBuildItem configurationBuildItem = bc + .consume(BuildTimeRunTimeFixedConfigurationBuildItem.class); + ReflectUtil.setFieldVal(field, o, + configurationBuildItem.getConfigDefinition().getRealizedInstance(fieldClass)); + }); + } 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)); + }); + } else { + throw reportError(fieldClass, "Unknown value for ConfigPhase"); + } } else if (isTemplate(fieldClass)) { throw reportError(field, "Bytecode recording templates disallowed on fields"); } else { @@ -288,7 +327,7 @@ public static Consumer loadStepsFrom(Class clazz) { : MainBytecodeRecorderBuildItem.class, optional ? ProduceFlags.of(ProduceFlag.WEAK) : ProduceFlags.NONE)); } - boolean methodConsumingConfig = consumingConfig; + EnumSet methodConsumingConfigPhases = consumingConfigPhases.clone(); if (methodParameters.length == 0) { methodParamFns = Collections.emptyList(); } else { @@ -337,11 +376,31 @@ public static Consumer loadStepsFrom(Class clazz) { } else if (rawTypeOf(parameterType) == Executor.class) { methodParamFns.add((bc, bri) -> bc.getExecutor()); } else if (parameterClass.isAnnotationPresent(ConfigRoot.class)) { - methodConsumingConfig = true; - methodParamFns.add((bc, bri) -> { - final ConfigurationBuildItem configurationBuildItem = bc.consume(ConfigurationBuildItem.class); - return configurationBuildItem.getConfigDefinition().getRealizedInstance(parameterClass); - }); + final ConfigRoot annotation = parameterClass.getAnnotation(ConfigRoot.class); + final ConfigPhase phase = annotation.phase(); + methodConsumingConfigPhases.add(phase); + + if (phase == ConfigPhase.BUILD_TIME) { + 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); + }); + } else if (phase == ConfigPhase.RUN_TIME) { + methodParamFns.add((bc, bri) -> { + final RunTimeConfigurationBuildItem configurationBuildItem = bc + .consume(RunTimeConfigurationBuildItem.class); + return configurationBuildItem.getConfigDefinition().getRealizedInstance(parameterClass); + }); + } else { + throw reportError(parameterClass, "Unknown value for ConfigPhase"); + } } else if (isTemplate(parameter.getType())) { if (!isRecorder) { throw reportError(parameter, @@ -390,76 +449,89 @@ public static Consumer loadStepsFrom(Class clazz) { throw reportError(method, "Unsupported method return type " + returnType); } - if (methodConsumingConfig) { + if (methodConsumingConfigPhases.contains(ConfigPhase.RUN_TIME)) { + if (isRecorder && recordAnnotation.value() == ExecutionTime.STATIC_INIT) { + throw reportError(method, + "Bytecode recorder is static but an injected config object is declared as run time"); + } methodStepConfig = methodStepConfig - .andThen(bsb -> bsb.consumes(ConfigurationBuildItem.class)); + .andThen(bsb -> bsb.consumes(RunTimeConfigurationBuildItem.class)); + } + if (methodConsumingConfigPhases.contains(ConfigPhase.BUILD_AND_RUN_TIME_FIXED)) { + methodStepConfig = methodStepConfig + .andThen(bsb -> bsb.consumes(BuildTimeRunTimeFixedConfigurationBuildItem.class)); + } + if (methodConsumingConfigPhases.contains(ConfigPhase.BUILD_TIME)) { + methodStepConfig = methodStepConfig + .andThen(bsb -> bsb.consumes(BuildTimeConfigurationBuildItem.class)); } final Consumer finalStepConfig = stepConfig.andThen(methodStepConfig) .andThen(BuildStepBuilder::build); final BiConsumer finalStepInstanceSetup = stepInstanceSetup; final String name = clazz.getName() + "#" + method.getName(); - chainConfig = chainConfig.andThen(bcb -> finalStepConfig.accept(bcb.addBuildStep(new org.jboss.builder.BuildStep() { - public void execute(final BuildContext bc) { - Object[] ctorArgs = new Object[ctorParamFns.size()]; - for (int i = 0; i < ctorArgs.length; i++) { - ctorArgs[i] = ctorParamFns.get(i).apply(bc); - } - Object instance; - try { - instance = constructor.newInstance(ctorArgs); - } catch (InstantiationException e) { - throw ReflectUtil.toError(e); - } catch (IllegalAccessException e) { - throw ReflectUtil.toError(e); - } catch (InvocationTargetException e) { - try { - throw e.getCause(); - } catch (RuntimeException | Error e2) { - throw e2; - } catch (Throwable t) { - throw new IllegalStateException(t); - } - } - finalStepInstanceSetup.accept(bc, instance); - Object[] methodArgs = new Object[methodParamFns.size()]; - BytecodeRecorderImpl bri = isRecorder - ? new BytecodeRecorderImpl(recordAnnotation.value() == ExecutionTime.STATIC_INIT, - clazz.getSimpleName(), method.getName()) - : null; - for (int i = 0; i < methodArgs.length; i++) { - methodArgs[i] = methodParamFns.get(i).apply(bc, bri); - } - Object result; - try { - result = method.invoke(instance, methodArgs); - } catch (IllegalAccessException e) { - throw ReflectUtil.toError(e); - } catch (InvocationTargetException e) { - try { - throw e.getCause(); - } catch (RuntimeException | Error e2) { - throw e2; - } catch (Throwable t) { - throw new IllegalStateException(t); - } - } - resultConsumer.accept(bc, result); - if (isRecorder) { - // commit recorded data - if (recordAnnotation.value() == ExecutionTime.STATIC_INIT) { - bc.produce(new StaticBytecodeRecorderBuildItem(bri)); - } else { - bc.produce(new MainBytecodeRecorderBuildItem(bri)); - } + chainConfig = chainConfig + .andThen(bcb -> finalStepConfig.accept(bcb.addBuildStep(new io.quarkus.builder.BuildStep() { + public void execute(final BuildContext bc) { + Object[] ctorArgs = new Object[ctorParamFns.size()]; + for (int i = 0; i < ctorArgs.length; i++) { + ctorArgs[i] = ctorParamFns.get(i).apply(bc); + } + Object instance; + try { + instance = constructor.newInstance(ctorArgs); + } catch (InstantiationException e) { + throw ReflectUtil.toError(e); + } catch (IllegalAccessException e) { + throw ReflectUtil.toError(e); + } catch (InvocationTargetException e) { + try { + throw e.getCause(); + } catch (RuntimeException | Error e2) { + throw e2; + } catch (Throwable t) { + throw new IllegalStateException(t); + } + } + finalStepInstanceSetup.accept(bc, instance); + Object[] methodArgs = new Object[methodParamFns.size()]; + BytecodeRecorderImpl bri = isRecorder + ? new BytecodeRecorderImpl(recordAnnotation.value() == ExecutionTime.STATIC_INIT, + clazz.getSimpleName(), method.getName()) + : null; + for (int i = 0; i < methodArgs.length; i++) { + methodArgs[i] = methodParamFns.get(i).apply(bc, bri); + } + Object result; + try { + result = method.invoke(instance, methodArgs); + } catch (IllegalAccessException e) { + throw ReflectUtil.toError(e); + } catch (InvocationTargetException e) { + try { + throw e.getCause(); + } catch (RuntimeException | Error e2) { + throw e2; + } catch (Throwable t) { + throw new IllegalStateException(t); + } + } + resultConsumer.accept(bc, result); + if (isRecorder) { + // commit recorded data + if (recordAnnotation.value() == ExecutionTime.STATIC_INIT) { + bc.produce(new StaticBytecodeRecorderBuildItem(bri)); + } else { + bc.produce(new MainBytecodeRecorderBuildItem(bri)); + } - } - } + } + } - public String toString() { - return name; - } - }))); + public String toString() { + return name; + } + }))); } return chainConfig; } 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 7bc6f6efedd03..d0eb7648a7df1 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/QuarkusAugmentor.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/QuarkusAugmentor.java @@ -23,15 +23,14 @@ import java.util.Set; import java.util.function.Consumer; -import org.jboss.builder.BuildChain; -import org.jboss.builder.BuildChainBuilder; -import org.jboss.builder.BuildExecutionBuilder; -import org.jboss.builder.BuildResult; -import org.jboss.builder.item.BuildItem; import org.jboss.logging.Logger; +import io.quarkus.builder.BuildChain; +import io.quarkus.builder.BuildChainBuilder; +import io.quarkus.builder.BuildExecutionBuilder; +import io.quarkus.builder.BuildResult; +import io.quarkus.builder.item.BuildItem; import io.quarkus.deployment.builditem.AdditionalApplicationArchiveBuildItem; -import io.quarkus.deployment.builditem.AdditionalApplicationArchiveMarkerBuildItem; import io.quarkus.deployment.builditem.ArchiveRootBuildItem; import io.quarkus.deployment.builditem.ClassOutputBuildItem; import io.quarkus.deployment.builditem.ExtensionClassLoaderBuildItem; @@ -39,7 +38,6 @@ import io.quarkus.deployment.builditem.GeneratedResourceBuildItem; import io.quarkus.deployment.builditem.LaunchModeBuildItem; import io.quarkus.deployment.builditem.ShutdownContextBuildItem; -import io.quarkus.deployment.builditem.substrate.SubstrateResourceBuildItem; import io.quarkus.runtime.LaunchMode; public class QuarkusAugmentor { @@ -78,7 +76,6 @@ public BuildResult run() throws Exception { chainBuilder .addInitial(QuarkusConfig.class) - .addInitial(SubstrateResourceBuildItem.class) .addInitial(ArchiveRootBuildItem.class) .addInitial(ShutdownContextBuildItem.class) .addInitial(ClassOutputBuildItem.class) @@ -98,8 +95,6 @@ public BuildResult run() throws Exception { BuildChain chain = chainBuilder .build(); BuildExecutionBuilder execBuilder = chain.createExecutionBuilder("main") - .produce(new SubstrateResourceBuildItem("META-INF/microprofile-config.properties")) - .produce(new SubstrateResourceBuildItem("application.properties")) .produce(QuarkusConfig.INSTANCE) .produce(new ArchiveRootBuildItem(root)) .produce(new ClassOutputBuildItem(output)) 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 cde046f974ea9..de94604617876 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/QuarkusConfig.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/QuarkusConfig.java @@ -24,8 +24,8 @@ import java.util.Set; import org.eclipse.microprofile.config.ConfigProvider; -import org.jboss.builder.item.SimpleBuildItem; +import io.quarkus.builder.item.SimpleBuildItem; import io.quarkus.deployment.configuration.ConfigurationError; public final class QuarkusConfig extends SimpleBuildItem { diff --git a/core/deployment/src/main/java/io/quarkus/deployment/annotations/BuildProducer.java b/core/deployment/src/main/java/io/quarkus/deployment/annotations/BuildProducer.java index 3ce088e09cb42..5f59db467b6a3 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/annotations/BuildProducer.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/annotations/BuildProducer.java @@ -1,6 +1,6 @@ package io.quarkus.deployment.annotations; -import org.jboss.builder.item.BuildItem; +import io.quarkus.builder.item.BuildItem; /** * An interface that can be injected to produce {@link BuildItem} instances diff --git a/core/deployment/src/main/java/io/quarkus/deployment/annotations/BuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/annotations/BuildStep.java index bd4a07ff90dd8..22b6e2954d4cc 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/annotations/BuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/annotations/BuildStep.java @@ -9,10 +9,9 @@ import java.util.function.Consumer; import java.util.function.Supplier; -import org.jboss.builder.item.BuildItem; -import org.jboss.builder.item.MultiBuildItem; -import org.jboss.builder.item.SimpleBuildItem; - +import io.quarkus.builder.item.BuildItem; +import io.quarkus.builder.item.MultiBuildItem; +import io.quarkus.builder.item.SimpleBuildItem; import io.quarkus.deployment.recording.BytecodeRecorderImpl; import io.quarkus.runtime.annotations.Template; diff --git a/core/deployment/src/main/java/io/quarkus/deployment/annotations/Record.java b/core/deployment/src/main/java/io/quarkus/deployment/annotations/Record.java index 32e1720eaae5b..360bd97534d74 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/annotations/Record.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/annotations/Record.java @@ -5,6 +5,8 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import io.quarkus.builder.item.BuildItem; + /** * Indicates that this {@link BuildStep} method will also output recorded bytecode. * @@ -30,7 +32,7 @@ /** * If this is true then the bytecode produced by this method will be considered to be optional, - * and will only be created if this build step also produces another {@link org.jboss.builder.item.BuildItem} + * and will only be created if this build step also produces another {@link BuildItem} * that is consumed by the build. * * If a method is optional it must be capable of producing at least one other item diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/AdditionalApplicationArchiveBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/AdditionalApplicationArchiveBuildItem.java index 2ca764c94c407..2a7b6fd212a03 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/AdditionalApplicationArchiveBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/AdditionalApplicationArchiveBuildItem.java @@ -18,7 +18,7 @@ import java.nio.file.Path; -import org.jboss.builder.item.MultiBuildItem; +import io.quarkus.builder.item.MultiBuildItem; /** * An additional application archive diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/AdditionalApplicationArchiveMarkerBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/AdditionalApplicationArchiveMarkerBuildItem.java index e19835a2d495f..3ae03c495fb4a 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/AdditionalApplicationArchiveMarkerBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/AdditionalApplicationArchiveMarkerBuildItem.java @@ -16,7 +16,7 @@ package io.quarkus.deployment.builditem; -import org.jboss.builder.item.MultiBuildItem; +import io.quarkus.builder.item.MultiBuildItem; /** * A marker file that if present indicates that a given archive should be treated as an diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/AnnotationProxyBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/AnnotationProxyBuildItem.java index 0312c22af61aa..c5ff23abe5afa 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/AnnotationProxyBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/AnnotationProxyBuildItem.java @@ -2,9 +2,9 @@ import java.lang.annotation.Annotation; -import org.jboss.builder.item.SimpleBuildItem; import org.jboss.jandex.AnnotationInstance; +import io.quarkus.builder.item.SimpleBuildItem; import io.quarkus.deployment.recording.AnnotationProxyProvider; import io.quarkus.deployment.recording.AnnotationProxyProvider.AnnotationProxyBuilder; import io.quarkus.runtime.annotations.Template; diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/ApplicationArchivesBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ApplicationArchivesBuildItem.java index d47101c49c993..71559d71d658b 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/ApplicationArchivesBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ApplicationArchivesBuildItem.java @@ -21,8 +21,7 @@ import java.util.HashSet; import java.util.Set; -import org.jboss.builder.item.SimpleBuildItem; - +import io.quarkus.builder.item.SimpleBuildItem; import io.quarkus.deployment.ApplicationArchive; //temp class diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/ApplicationClassNameBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ApplicationClassNameBuildItem.java index 4ebe970008caa..434946c8a1ea3 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/ApplicationClassNameBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ApplicationClassNameBuildItem.java @@ -16,7 +16,7 @@ package io.quarkus.deployment.builditem; -import org.jboss.builder.item.SimpleBuildItem; +import io.quarkus.builder.item.SimpleBuildItem; public final class ApplicationClassNameBuildItem extends SimpleBuildItem { private final String className; diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/ApplicationClassPredicateBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ApplicationClassPredicateBuildItem.java new file mode 100644 index 0000000000000..be756de364559 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ApplicationClassPredicateBuildItem.java @@ -0,0 +1,22 @@ +package io.quarkus.deployment.builditem; + +import java.util.function.Predicate; + +import io.quarkus.builder.item.MultiBuildItem; + +/** + * Makes it possible to identify wiring classes generated for classes from additional hot deployment paths. + */ +public final class ApplicationClassPredicateBuildItem extends MultiBuildItem { + + private final Predicate predicate; + + public ApplicationClassPredicateBuildItem(Predicate predicate) { + this.predicate = predicate; + } + + public boolean test(String name) { + return predicate.test(name); + } + +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/ApplicationIndexBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ApplicationIndexBuildItem.java index 35cfa6e1e6a1e..3d2b42c75af08 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/ApplicationIndexBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ApplicationIndexBuildItem.java @@ -16,9 +16,10 @@ package io.quarkus.deployment.builditem; -import org.jboss.builder.item.SimpleBuildItem; import org.jboss.jandex.Index; +import io.quarkus.builder.item.SimpleBuildItem; + /** * The Jandex index of the application root */ 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 new file mode 100644 index 0000000000000..c9a0549e7f00a --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ApplicationInfoBuildItem.java @@ -0,0 +1,24 @@ +package io.quarkus.deployment.builditem; + +import io.quarkus.builder.item.SimpleBuildItem; + +public final class ApplicationInfoBuildItem extends SimpleBuildItem { + + public static final String UNSET_VALUE = "<>"; + + private final String name; + private final String version; + + public ApplicationInfoBuildItem(String name, String version) { + this.name = name; + this.version = version; + } + + public String getName() { + return name; + } + + public String getVersion() { + return version; + } +} 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 dc82c19d68c5b..a4f18cab668b8 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 @@ -18,7 +18,7 @@ import java.nio.file.Path; -import org.jboss.builder.item.SimpleBuildItem; +import io.quarkus.builder.item.SimpleBuildItem; public final class ArchiveRootBuildItem extends SimpleBuildItem { diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/ConfigurationBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/BuildTimeConfigurationBuildItem.java similarity index 64% rename from core/deployment/src/main/java/io/quarkus/deployment/builditem/ConfigurationBuildItem.java rename to core/deployment/src/main/java/io/quarkus/deployment/builditem/BuildTimeConfigurationBuildItem.java index b572d0fcc39e8..70d4c58fb3a1d 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/ConfigurationBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/BuildTimeConfigurationBuildItem.java @@ -1,16 +1,15 @@ package io.quarkus.deployment.builditem; -import org.jboss.builder.item.SimpleBuildItem; - +import io.quarkus.builder.item.SimpleBuildItem; import io.quarkus.deployment.configuration.ConfigDefinition; /** * The build item which carries the build time configuration. */ -public final class ConfigurationBuildItem extends SimpleBuildItem { +public final class BuildTimeConfigurationBuildItem extends SimpleBuildItem { private final ConfigDefinition configDefinition; - public ConfigurationBuildItem(final ConfigDefinition configDefinition) { + public BuildTimeConfigurationBuildItem(final ConfigDefinition configDefinition) { this.configDefinition = 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 new file mode 100644 index 0000000000000..1c129c15a8a7e --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/BuildTimeRunTimeFixedConfigurationBuildItem.java @@ -0,0 +1,19 @@ +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/BytecodeRecorderObjectLoaderBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/BytecodeRecorderObjectLoaderBuildItem.java index 9cd7848009fb2..937eb65d6052e 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/BytecodeRecorderObjectLoaderBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/BytecodeRecorderObjectLoaderBuildItem.java @@ -1,7 +1,6 @@ package io.quarkus.deployment.builditem; -import org.jboss.builder.item.MultiBuildItem; - +import io.quarkus.builder.item.MultiBuildItem; import io.quarkus.deployment.recording.ObjectLoader; /** diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/BytecodeTransformerBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/BytecodeTransformerBuildItem.java index e83039277db56..a55c4cf9f8322 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/BytecodeTransformerBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/BytecodeTransformerBuildItem.java @@ -18,9 +18,10 @@ import java.util.function.BiFunction; -import org.jboss.builder.item.MultiBuildItem; import org.objectweb.asm.ClassVisitor; +import io.quarkus.builder.item.MultiBuildItem; + public final class BytecodeTransformerBuildItem extends MultiBuildItem { final String classToTransform; diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/CapabilityBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/CapabilityBuildItem.java index b6f6f8d6aa9b2..bdf21d2acd7b0 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/CapabilityBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/CapabilityBuildItem.java @@ -16,8 +16,7 @@ package io.quarkus.deployment.builditem; -import org.jboss.builder.item.MultiBuildItem; - +import io.quarkus.builder.item.MultiBuildItem; import io.quarkus.deployment.Capabilities; /** diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/ClassOutputBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ClassOutputBuildItem.java index ffec6b2090894..d81dde0d1be6d 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/ClassOutputBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ClassOutputBuildItem.java @@ -16,8 +16,7 @@ package io.quarkus.deployment.builditem; -import org.jboss.builder.item.SimpleBuildItem; - +import io.quarkus.builder.item.SimpleBuildItem; import io.quarkus.deployment.ClassOutput; public final class ClassOutputBuildItem extends SimpleBuildItem { diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/CombinedIndexBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/CombinedIndexBuildItem.java index b401596d417eb..37b2da0c5fbca 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/CombinedIndexBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/CombinedIndexBuildItem.java @@ -16,9 +16,10 @@ package io.quarkus.deployment.builditem; -import org.jboss.builder.item.SimpleBuildItem; import org.jboss.jandex.IndexView; +import io.quarkus.builder.item.SimpleBuildItem; + public final class CombinedIndexBuildItem extends SimpleBuildItem { private final IndexView index; diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/ConfigurationCustomConverterBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ConfigurationCustomConverterBuildItem.java index 2dc178a26509f..7f04d189f5101 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/ConfigurationCustomConverterBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ConfigurationCustomConverterBuildItem.java @@ -1,7 +1,8 @@ package io.quarkus.deployment.builditem; import org.eclipse.microprofile.config.spi.Converter; -import org.jboss.builder.item.MultiBuildItem; + +import io.quarkus.builder.item.MultiBuildItem; /** * A configuration converter to register. diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/ConfigurationTypeBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ConfigurationTypeBuildItem.java index 5bae0a012760d..0820564b4af8d 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/ConfigurationTypeBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ConfigurationTypeBuildItem.java @@ -1,8 +1,9 @@ package io.quarkus.deployment.builditem; -import org.jboss.builder.item.MultiBuildItem; import org.wildfly.common.Assert; +import io.quarkus.builder.item.MultiBuildItem; + /** * The configuration type build item. Every configuration type should be registered using this build item * to ensure that the converter is properly loaded in the native image case. diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/ExecutorBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ExecutorBuildItem.java index ad5d348b1ff45..9b860237f5743 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/ExecutorBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ExecutorBuildItem.java @@ -16,20 +16,21 @@ package io.quarkus.deployment.builditem; -import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; -import org.jboss.builder.item.SimpleBuildItem; +import io.quarkus.builder.item.SimpleBuildItem; /** + * The main executor for blocking tasks */ public final class ExecutorBuildItem extends SimpleBuildItem { - private final Executor executor; + private final ExecutorService executor; - public ExecutorBuildItem(final Executor executor) { + public ExecutorBuildItem(final ExecutorService executor) { this.executor = executor; } - public Executor getExecutorProxy() { + public ExecutorService getExecutorProxy() { return executor; } } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/ExtensionClassLoaderBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ExtensionClassLoaderBuildItem.java index f566155ffb111..24bfcad797b25 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/ExtensionClassLoaderBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ExtensionClassLoaderBuildItem.java @@ -1,6 +1,6 @@ package io.quarkus.deployment.builditem; -import org.jboss.builder.item.SimpleBuildItem; +import io.quarkus.builder.item.SimpleBuildItem; /** * The extension class loader. diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/ExtensionSslNativeSupportBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ExtensionSslNativeSupportBuildItem.java index 5686b56b70db4..0e2469dcfd094 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/ExtensionSslNativeSupportBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ExtensionSslNativeSupportBuildItem.java @@ -16,7 +16,7 @@ package io.quarkus.deployment.builditem; -import org.jboss.builder.item.MultiBuildItem; +import io.quarkus.builder.item.MultiBuildItem; public final class ExtensionSslNativeSupportBuildItem extends MultiBuildItem { 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 6a0927f37fe26..d1ee4f0198514 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 @@ -2,7 +2,7 @@ import java.util.Objects; -import org.jboss.builder.item.MultiBuildItem; +import io.quarkus.builder.item.MultiBuildItem; /** * Describes a functionality provided by an extension. The info is displayed to users. @@ -10,21 +10,28 @@ public final class FeatureBuildItem extends MultiBuildItem { public static final String AGROAL = "agroal"; + public static final String KUBERNETES = "kubernetes"; public static final String CAMEL_CORE = "camel-core"; public static final String CAMEL_INFINISPAN = "camel-infinispan"; + public static final String CAMEL_AWS_S3 = "camel-aws-s3"; public static final String CAMEL_NETTY4_HTTP = "camel-netty4-http"; public static final String CAMEL_SALESFORCE = "camel-salesforce"; public static final String CDI = "cdi"; + public static final String ELASTICSEARCH_REST_CLIENT = "elasticsearch-rest-client"; + public static final String FLYWAY = "flyway"; public static final String HIBERNATE_ORM = "hibernate-orm"; public static final String HIBERNATE_VALIDATOR = "hibernate-validator"; + public static final String HIBERNATE_SEARCH_ELASTICSEARCH = "hibernate-search-elasticsearch"; public static final String INFINISPAN_CLIENT = "infinispan-client"; public static final String JAEGER = "jaeger"; public static final String JDBC_H2 = "jdbc-h2"; public static final String JDBC_MARIADB = "jdbc-mariadb"; public static final String JDBC_POSTGRESQL = "jdbc-postgresql"; public static final String JDBC_MSSQL = "jdbc-mssql"; + public static final String KEYCLOAK = "keycloak"; public static final String KOTLIN = "kotlin"; public static final String NARAYANA_JTA = "narayana-jta"; + public static final String REACTIVE_PG_CLIENT = "reactive-pg-client"; public static final String RESTEASY = "resteasy"; public static final String RESTEASY_JSONB = "resteasy-jsonb"; public static final String SCHEDULER = "scheduler"; @@ -36,13 +43,15 @@ public final class FeatureBuildItem extends MultiBuildItem { public static final String SMALLRYE_OPENAPI = "smallrye-openapi"; public static final String SMALLRYE_OPENTRACING = "smallrye-opentracing"; public static final String SMALLRYE_REACTIVE_MESSAGING = "smallrye-reactive-messaging"; - public static final String SMALLRYE_REACTIVE_MESSAGING_KAFKA = "smallrye-reactive-messaging"; + public static final String SMALLRYE_REACTIVE_MESSAGING_KAFKA = "smallrye-reactive-messaging-kafka"; public static final String SMALLRYE_REACTIVE_STREAMS_OPERATORS = "smallrye-reactive-streams-operators"; public static final String SMALLRYE_REACTIVE_TYPE_CONVERTERS = "smallrye-reactive-type-converters"; public static final String SMALLRYE_REST_CLIENT = "smallrye-rest-client"; public static final String SPRING_DI = "spring-di"; + public static final String SWAGGER_UI = "swagger-ui"; public static final String UNDERTOW_WEBSOCKETS = "undertow-websockets"; public static final String VERTX = "vertx"; + public static final String VERTX_WEB = "vertx-web"; private final String info; @@ -54,4 +63,4 @@ public String getInfo() { return info; } -} \ No newline at end of file +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/GeneratedClassBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/GeneratedClassBuildItem.java index c2a6d84d96f32..09c5df651a24f 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/GeneratedClassBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/GeneratedClassBuildItem.java @@ -16,7 +16,7 @@ package io.quarkus.deployment.builditem; -import org.jboss.builder.item.MultiBuildItem; +import io.quarkus.builder.item.MultiBuildItem; public final class GeneratedClassBuildItem extends MultiBuildItem { final boolean applicationClass; diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/GeneratedResourceBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/GeneratedResourceBuildItem.java index 24cb02cb9bf70..c4d10261c859d 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/GeneratedResourceBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/GeneratedResourceBuildItem.java @@ -16,7 +16,7 @@ package io.quarkus.deployment.builditem; -import org.jboss.builder.item.MultiBuildItem; +import io.quarkus.builder.item.MultiBuildItem; public final class GeneratedResourceBuildItem extends MultiBuildItem { final String name; diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/HotDeploymentConfigFileBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/HotDeploymentConfigFileBuildItem.java index 65548ee7679cf..f4775f8ba029e 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/HotDeploymentConfigFileBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/HotDeploymentConfigFileBuildItem.java @@ -16,7 +16,7 @@ package io.quarkus.deployment.builditem; -import org.jboss.builder.item.MultiBuildItem; +import io.quarkus.builder.item.MultiBuildItem; /** * A configuration file that if modified should result in a hot redeployment when in diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/JavaLibraryPathAdditionalPathBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/JavaLibraryPathAdditionalPathBuildItem.java index 4e796ca142fe1..af2d4bd6fa548 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/JavaLibraryPathAdditionalPathBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/JavaLibraryPathAdditionalPathBuildItem.java @@ -1,6 +1,6 @@ package io.quarkus.deployment.builditem; -import org.jboss.builder.item.MultiBuildItem; +import io.quarkus.builder.item.MultiBuildItem; public final class JavaLibraryPathAdditionalPathBuildItem extends MultiBuildItem { diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/LaunchModeBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/LaunchModeBuildItem.java index ed97c28d64192..309c36d0185ac 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/LaunchModeBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/LaunchModeBuildItem.java @@ -1,7 +1,6 @@ package io.quarkus.deployment.builditem; -import org.jboss.builder.item.SimpleBuildItem; - +import io.quarkus.builder.item.SimpleBuildItem; import io.quarkus.runtime.LaunchMode; /** diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/LogCategoryBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/LogCategoryBuildItem.java index ebfe047a71583..3d429f51a2bd6 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/LogCategoryBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/LogCategoryBuildItem.java @@ -2,9 +2,10 @@ import java.util.logging.Level; -import org.jboss.builder.item.MultiBuildItem; import org.wildfly.common.Assert; +import io.quarkus.builder.item.MultiBuildItem; + /** * Establish the default log level of a log category. */ diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/MainBytecodeRecorderBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/MainBytecodeRecorderBuildItem.java index ce98040efe694..b13ad5ed99a58 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/MainBytecodeRecorderBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/MainBytecodeRecorderBuildItem.java @@ -16,8 +16,7 @@ package io.quarkus.deployment.builditem; -import org.jboss.builder.item.MultiBuildItem; - +import io.quarkus.builder.item.MultiBuildItem; import io.quarkus.deployment.recording.BytecodeRecorderImpl; public final class MainBytecodeRecorderBuildItem extends MultiBuildItem { diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/MainClassBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/MainClassBuildItem.java index dc706561b8162..3d160267c98a6 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/MainClassBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/MainClassBuildItem.java @@ -16,7 +16,7 @@ package io.quarkus.deployment.builditem; -import org.jboss.builder.item.SimpleBuildItem; +import io.quarkus.builder.item.SimpleBuildItem; public final class MainClassBuildItem extends SimpleBuildItem { diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/ObjectSubstitutionBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ObjectSubstitutionBuildItem.java index 5d79f71b6139c..ef465c6f3a84a 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/ObjectSubstitutionBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ObjectSubstitutionBuildItem.java @@ -1,7 +1,6 @@ package io.quarkus.deployment.builditem; -import org.jboss.builder.item.MultiBuildItem; - +import io.quarkus.builder.item.MultiBuildItem; import io.quarkus.runtime.ObjectSubstitution; /** diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/ProxyUnwrapperBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ProxyUnwrapperBuildItem.java index ef0a7487450da..b66e6dfc9c593 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/ProxyUnwrapperBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ProxyUnwrapperBuildItem.java @@ -18,7 +18,7 @@ import java.util.function.Function; -import org.jboss.builder.item.MultiBuildItem; +import io.quarkus.builder.item.MultiBuildItem; /** * A build item that can be used to unwrap CDI or other proxies 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 new file mode 100644 index 0000000000000..83fe0e8ad0fc1 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/RunTimeConfigurationBuildItem.java @@ -0,0 +1,19 @@ +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/RunTimeConfigurationDefaultBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/RunTimeConfigurationDefaultBuildItem.java index a0d58fbe20435..55aa3d761ed39 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/RunTimeConfigurationDefaultBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/RunTimeConfigurationDefaultBuildItem.java @@ -1,8 +1,8 @@ package io.quarkus.deployment.builditem; -import org.jboss.builder.item.MultiBuildItem; import org.wildfly.common.Assert; +import io.quarkus.builder.item.MultiBuildItem; import io.quarkus.runtime.annotations.ConfigItem; /** diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/RunTimeConfigurationSourceBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/RunTimeConfigurationSourceBuildItem.java index ef85d698e3228..afa6885c1b6c4 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/RunTimeConfigurationSourceBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/RunTimeConfigurationSourceBuildItem.java @@ -2,9 +2,10 @@ import java.util.OptionalInt; -import org.jboss.builder.item.MultiBuildItem; import org.wildfly.common.Assert; +import io.quarkus.builder.item.MultiBuildItem; + /** * Define an additional configuration source which is used at run time. */ diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/ServiceStartBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ServiceStartBuildItem.java index b7e0ceed9d9c9..d48716a627432 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/ServiceStartBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ServiceStartBuildItem.java @@ -16,8 +16,7 @@ package io.quarkus.deployment.builditem; -import org.jboss.builder.item.MultiBuildItem; - +import io.quarkus.builder.item.MultiBuildItem; import io.quarkus.runtime.StartupEvent; /** diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/ShutdownContextBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ShutdownContextBuildItem.java index 12718bcfb1d57..4481c86ab2327 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/ShutdownContextBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/ShutdownContextBuildItem.java @@ -16,8 +16,7 @@ package io.quarkus.deployment.builditem; -import org.jboss.builder.item.SimpleBuildItem; - +import io.quarkus.builder.item.SimpleBuildItem; import io.quarkus.deployment.recording.BytecodeRecorderImpl; import io.quarkus.runtime.ShutdownContext; diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/SslNativeConfigBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/SslNativeConfigBuildItem.java index f10d438ad4a94..7373929fa7f00 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/SslNativeConfigBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/SslNativeConfigBuildItem.java @@ -18,7 +18,7 @@ import java.util.Optional; -import org.jboss.builder.item.SimpleBuildItem; +import io.quarkus.builder.item.SimpleBuildItem; public final class SslNativeConfigBuildItem extends SimpleBuildItem { diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/SslTrustStoreSystemPropertyBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/SslTrustStoreSystemPropertyBuildItem.java index 11c6b95eaf4ae..532e570a8c849 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/SslTrustStoreSystemPropertyBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/SslTrustStoreSystemPropertyBuildItem.java @@ -1,6 +1,6 @@ package io.quarkus.deployment.builditem; -import org.jboss.builder.item.SimpleBuildItem; +import io.quarkus.builder.item.SimpleBuildItem; public final class SslTrustStoreSystemPropertyBuildItem extends SimpleBuildItem { diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/StaticBytecodeRecorderBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/StaticBytecodeRecorderBuildItem.java index e9494b7b45a17..76dc7b8ef2e46 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/StaticBytecodeRecorderBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/StaticBytecodeRecorderBuildItem.java @@ -16,8 +16,7 @@ package io.quarkus.deployment.builditem; -import org.jboss.builder.item.MultiBuildItem; - +import io.quarkus.builder.item.MultiBuildItem; import io.quarkus.deployment.recording.BytecodeRecorderImpl; public final class StaticBytecodeRecorderBuildItem extends MultiBuildItem { diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/SystemPropertyBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/SystemPropertyBuildItem.java index 01467b5f3a4bc..cf82c979d1126 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/SystemPropertyBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/SystemPropertyBuildItem.java @@ -16,7 +16,7 @@ package io.quarkus.deployment.builditem; -import org.jboss.builder.item.MultiBuildItem; +import io.quarkus.builder.item.MultiBuildItem; /** * Represents a system property that will be set immediately on application startup. diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/TestAnnotationBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/TestAnnotationBuildItem.java new file mode 100644 index 0000000000000..65773ed92f2fb --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/TestAnnotationBuildItem.java @@ -0,0 +1,20 @@ +package io.quarkus.deployment.builditem; + +import io.quarkus.builder.item.MultiBuildItem; + +/** + * This is an optional build item that allows us to track annotations that will define test classes + * It is only available during tests + */ +public final class TestAnnotationBuildItem extends MultiBuildItem { + + private final String annotationClassName; + + public TestAnnotationBuildItem(String annotationClassName) { + this.annotationClassName = annotationClassName; + } + + public String getAnnotationClassName() { + return annotationClassName; + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/TestClassPredicateBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/TestClassPredicateBuildItem.java new file mode 100644 index 0000000000000..f918eec5b9c8d --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/TestClassPredicateBuildItem.java @@ -0,0 +1,23 @@ +package io.quarkus.deployment.builditem; + +import java.util.function.Predicate; + +import io.quarkus.builder.item.SimpleBuildItem; + +/** + * This is an optional build item that allows extensions to distinguish test classes from application classes. It is only + * available during tests. + */ +public final class TestClassPredicateBuildItem extends SimpleBuildItem { + + private final Predicate predicate; + + public TestClassPredicateBuildItem(Predicate predicate) { + this.predicate = predicate; + } + + public Predicate getPredicate() { + return predicate; + } + +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/WiringClassBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/WiringClassBuildItem.java index 7083d5447a005..b31bad26fb068 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/WiringClassBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/WiringClassBuildItem.java @@ -16,7 +16,7 @@ package io.quarkus.deployment.builditem; -import org.jboss.builder.item.MultiBuildItem; +import io.quarkus.builder.item.MultiBuildItem; /** * A generated wiring class diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/ReflectiveClassBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/ReflectiveClassBuildItem.java index cdce684d3aea0..b39642cd8044a 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/ReflectiveClassBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/ReflectiveClassBuildItem.java @@ -16,11 +16,13 @@ package io.quarkus.deployment.builditem.substrate; +import static java.util.Arrays.stream; + import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import org.jboss.builder.item.MultiBuildItem; +import io.quarkus.builder.item.MultiBuildItem; /** * Used to register a class for reflection in substrate @@ -31,12 +33,18 @@ public final class ReflectiveClassBuildItem extends MultiBuildItem { private final boolean methods; private final boolean fields; private final boolean constructors; + private final boolean finalFieldsWritable; public ReflectiveClassBuildItem(boolean methods, boolean fields, Class... className) { this(true, methods, fields, className); } public ReflectiveClassBuildItem(boolean constructors, boolean methods, boolean fields, Class... className) { + this(constructors, methods, fields, false, className); + } + + private ReflectiveClassBuildItem(boolean constructors, boolean methods, boolean fields, boolean finalFieldsWritable, + Class... className) { List names = new ArrayList<>(); for (Class i : className) { if (i == null) { @@ -48,14 +56,19 @@ public ReflectiveClassBuildItem(boolean constructors, boolean methods, boolean f this.methods = methods; this.fields = fields; this.constructors = constructors; + this.finalFieldsWritable = finalFieldsWritable; } public ReflectiveClassBuildItem(boolean methods, boolean fields, String... className) { this(true, methods, fields, className); - } public ReflectiveClassBuildItem(boolean constructors, boolean methods, boolean fields, String... className) { + this(constructors, methods, fields, false, className); + } + + private ReflectiveClassBuildItem(boolean constructors, boolean methods, boolean fields, boolean finalFieldsWritable, + String... className) { for (String i : className) { if (i == null) { throw new NullPointerException(); @@ -65,6 +78,7 @@ public ReflectiveClassBuildItem(boolean constructors, boolean methods, boolean f this.methods = methods; this.fields = fields; this.constructors = constructors; + this.finalFieldsWritable = finalFieldsWritable; } public List getClassNames() { @@ -82,4 +96,67 @@ public boolean isFields() { public boolean isConstructors() { return constructors; } + + public boolean areFinalFieldsWritable() { + return finalFieldsWritable; + } + + public static Builder builder(Class... className) { + String[] classNameStrings = stream(className) + .map(aClass -> { + if (aClass == null) { + throw new NullPointerException(); + } + return aClass.getName(); + }) + .toArray(String[]::new); + + return new Builder() + .className(classNameStrings); + } + + public static Builder builder(String... className) { + return new Builder() + .className(className); + } + + public static class Builder { + private String[] className; + private boolean constructors = true; + private boolean methods; + private boolean fields; + private boolean finalFieldsWritable; + + private Builder() { + } + + public Builder className(String[] className) { + this.className = className; + return this; + } + + public Builder constructors(boolean constructors) { + this.constructors = constructors; + return this; + } + + public Builder methods(boolean methods) { + this.methods = methods; + return this; + } + + public Builder fields(boolean fields) { + this.fields = fields; + return this; + } + + public Builder finalFieldsWritable(boolean finalFieldsWritable) { + this.finalFieldsWritable = finalFieldsWritable; + return this; + } + + public ReflectiveClassBuildItem build() { + return new ReflectiveClassBuildItem(constructors, methods, fields, finalFieldsWritable, className); + } + } } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/ReflectiveFieldBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/ReflectiveFieldBuildItem.java index 5ffe45cce27a7..2ebb0584e1a3b 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/ReflectiveFieldBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/ReflectiveFieldBuildItem.java @@ -18,9 +18,10 @@ import java.lang.reflect.Field; -import org.jboss.builder.item.MultiBuildItem; import org.jboss.jandex.FieldInfo; +import io.quarkus.builder.item.MultiBuildItem; + public final class ReflectiveFieldBuildItem extends MultiBuildItem { final String declaringClass; diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/ReflectiveHierarchyBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/ReflectiveHierarchyBuildItem.java index 1d3efb7577d12..678be658117a0 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/ReflectiveHierarchyBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/ReflectiveHierarchyBuildItem.java @@ -16,9 +16,10 @@ package io.quarkus.deployment.builditem.substrate; -import org.jboss.builder.item.MultiBuildItem; import org.jboss.jandex.Type; +import io.quarkus.builder.item.MultiBuildItem; + /** * Attempts to register a complete type hierarchy for reflection. *

diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/ReflectiveMethodBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/ReflectiveMethodBuildItem.java index 9c9bae677e329..9ad97f23c60f6 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/ReflectiveMethodBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/ReflectiveMethodBuildItem.java @@ -20,9 +20,10 @@ import java.util.Arrays; import java.util.Objects; -import org.jboss.builder.item.MultiBuildItem; import org.jboss.jandex.MethodInfo; +import io.quarkus.builder.item.MultiBuildItem; + public final class ReflectiveMethodBuildItem extends MultiBuildItem { final String declaringClass; diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/RuntimeInitializedClassBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/RuntimeInitializedClassBuildItem.java index e8ed8324df9b3..8785ab3acb698 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/RuntimeInitializedClassBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/RuntimeInitializedClassBuildItem.java @@ -16,7 +16,7 @@ package io.quarkus.deployment.builditem.substrate; -import org.jboss.builder.item.MultiBuildItem; +import io.quarkus.builder.item.MultiBuildItem; public final class RuntimeInitializedClassBuildItem extends MultiBuildItem { diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/RuntimeReinitializedClassBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/RuntimeReinitializedClassBuildItem.java index 22dfdb6b34d39..e60e8967174b5 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/RuntimeReinitializedClassBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/RuntimeReinitializedClassBuildItem.java @@ -16,7 +16,7 @@ package io.quarkus.deployment.builditem.substrate; -import org.jboss.builder.item.MultiBuildItem; +import io.quarkus.builder.item.MultiBuildItem; /** * A class that will be reinitialized at runtime by Substrate. This will result in the static diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/ServiceProviderBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/ServiceProviderBuildItem.java index 67f1f1627dc6a..e186d350f935d 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/ServiceProviderBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/ServiceProviderBuildItem.java @@ -20,7 +20,7 @@ import java.util.List; import java.util.Objects; -import org.jboss.builder.item.MultiBuildItem; +import io.quarkus.builder.item.MultiBuildItem; /** * Represents a Service Provider registration. @@ -35,8 +35,12 @@ public final class ServiceProviderBuildItem extends MultiBuildItem { private final List providers; public ServiceProviderBuildItem(String serviceInterfaceClassName, String... providerClassNames) { - serviceInterface = Objects.requireNonNull(serviceInterfaceClassName, "The service interface must not be `null`"); - providers = Arrays.asList(Objects.requireNonNull(providerClassNames)); + this(serviceInterfaceClassName, Arrays.asList(providerClassNames)); + } + + public ServiceProviderBuildItem(String serviceInterfaceClassName, List providers) { + this.serviceInterface = Objects.requireNonNull(serviceInterfaceClassName, "The service interface must not be `null`"); + this.providers = providers; // Validation if (serviceInterface.length() == 0) { @@ -45,7 +49,7 @@ public ServiceProviderBuildItem(String serviceInterfaceClassName, String... prov providers.forEach(s -> { if (s == null || s.length() == 0) { - throw new IllegalArgumentException("The provider class name cannot be blank"); + throw new IllegalArgumentException("The provider class name cannot be null or blank"); } }); } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/SubstrateConfigBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/SubstrateConfigBuildItem.java index ba58ff60a2985..6f042d3af6c47 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/SubstrateConfigBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/SubstrateConfigBuildItem.java @@ -24,7 +24,7 @@ import java.util.Map; import java.util.Set; -import org.jboss.builder.item.MultiBuildItem; +import io.quarkus.builder.item.MultiBuildItem; public final class SubstrateConfigBuildItem extends MultiBuildItem { diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/SubstrateOutputBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/SubstrateOutputBuildItem.java index 06ea474e6b183..347391eca5b3a 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/SubstrateOutputBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/SubstrateOutputBuildItem.java @@ -16,7 +16,7 @@ package io.quarkus.deployment.builditem.substrate; -import org.jboss.builder.item.MultiBuildItem; +import io.quarkus.builder.item.MultiBuildItem; public final class SubstrateOutputBuildItem extends MultiBuildItem { diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/SubstrateProxyDefinitionBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/SubstrateProxyDefinitionBuildItem.java index 22d8e8ce51da4..840e4f6a405a0 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/SubstrateProxyDefinitionBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/SubstrateProxyDefinitionBuildItem.java @@ -20,7 +20,7 @@ import java.util.Arrays; import java.util.List; -import org.jboss.builder.item.MultiBuildItem; +import io.quarkus.builder.item.MultiBuildItem; /** * A build item that represents a {@link java.lang.reflect.Proxy} definition diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/SubstrateResourceBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/SubstrateResourceBuildItem.java index 39c47042f0fdb..a8cd3d425f61e 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/SubstrateResourceBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/SubstrateResourceBuildItem.java @@ -20,7 +20,7 @@ import java.util.Arrays; import java.util.List; -import org.jboss.builder.item.MultiBuildItem; +import io.quarkus.builder.item.MultiBuildItem; /** * A build item that indicates that a static resource should be included in the native image diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/SubstrateResourceBundleBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/SubstrateResourceBundleBuildItem.java index ac561516f73df..45c3720119e09 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/SubstrateResourceBundleBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/SubstrateResourceBundleBuildItem.java @@ -16,7 +16,7 @@ package io.quarkus.deployment.builditem.substrate; -import org.jboss.builder.item.MultiBuildItem; +import io.quarkus.builder.item.MultiBuildItem; /** * Indicates that a resource bundle should be included in the native image diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/SubstrateSystemPropertyBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/SubstrateSystemPropertyBuildItem.java index c68f9690b6524..e7638d286375f 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/SubstrateSystemPropertyBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/substrate/SubstrateSystemPropertyBuildItem.java @@ -16,7 +16,7 @@ package io.quarkus.deployment.builditem.substrate; -import org.jboss.builder.item.MultiBuildItem; +import io.quarkus.builder.item.MultiBuildItem; /** * A system property that will be set at native image build time 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 index 170b2f578b03c..ae5bbb3dc4265 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/BooleanConfigType.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/BooleanConfigType.java @@ -4,9 +4,12 @@ import java.util.Optional; 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.ExpandingConfigSource; import io.quarkus.runtime.configuration.NameIterator; import io.smallrye.config.SmallRyeConfig; @@ -24,21 +27,22 @@ public BooleanConfigType(final String containingName, final CompoundConfigType c this.defaultValue = defaultValue; } - public void acceptConfigurationValue(final NameIterator name, final SmallRyeConfig config) { + 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, config); + 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 config) { + 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, config); + container.generateAcceptConfigurationValueIntoLeaf(body, this, name, cache, config); // the iterator is not used after this point // if (isConsumeSegment()) body.invokeVirtualMethod(NI_NEXT_METHOD, name); } @@ -47,7 +51,7 @@ public void acceptConfigurationValueIntoGroup(final Object enclosing, final Fiel final SmallRyeConfig config) { try { field.setBoolean(enclosing, config.getOptionalValue(name.toString(), Boolean.class) - .orElse(config.convert(defaultValue, Boolean.class)).booleanValue()); + .orElse(Boolean.FALSE).booleanValue()); } catch (IllegalAccessException e) { throw toError(e); } @@ -55,7 +59,7 @@ public void acceptConfigurationValueIntoGroup(final Object enclosing, final Fiel public void generateAcceptConfigurationValueIntoGroup(final BytecodeCreator body, final ResultHandle enclosing, final MethodDescriptor setter, final ResultHandle name, final ResultHandle config) { - // config.getOptionalValue(name.toString(), Boolean.class).orElse(config.convert(defaultValue, Boolean.class)).booleanValue() + // config.getOptionalValue(name.toString(), Boolean.class).orElse(Boolean.FALSE).booleanValue() final ResultHandle optionalValue = body.checkCast(body.invokeVirtualMethod( SRC_GET_OPT_METHOD, config, @@ -63,7 +67,7 @@ public void generateAcceptConfigurationValueIntoGroup(final BytecodeCreator body OBJ_TO_STRING_METHOD, name), body.loadClass(Boolean.class)), Optional.class); - final ResultHandle convertedDefault = getConvertedDefault(body, config); + final ResultHandle convertedDefault = body.readStaticField(FieldDescriptor.of(Boolean.class, "FALSE", Boolean.class)); final ResultHandle defaultedValue = body.checkCast(body.invokeVirtualMethod( OPT_OR_ELSE_METHOD, optionalValue, @@ -72,36 +76,46 @@ public void generateAcceptConfigurationValueIntoGroup(final BytecodeCreator body body.invokeStaticMethod(setter, enclosing, booleanValue); } + public String getDefaultValueString() { + return defaultValue; + } + public Class getItemClass() { return boolean.class; } - void getDefaultValueIntoEnclosingGroup(final Object enclosing, final SmallRyeConfig config, final Field field) { + void getDefaultValueIntoEnclosingGroup(final Object enclosing, final ExpandingConfigSource.Cache cache, + final SmallRyeConfig config, final Field field) { try { - field.setBoolean(enclosing, config.convert(defaultValue, Boolean.class).booleanValue()); + field.setBoolean(enclosing, + config.convert(ExpandingConfigSource.expandValue(defaultValue, cache), Boolean.class).booleanValue()); } catch (IllegalAccessException e) { throw toError(e); } } void generateGetDefaultValueIntoEnclosingGroup(final BytecodeCreator body, final ResultHandle enclosing, - final MethodDescriptor setter, final ResultHandle config) { + final MethodDescriptor setter, final ResultHandle cache, final ResultHandle config) { final ResultHandle value = body.invokeVirtualMethod(BOOL_VALUE_METHOD, - body.checkCast(getConvertedDefault(body, config), Boolean.class)); + body.checkCast(getConvertedDefault(body, cache, config), Boolean.class)); body.invokeStaticMethod(setter, enclosing, value); } public ResultHandle writeInitialization(final BytecodeCreator body, final AccessorFinder accessorFinder, - final ResultHandle smallRyeConfig) { + final ResultHandle cache, final ResultHandle smallRyeConfig) { return body.invokeVirtualMethod(BOOL_VALUE_METHOD, - body.checkCast(getConvertedDefault(body, smallRyeConfig), Boolean.class)); + body.checkCast(getConvertedDefault(body, cache, smallRyeConfig), Boolean.class)); } - private ResultHandle getConvertedDefault(final BytecodeCreator body, final ResultHandle config) { + private ResultHandle getConvertedDefault(final BytecodeCreator body, final ResultHandle cache, final ResultHandle config) { return body.checkCast(body.invokeVirtualMethod( SRC_CONVERT_METHOD, config, - body.load(defaultValue), + cache == null ? body.load(defaultValue) + : body.invokeStaticMethod( + ConfigurationSetup.ECS_EXPAND_VALUE, + body.load(defaultValue), + cache), body.loadClass(Boolean.class)), Boolean.class); } } 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 index 6b75c6e6d5815..4cc3734a8d0d4 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/CompoundConfigType.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/CompoundConfigType.java @@ -2,6 +2,7 @@ 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; @@ -17,14 +18,17 @@ public abstract class CompoundConfigType extends ConfigType { * 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, SmallRyeConfig config, Object self, String childName); + abstract Object getChildObject(NameIterator name, final ExpandingConfigSource.Cache cache, SmallRyeConfig config, + Object self, String childName); - abstract ResultHandle generateGetChildObject(BytecodeCreator body, ResultHandle name, ResultHandle config, + abstract ResultHandle generateGetChildObject(BytecodeCreator body, ResultHandle name, final ResultHandle cache, + ResultHandle config, ResultHandle self, String childName); /** @@ -44,15 +48,18 @@ abstract void generateSetChildObject(BytecodeCreator body, ResultHandle name, Re * 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, SmallRyeConfig config); + abstract Object getOrCreate(NameIterator name, final ExpandingConfigSource.Cache cache, SmallRyeConfig config); - abstract ResultHandle generateGetOrCreate(BytecodeCreator body, ResultHandle name, ResultHandle config); + abstract ResultHandle generateGetOrCreate(BytecodeCreator body, ResultHandle name, final ResultHandle cache, + ResultHandle config); - abstract void acceptConfigurationValueIntoLeaf(LeafConfigType leafType, NameIterator name, SmallRyeConfig config); + abstract void acceptConfigurationValueIntoLeaf(LeafConfigType leafType, NameIterator name, + final ExpandingConfigSource.Cache cache, SmallRyeConfig config); abstract void generateAcceptConfigurationValueIntoLeaf(BytecodeCreator body, LeafConfigType leafType, ResultHandle name, - ResultHandle config); + 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 index beaece45d99df..88bc69dc3d403 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/ConfigDefinition.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/ConfigDefinition.java @@ -1,7 +1,5 @@ package io.quarkus.deployment.configuration; -import static io.quarkus.deployment.steps.ConfigurationSetup.CONFIG_ROOT; -import static io.quarkus.deployment.steps.ConfigurationSetup.CONFIG_ROOT_FIELD; import static io.quarkus.deployment.util.ReflectUtil.rawTypeOf; import static io.quarkus.deployment.util.ReflectUtil.rawTypeOfParameter; import static io.quarkus.deployment.util.ReflectUtil.typeOfParameter; @@ -27,8 +25,10 @@ import java.util.OptionalDouble; import java.util.OptionalInt; import java.util.OptionalLong; +import java.util.Set; import java.util.TreeMap; +import org.jboss.logging.Logger; import org.objectweb.asm.Opcodes; import org.wildfly.common.Assert; @@ -36,6 +36,7 @@ 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; @@ -44,6 +45,7 @@ import io.quarkus.runtime.annotations.ConfigItem; import io.quarkus.runtime.annotations.ConfigPhase; import io.quarkus.runtime.annotations.ConfigRoot; +import io.quarkus.runtime.configuration.ExpandingConfigSource; import io.quarkus.runtime.configuration.NameIterator; import io.smallrye.config.SmallRyeConfig; @@ -52,6 +54,8 @@ * 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 final TreeMap rootObjectsByContainingName = new TreeMap<>(); @@ -59,38 +63,46 @@ public class ConfigDefinition extends CompoundConfigType { private final ConfigPatternMap leafPatterns = new ConfigPatternMap<>(); private final IdentityHashMap realizedInstances = new IdentityHashMap<>(); private final TreeMap rootTypesByContainingName = new TreeMap<>(); - private final TreeMap rootTypesByKey = new TreeMap<>(); + private final FieldDescriptor rootField; + private final TreeMap loadedProperties = new TreeMap<>(); - public ConfigDefinition() { + public ConfigDefinition(final FieldDescriptor rootField) { super(null, null, false); + Assert.checkNotNullParam("rootField", rootField); + this.rootField = rootField; } - void acceptConfigurationValueIntoLeaf(final LeafConfigType leafType, final NameIterator name, final SmallRyeConfig config) { + 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 config) { + 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 SmallRyeConfig config, final Object self, final String childName) { + 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 config, + 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 SmallRyeConfig config) { + TreeMap getOrCreate(final NameIterator name, final ExpandingConfigSource.Cache cache, + final SmallRyeConfig config) { return rootObjectsByContainingName; } - ResultHandle generateGetOrCreate(final BytecodeCreator body, final ResultHandle name, final ResultHandle config) { - return body.readStaticField(CONFIG_ROOT_FIELD); + 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) { @@ -110,17 +122,18 @@ void generateSetChildObject(final BytecodeCreator body, final ResultHandle name, throw Assert.unsupported(); } - void getDefaultValueIntoEnclosingGroup(final Object enclosing, final SmallRyeConfig config, final Field field) { + 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 config) { + final MethodDescriptor setter, final ResultHandle cache, final ResultHandle config) { throw Assert.unsupported(); } public ResultHandle writeInitialization(final BytecodeCreator body, final AccessorFinder accessorFinder, - final ResultHandle smallRyeConfig) { + final ResultHandle cache, final ResultHandle smallRyeConfig) { throw Assert.unsupported(); } @@ -128,11 +141,12 @@ public void load() { loadFrom(leafPatterns); } - public void initialize(SmallRyeConfig config) { - for (Map.Entry entry : rootTypesByKey.entrySet()) { - final String key = entry.getKey(); + public void initialize(final SmallRyeConfig config, final ExpandingConfigSource.Cache cache) { + for (Map.Entry entry : rootTypesByContainingName.entrySet()) { final RootInfo rootInfo = entry.getValue(); - rootInfo.getRootType().getOrCreate(new NameIterator(key, true), config); + // 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); } } @@ -161,10 +175,10 @@ public void registerConfigRoot(Class configRoot) { 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(CONFIG_ROOT, containingName, Object.class), configPhase); + final RootInfo rootInfo = new RootInfo(configRoot, configGroup, FieldDescriptor + .of(DescriptorUtils.getTypeStringFromDescriptorFormat(rootField.getType()), containingName, Object.class), + configPhase); rootTypesByContainingName.put(containingName, rootInfo); - rootTypesByKey.put(rootName, rootInfo); } private GroupConfigType processConfigGroup(final String containingName, final CompoundConfigType container, @@ -313,15 +327,15 @@ private static IllegalArgumentException reportError(AnnotatedElement e, String m } public void generateConfigRootClass(ClassOutput classOutput, AccessorFinder accessorFinder) { - try (ClassCreator cc = ClassCreator.builder().classOutput(classOutput).className(CONFIG_ROOT).superClass(Object.class) + 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); - // early publish of self - ctor.writeStaticField(CONFIG_ROOT_FIELD, self); + final ResultHandle cache = ctor.newInstance(ECS_CACHE_CTOR); // initialize all fields to defaults for (RootInfo value : rootTypesByContainingName.values()) { if (value.getConfigPhase().isAvailableAtRun()) { @@ -330,7 +344,7 @@ public void generateConfigRootClass(ClassOutput classOutput, AccessorFinder acce final FieldDescriptor fieldDescriptor = cc.getFieldCreator(containingName, Object.class) .setModifiers(Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL).getFieldDescriptor(); ctor.writeInstanceField(fieldDescriptor, self, - rootType.writeInitialization(ctor, accessorFinder, config)); + rootType.writeInitialization(ctor, accessorFinder, cache, config)); } } ctor.returnValue(null); @@ -338,27 +352,34 @@ public void generateConfigRootClass(ClassOutput classOutput, AccessorFinder acce } } - public void loadConfiguration(SmallRyeConfig config) { - initialize(config); - for (String propertyName : config.getPropertyNames()) { + 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() && name.nextSegmentEquals("quarkus")) { - name.next(); - final LeafConfigType leafType = leafPatterns.match(name); - if (leafType != null) { - name.goToEnd(); - leafType.acceptConfigurationValue(name, config); + if (name.hasNext()) { + if (name.nextSegmentEquals("quarkus")) { + name.next(); + for (ConfigDefinition definition : definitions) { + final LeafConfigType leafType = definition.leafPatterns.match(name); + if (leafType != null) { + name.goToEnd(); + leafType.acceptConfigurationValue(name, cache, config); + final String nameString = name.toString(); + definition.loadedProperties.put(nameString, config.getValue(nameString, String.class)); + continue outer; + } + } + log.warnf("Unrecognized configuration key \"%s\" provided", propertyName); } else { - // TODO: log.warnf("Unknown configuration key \"%s\" provided", propertyName); + // non-Quarkus value; capture it in the unmatched map for storage as a default value + unmatched.add(propertyName); } } } - // now, ensure all roots are instantiated - for (Map.Entry entry : rootTypesByContainingName.entrySet()) { - if (entry.getValue().getConfigPhase().isAvailableAtRun()) { - entry.getValue().getRootType().getOrCreate(new NameIterator(entry.getKey(), true), config); - } - } } public ConfigPatternMap getLeafPatterns() { @@ -369,6 +390,10 @@ public ConfigDefinition getConfigDefinition() { return this; } + public TreeMap getLoadedProperties() { + return loadedProperties; + } + private void loadFrom(ConfigPatternMap map) { final LeafConfigType matched = map.getMatched(); if (matched != null) { @@ -379,13 +404,6 @@ private void loadFrom(ConfigPatternMap map) { } } - public ConfigPhase getPhaseByKey(final String key) { - final RootInfo rootInfo = rootTypesByKey.get(key); - if (rootInfo == null) - throw new IllegalArgumentException("Unknown root key: " + key); - return rootInfo.getConfigPhase(); - } - public Object getRealizedInstance(final Class rootClass) { final Object obj = rootObjectsByClass.get(rootClass); if (obj == null) { 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 index 817acd353f75d..8fa6ffd54d115 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/ConfigType.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/ConfigType.java @@ -10,6 +10,7 @@ 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; @@ -42,6 +43,8 @@ public abstract class ConfigType { 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. @@ -114,18 +117,20 @@ protected static IllegalStateException notLoadedException() { /** * 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 SmallRyeConfig config, final Field field); + 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 config); + final MethodDescriptor setter, final ResultHandle cache, final ResultHandle config); public abstract ResultHandle writeInitialization(final BytecodeCreator body, final AccessorFinder accessorFinder, - final ResultHandle smallRyeConfig); + 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 new file mode 100644 index 0000000000000..a4b851c0d2b00 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/DefaultValuesConfigurationSource.java @@ -0,0 +1,41 @@ +package io.quarkus.deployment.configuration; + +import java.util.Collections; +import java.util.Map; + +import org.eclipse.microprofile.config.spi.ConfigSource; + +/** + * + */ +public class DefaultValuesConfigurationSource implements ConfigSource { + private final ConfigPatternMap leafs; + + public DefaultValuesConfigurationSource(final ConfigPatternMap leafs) { + this.leafs = leafs; + } + + public Map getProperties() { + return Collections.emptyMap(); + } + + public String getValue(final String propertyName) { + final LeafConfigType match = leafs.match(propertyName); + if (match == null) { + return null; + } + final String defaultValueString = match.getDefaultValueString(); + if (defaultValueString == null || defaultValueString.isEmpty()) { + return null; + } + return defaultValueString; + } + + public String getName() { + return "default values"; + } + + public int getOrdinal() { + return Integer.MIN_VALUE; + } +} 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 index bf65c8110b49c..1858f9ca7b97c 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/DoubleConfigType.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/DoubleConfigType.java @@ -6,9 +6,11 @@ import org.wildfly.common.Assert; import io.quarkus.deployment.AccessorFinder; +import io.quarkus.deployment.steps.ConfigurationSetup; 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; @@ -30,21 +32,22 @@ public DoubleConfigType(final String containingName, final CompoundConfigType co this.defaultValue = defaultValue; } - public void acceptConfigurationValue(final NameIterator name, final SmallRyeConfig config) { + 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, config); + 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 config) { + 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, config); + container.generateAcceptConfigurationValueIntoLeaf(body, this, name, cache, config); // the iterator is not used after this point // if (isConsumeSegment()) body.invokeVirtualMethod(NI_NEXT_METHOD, name); } @@ -52,8 +55,7 @@ public void generateAcceptConfigurationValue(final BytecodeCreator body, final R public void acceptConfigurationValueIntoGroup(final Object enclosing, final Field field, final NameIterator name, final SmallRyeConfig config) { try { - field.setDouble(enclosing, config.getValue(name.toString(), OptionalDouble.class) - .orElse(config.convert(defaultValue, Double.class).doubleValue())); + field.setDouble(enclosing, config.getValue(name.toString(), OptionalDouble.class).orElse(0.0)); } catch (IllegalAccessException e) { throw toError(e); } @@ -61,7 +63,7 @@ public void acceptConfigurationValueIntoGroup(final Object enclosing, final Fiel public void generateAcceptConfigurationValueIntoGroup(final BytecodeCreator body, final ResultHandle enclosing, final MethodDescriptor setter, final ResultHandle name, final ResultHandle config) { - // config.getValue(name.toString(), OptionalDouble.class).orElse(config.convert(defaultValue, Double.class).doubleValue()) + // config.getValue(name.toString(), OptionalDouble.class).orElse(0.0) final ResultHandle optionalValue = body.checkCast(body.invokeVirtualMethod( SRC_GET_VALUE, config, @@ -69,43 +71,51 @@ public void generateAcceptConfigurationValueIntoGroup(final BytecodeCreator body OBJ_TO_STRING_METHOD, name), body.loadClass(OptionalDouble.class)), OptionalDouble.class); - final ResultHandle convertedDefault = getConvertedDefault(body, config); - final ResultHandle defaultedValue = body.checkCast(body.invokeVirtualMethod( + final ResultHandle doubleValue = body.invokeVirtualMethod( OPTDOUBLE_OR_ELSE_METHOD, optionalValue, - convertedDefault), Double.class); - final ResultHandle doubleValue = body.invokeVirtualMethod(DOUBLE_VALUE_METHOD, defaultedValue); + body.load(0.0)); body.invokeStaticMethod(setter, enclosing, doubleValue); } + public String getDefaultValueString() { + return defaultValue; + } + public Class getItemClass() { return double.class; } - void getDefaultValueIntoEnclosingGroup(final Object enclosing, final SmallRyeConfig config, final Field field) { + void getDefaultValueIntoEnclosingGroup(final Object enclosing, final ExpandingConfigSource.Cache cache, + final SmallRyeConfig config, final Field field) { try { - field.setDouble(enclosing, config.convert(defaultValue, Double.class).doubleValue()); + field.setDouble(enclosing, config.convert( + ExpandingConfigSource.expandValue(defaultValue, cache), Double.class).doubleValue()); } catch (IllegalAccessException e) { throw toError(e); } } void generateGetDefaultValueIntoEnclosingGroup(final BytecodeCreator body, final ResultHandle enclosing, - final MethodDescriptor setter, final ResultHandle config) { + final MethodDescriptor setter, final ResultHandle cache, final ResultHandle config) { body.invokeStaticMethod(setter, enclosing, - body.invokeVirtualMethod(DOUBLE_VALUE_METHOD, getConvertedDefault(body, config))); + body.invokeVirtualMethod(DOUBLE_VALUE_METHOD, getConvertedDefault(body, cache, config))); } public ResultHandle writeInitialization(final BytecodeCreator body, final AccessorFinder accessorFinder, - final ResultHandle smallRyeConfig) { - return body.invokeVirtualMethod(DOUBLE_VALUE_METHOD, getConvertedDefault(body, smallRyeConfig)); + final ResultHandle cache, final ResultHandle smallRyeConfig) { + return body.invokeVirtualMethod(DOUBLE_VALUE_METHOD, getConvertedDefault(body, cache, smallRyeConfig)); } - private ResultHandle getConvertedDefault(final BytecodeCreator body, final ResultHandle config) { + private ResultHandle getConvertedDefault(final BytecodeCreator body, final ResultHandle cache, final ResultHandle config) { return body.checkCast(body.invokeVirtualMethod( SRC_CONVERT_METHOD, config, - body.load(defaultValue), + cache == null ? body.load(defaultValue) + : body.invokeStaticMethod( + ConfigurationSetup.ECS_EXPAND_VALUE, + body.load(defaultValue), + cache), body.loadClass(Double.class)), Double.class); } } 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 index efd7815ffa3d6..795c97b311d1c 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/FloatConfigType.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/FloatConfigType.java @@ -5,11 +5,13 @@ 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.ExpandingConfigSource; import io.quarkus.runtime.configuration.NameIterator; import io.smallrye.config.SmallRyeConfig; @@ -29,21 +31,22 @@ public FloatConfigType(final String containingName, final CompoundConfigType con this.defaultValue = defaultValue; } - public void acceptConfigurationValue(final NameIterator name, final SmallRyeConfig config) { + 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, config); + 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 config) { + 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, config); + container.generateAcceptConfigurationValueIntoLeaf(body, this, name, cache, config); // the iterator is not used after this point // if (isConsumeSegment()) body.invokeVirtualMethod(NI_NEXT_METHOD, name); } @@ -52,8 +55,7 @@ public void acceptConfigurationValueIntoGroup(final Object enclosing, final Fiel final SmallRyeConfig config) { try { final Float floatValue = config.getValue(name.toString(), Float.class); - final float f = floatValue != null ? floatValue.floatValue() - : config.convert(defaultValue, Float.class).floatValue(); + final float f = floatValue != null ? floatValue.floatValue() : 0f; field.setFloat(enclosing, f); } catch (IllegalAccessException e) { throw toError(e); @@ -63,7 +65,7 @@ public void acceptConfigurationValueIntoGroup(final Object enclosing, final Fiel public void generateAcceptConfigurationValueIntoGroup(final BytecodeCreator body, final ResultHandle enclosing, final MethodDescriptor setter, final ResultHandle name, final ResultHandle config) { // final Float floatValue = config.getValue(name.toString(), Float.class); - // final float f = floatValue != null ? floatValue.floatValue() : config.convert(defaultValue, Float.class).floatValue(); + // final float f = floatValue != null ? floatValue.floatValue() : 0f; final AssignableResultHandle result = body.createVariable(float.class); final ResultHandle floatValue = body.checkCast(body.invokeVirtualMethod( SRC_GET_VALUE, @@ -74,12 +76,7 @@ public void generateAcceptConfigurationValueIntoGroup(final BytecodeCreator body body.loadClass(Float.class)), Float.class); final BranchResult ifNull = body.ifNull(floatValue); final BytecodeCreator isNull = ifNull.trueBranch(); - isNull.assign(result, - isNull.checkCast(isNull.invokeVirtualMethod( - FLOAT_VALUE_METHOD, - floatValue, - getConvertedDefault(isNull, config)), - Float.class)); + isNull.assign(result, isNull.load(0f)); final BytecodeCreator isNotNull = ifNull.falseBranch(); isNotNull.assign(result, isNotNull.invokeVirtualMethod( @@ -88,34 +85,44 @@ public void generateAcceptConfigurationValueIntoGroup(final BytecodeCreator body body.invokeStaticMethod(setter, enclosing, result); } + public String getDefaultValueString() { + return defaultValue; + } + public Class getItemClass() { return float.class; } - void getDefaultValueIntoEnclosingGroup(final Object enclosing, final SmallRyeConfig config, final Field field) { + void getDefaultValueIntoEnclosingGroup(final Object enclosing, final ExpandingConfigSource.Cache cache, + final SmallRyeConfig config, final Field field) { try { - field.setFloat(enclosing, config.convert(defaultValue, Float.class).floatValue()); + field.setFloat(enclosing, + config.convert(ExpandingConfigSource.expandValue(defaultValue, cache), Float.class).floatValue()); } catch (IllegalAccessException e) { throw toError(e); } } void generateGetDefaultValueIntoEnclosingGroup(final BytecodeCreator body, final ResultHandle enclosing, - final MethodDescriptor setter, final ResultHandle config) { + final MethodDescriptor setter, final ResultHandle cache, final ResultHandle config) { body.invokeStaticMethod(setter, enclosing, - body.invokeVirtualMethod(FLOAT_VALUE_METHOD, getConvertedDefault(body, config))); + body.invokeVirtualMethod(FLOAT_VALUE_METHOD, getConvertedDefault(body, cache, config))); } public ResultHandle writeInitialization(final BytecodeCreator body, final AccessorFinder accessorFinder, - final ResultHandle smallRyeConfig) { - return body.invokeVirtualMethod(FLOAT_VALUE_METHOD, getConvertedDefault(body, smallRyeConfig)); + final ResultHandle cache, final ResultHandle smallRyeConfig) { + return body.invokeVirtualMethod(FLOAT_VALUE_METHOD, getConvertedDefault(body, cache, smallRyeConfig)); } - private ResultHandle getConvertedDefault(final BytecodeCreator body, final ResultHandle config) { + private ResultHandle getConvertedDefault(final BytecodeCreator body, final ResultHandle cache, final ResultHandle config) { return body.checkCast(body.invokeVirtualMethod( SRC_CONVERT_METHOD, config, - body.load(defaultValue), + cache == null ? body.load(defaultValue) + : body.invokeStaticMethod( + ConfigurationSetup.ECS_EXPAND_VALUE, + body.load(defaultValue), + cache), body.loadClass(Float.class)), Float.class); } } 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 index 608a1ec17a545..a26c35c3323c5 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/GroupConfigType.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/GroupConfigType.java @@ -4,6 +4,7 @@ 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; @@ -16,6 +17,7 @@ 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; @@ -85,14 +87,14 @@ public void load() throws ClassNotFoundException { } public ResultHandle writeInitialization(final BytecodeCreator body, final AccessorFinder accessorFinder, - final ResultHandle smallRyeConfig) { + 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, smallRyeConfig); + final ResultHandle value = fieldType.writeInitialization(body, accessorFinder, cache, smallRyeConfig); body.invokeStaticMethod(accessorFinder.getSetterFor(fieldDescriptor), instance, value); } return instance; @@ -119,7 +121,7 @@ private Field findField(final String name) { return fieldInfo.getField(); } - private Object create(final SmallRyeConfig config) { + private Object create(final ExpandingConfigSource.Cache cache, final SmallRyeConfig config) { Object self; try { self = constructor.newInstance(); @@ -128,43 +130,51 @@ private Object create(final SmallRyeConfig config) { } catch (IllegalAccessException e) { throw toError(e); } catch (InvocationTargetException e) { - throw new IllegalStateException(e.getCause()); + 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, config, findField(entry.getKey())); + entry.getValue().getDefaultValueIntoEnclosingGroup(self, cache, config, findField(entry.getKey())); } return self; } - private ResultHandle generateCreate(final BytecodeCreator body, final ResultHandle config) { + 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, config); + childType.generateGetDefaultValueIntoEnclosingGroup(body, self, setter, cache, config); } return self; } - Object getChildObject(final NameIterator name, final SmallRyeConfig config, final Object self, final String childName) { + 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, config, field); + childType.getDefaultValueIntoEnclosingGroup(self, cache, config, field); val = getFromField(field, self); } return val; } - ResultHandle generateGetChildObject(final BytecodeCreator body, final ResultHandle name, final ResultHandle config, + 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(), config); + childType.generateGetDefaultValueIntoEnclosingGroup(isNull, self, fieldInfo.getSetter(), cache, config); isNull.assign(val, isNull.invokeStaticMethod(fieldInfo.getGetter(), self)); } return val; @@ -178,17 +188,17 @@ private static Object getFromField(Field field, Object obj) { } } - Object getOrCreate(final NameIterator name, final SmallRyeConfig config) { + 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, config); - Object self = container.getChildObject(name, config, enclosing, getContainingName()); + 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(config); + self = create(cache, config); if (isConsumeSegment()) name.previous(); container.setChildObject(name, enclosing, getContainingName(), self); @@ -198,19 +208,20 @@ Object getOrCreate(final NameIterator name, final SmallRyeConfig config) { return self; } - ResultHandle generateGetOrCreate(final BytecodeCreator body, final ResultHandle name, final ResultHandle config) { + 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, config); + final ResultHandle enclosing = container.generateGetOrCreate(body, name, cache, config); final AssignableResultHandle var = body.createVariable(Object.class); - body.assign(var, container.generateGetChildObject(body, name, config, enclosing, getContainingName())); + 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, config)); + createBranch.assign(var, generateCreate(createBranch, cache, config)); if (isConsumeSegment()) createBranch.invokeVirtualMethod(NI_PREV_METHOD, name); container.generateSetChildObject(createBranch, name, enclosing, getContainingName(), var); @@ -221,15 +232,17 @@ ResultHandle generateGetOrCreate(final BytecodeCreator body, final ResultHandle return var; } - void acceptConfigurationValueIntoLeaf(final LeafConfigType leafType, final NameIterator name, final SmallRyeConfig config) { + 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, config), fieldInfo.getField(), name, config); + leafType.acceptConfigurationValueIntoGroup(getOrCreate(name, cache, config), fieldInfo.getField(), name, config); } void generateAcceptConfigurationValueIntoLeaf(final BytecodeCreator body, final LeafConfigType leafType, - final ResultHandle name, final ResultHandle config) { + final ResultHandle name, final ResultHandle cache, final ResultHandle config) { final FieldInfo fieldInfo = fieldInfos.get(leafType.getContainingName()); - leafType.generateAcceptConfigurationValueIntoGroup(body, generateGetOrCreate(body, name, config), fieldInfo.getSetter(), + leafType.generateAcceptConfigurationValueIntoGroup(body, generateGetOrCreate(body, name, cache, config), + fieldInfo.getSetter(), name, config); } @@ -246,17 +259,18 @@ void generateSetChildObject(final BytecodeCreator body, final ResultHandle name, body.invokeStaticMethod(fieldInfos.get(containingName).getSetter(), self, value); } - void getDefaultValueIntoEnclosingGroup(final Object enclosing, final SmallRyeConfig config, final Field field) { + void getDefaultValueIntoEnclosingGroup(final Object enclosing, final ExpandingConfigSource.Cache cache, + final SmallRyeConfig config, final Field field) { try { - field.set(enclosing, create(config)); + 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 config) { - final ResultHandle self = generateCreate(body, config); + final MethodDescriptor setter, final ResultHandle cache, final ResultHandle config) { + final ResultHandle self = generateCreate(body, cache, config); body.invokeStaticMethod(setter, enclosing, self); } 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 index 15e9358037049..769c0834ad6bb 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/IntConfigType.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/IntConfigType.java @@ -6,9 +6,11 @@ import org.wildfly.common.Assert; import io.quarkus.deployment.AccessorFinder; +import io.quarkus.deployment.steps.ConfigurationSetup; 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; @@ -29,21 +31,22 @@ public IntConfigType(final String containingName, final CompoundConfigType conta this.defaultValue = defaultValue; } - public void acceptConfigurationValue(final NameIterator name, final SmallRyeConfig config) { + 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, config); + 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 config) { + 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, config); + container.generateAcceptConfigurationValueIntoLeaf(body, this, name, cache, config); // the iterator is not used after this point // if (isConsumeSegment()) body.invokeVirtualMethod(NI_NEXT_METHOD, name); } @@ -51,8 +54,7 @@ public void generateAcceptConfigurationValue(final BytecodeCreator body, final R public void acceptConfigurationValueIntoGroup(final Object enclosing, final Field field, final NameIterator name, final SmallRyeConfig config) { try { - field.setInt(enclosing, config.getValue(name.toString(), OptionalInt.class) - .orElse(config.convert(defaultValue, Integer.class).intValue())); + field.setInt(enclosing, config.getValue(name.toString(), OptionalInt.class).orElse(0)); } catch (IllegalAccessException e) { throw toError(e); } @@ -60,7 +62,7 @@ public void acceptConfigurationValueIntoGroup(final Object enclosing, final Fiel public void generateAcceptConfigurationValueIntoGroup(final BytecodeCreator body, final ResultHandle enclosing, final MethodDescriptor setter, final ResultHandle name, final ResultHandle config) { - // config.getValue(name.toString(), OptionalInt.class).orElse(config.convert(defaultValue, Integer.class).intValue()) + // config.getValue(name.toString(), OptionalInt.class).orElse(0) final ResultHandle optionalValue = body.checkCast(body.invokeVirtualMethod( SRC_GET_VALUE, config, @@ -68,43 +70,51 @@ public void generateAcceptConfigurationValueIntoGroup(final BytecodeCreator body OBJ_TO_STRING_METHOD, name), body.loadClass(OptionalInt.class)), OptionalInt.class); - final ResultHandle convertedDefault = getConvertedDefault(body, config); - final ResultHandle defaultedValue = body.checkCast(body.invokeVirtualMethod( + final ResultHandle intValue = body.invokeVirtualMethod( OPTINT_OR_ELSE_METHOD, optionalValue, - convertedDefault), Integer.class); - final ResultHandle intValue = body.invokeVirtualMethod(INT_VALUE_METHOD, defaultedValue); + body.load(0)); body.invokeStaticMethod(setter, enclosing, intValue); } + public String getDefaultValueString() { + return defaultValue; + } + public Class getItemClass() { return int.class; } - void getDefaultValueIntoEnclosingGroup(final Object enclosing, final SmallRyeConfig config, final Field field) { + void getDefaultValueIntoEnclosingGroup(final Object enclosing, final ExpandingConfigSource.Cache cache, + final SmallRyeConfig config, final Field field) { try { - field.setInt(enclosing, config.convert(defaultValue, Integer.class).intValue()); + field.setInt(enclosing, + config.convert(ExpandingConfigSource.expandValue(defaultValue, cache), Integer.class).intValue()); } catch (IllegalAccessException e) { throw toError(e); } } void generateGetDefaultValueIntoEnclosingGroup(final BytecodeCreator body, final ResultHandle enclosing, - final MethodDescriptor setter, final ResultHandle config) { + final MethodDescriptor setter, final ResultHandle cache, final ResultHandle config) { body.invokeStaticMethod(setter, enclosing, - body.invokeVirtualMethod(INT_VALUE_METHOD, getConvertedDefault(body, config))); + body.invokeVirtualMethod(INT_VALUE_METHOD, getConvertedDefault(body, cache, config))); } public ResultHandle writeInitialization(final BytecodeCreator body, final AccessorFinder accessorFinder, - final ResultHandle smallRyeConfig) { - return body.invokeVirtualMethod(INT_VALUE_METHOD, getConvertedDefault(body, smallRyeConfig)); + final ResultHandle cache, final ResultHandle smallRyeConfig) { + return body.invokeVirtualMethod(INT_VALUE_METHOD, getConvertedDefault(body, cache, smallRyeConfig)); } - private ResultHandle getConvertedDefault(final BytecodeCreator body, final ResultHandle config) { + private ResultHandle getConvertedDefault(final BytecodeCreator body, final ResultHandle cache, final ResultHandle config) { return body.checkCast(body.invokeVirtualMethod( SRC_CONVERT_METHOD, config, - body.load(defaultValue), + cache == null ? body.load(defaultValue) + : body.invokeStaticMethod( + ConfigurationSetup.ECS_EXPAND_VALUE, + body.load(defaultValue), + cache), body.loadClass(Integer.class)), Integer.class); } } 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 index a0744e62a2782..c46d66ba80485 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/LeafConfigType.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/LeafConfigType.java @@ -9,6 +9,7 @@ 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; @@ -35,11 +36,14 @@ public void load() { * 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, @NotNull SmallRyeConfig config); + public abstract void acceptConfigurationValue(@NotNull NameIterator name, final ExpandingConfigSource.Cache cache, + @NotNull SmallRyeConfig config); - public abstract void generateAcceptConfigurationValue(BytecodeCreator body, ResultHandle name, ResultHandle 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); @@ -55,4 +59,6 @@ void generateAcceptConfigurationValueIntoMap(BytecodeCreator body, ResultHandle ResultHandle name, ResultHandle config) { throw Assert.unsupported(); } + + public abstract String getDefaultValueString(); } 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 index 6714ab8a7f255..365422ac2661d 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/LongConfigType.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/LongConfigType.java @@ -6,9 +6,11 @@ import org.wildfly.common.Assert; import io.quarkus.deployment.AccessorFinder; +import io.quarkus.deployment.steps.ConfigurationSetup; 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; @@ -29,21 +31,22 @@ public LongConfigType(final String containingName, final CompoundConfigType cont this.defaultValue = defaultValue; } - public void acceptConfigurationValue(final NameIterator name, final SmallRyeConfig config) { + 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, config); + 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 config) { + 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, config); + container.generateAcceptConfigurationValueIntoLeaf(body, this, name, cache, config); // the iterator is not used after this point // if (isConsumeSegment()) body.invokeVirtualMethod(NI_NEXT_METHOD, name); } @@ -51,8 +54,7 @@ public void generateAcceptConfigurationValue(final BytecodeCreator body, final R public void acceptConfigurationValueIntoGroup(final Object enclosing, final Field field, final NameIterator name, final SmallRyeConfig config) { try { - field.setLong(enclosing, config.getValue(name.toString(), OptionalLong.class) - .orElse(config.convert(defaultValue, Long.class).longValue())); + field.setLong(enclosing, config.getValue(name.toString(), OptionalLong.class).orElse(0L)); } catch (IllegalAccessException e) { throw toError(e); } @@ -60,7 +62,7 @@ public void acceptConfigurationValueIntoGroup(final Object enclosing, final Fiel public void generateAcceptConfigurationValueIntoGroup(final BytecodeCreator body, final ResultHandle enclosing, final MethodDescriptor setter, final ResultHandle name, final ResultHandle config) { - // config.getValue(name.toString(), OptionalLong.class).orElse(config.convert(defaultValue, Long.class).longValue()) + // config.getValue(name.toString(), OptionalLong.class).orElse(0L) final ResultHandle optionalValue = body.checkCast(body.invokeVirtualMethod( SRC_GET_VALUE, config, @@ -68,43 +70,51 @@ public void generateAcceptConfigurationValueIntoGroup(final BytecodeCreator body OBJ_TO_STRING_METHOD, name), body.loadClass(OptionalLong.class)), OptionalLong.class); - final ResultHandle convertedDefault = getConvertedDefault(body, config); - final ResultHandle defaultedValue = body.checkCast(body.invokeVirtualMethod( + final ResultHandle longValue = body.invokeVirtualMethod( OPTLONG_OR_ELSE_METHOD, optionalValue, - convertedDefault), Long.class); - final ResultHandle longValue = body.invokeVirtualMethod(LONG_VALUE_METHOD, defaultedValue); + body.load(0L)); body.invokeStaticMethod(setter, enclosing, longValue); } + public String getDefaultValueString() { + return defaultValue; + } + public Class getItemClass() { return long.class; } - void getDefaultValueIntoEnclosingGroup(final Object enclosing, final SmallRyeConfig config, final Field field) { + void getDefaultValueIntoEnclosingGroup(final Object enclosing, final ExpandingConfigSource.Cache cache, + final SmallRyeConfig config, final Field field) { try { - field.setLong(enclosing, config.convert(defaultValue, Long.class).longValue()); + field.setLong(enclosing, + config.convert(ExpandingConfigSource.expandValue(defaultValue, cache), Long.class).longValue()); } catch (IllegalAccessException e) { throw toError(e); } } void generateGetDefaultValueIntoEnclosingGroup(final BytecodeCreator body, final ResultHandle enclosing, - final MethodDescriptor setter, final ResultHandle config) { + final MethodDescriptor setter, final ResultHandle cache, final ResultHandle config) { body.invokeStaticMethod(setter, enclosing, - body.invokeVirtualMethod(LONG_VALUE_METHOD, getConvertedDefault(body, config))); + body.invokeVirtualMethod(LONG_VALUE_METHOD, getConvertedDefault(body, cache, config))); } public ResultHandle writeInitialization(final BytecodeCreator body, final AccessorFinder accessorFinder, - final ResultHandle smallRyeConfig) { - return body.invokeVirtualMethod(LONG_VALUE_METHOD, getConvertedDefault(body, smallRyeConfig)); + final ResultHandle cache, final ResultHandle smallRyeConfig) { + return body.invokeVirtualMethod(LONG_VALUE_METHOD, getConvertedDefault(body, cache, smallRyeConfig)); } - private ResultHandle getConvertedDefault(final BytecodeCreator body, final ResultHandle config) { + private ResultHandle getConvertedDefault(final BytecodeCreator body, final ResultHandle cache, final ResultHandle config) { return body.checkCast(body.invokeVirtualMethod( SRC_CONVERT_METHOD, config, - body.load(defaultValue), + cache == null ? body.load(defaultValue) + : body.invokeStaticMethod( + ConfigurationSetup.ECS_EXPAND_VALUE, + body.load(defaultValue), + cache), body.loadClass(Long.class)), Long.class); } } 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 index c20842573d149..a2ce0e360c52a 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/MapConfigType.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/MapConfigType.java @@ -9,6 +9,7 @@ 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; @@ -30,11 +31,13 @@ public void load() { } @SuppressWarnings("unchecked") - Object getChildObject(final NameIterator name, final SmallRyeConfig config, final Object self, final String childName) { + 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 config, + 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)); @@ -51,14 +54,15 @@ void generateSetChildObject(final BytecodeCreator body, final ResultHandle name, body.invokeVirtualMethod(NI_GET_NEXT_SEGMENT, name), value); } - TreeMap getOrCreate(final NameIterator name, final SmallRyeConfig config) { + 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, config); - self = (TreeMap) container.getChildObject(name, config, enclosing, getContainingName()); + 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); @@ -71,15 +75,16 @@ TreeMap getOrCreate(final NameIterator name, final SmallRyeConfi return self; } - ResultHandle generateGetOrCreate(final BytecodeCreator body, final ResultHandle name, final ResultHandle config) { + 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, config); + 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, config, enclosing, getContainingName()), Map.class)); + 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); @@ -92,21 +97,23 @@ ResultHandle generateGetOrCreate(final BytecodeCreator body, final ResultHandle } } - void acceptConfigurationValueIntoLeaf(final LeafConfigType leafType, final NameIterator name, final SmallRyeConfig config) { - leafType.acceptConfigurationValueIntoMap(getOrCreate(name, config), name, config); + 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 config) { - leafType.generateAcceptConfigurationValueIntoMap(body, generateGetOrCreate(body, name, config), name, config); + 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 smallRyeConfig) { + final ResultHandle cache, final ResultHandle smallRyeConfig) { return body.newInstance(TREE_MAP_CTOR); } - void getDefaultValueIntoEnclosingGroup(final Object enclosing, final SmallRyeConfig config, final Field field) { + void getDefaultValueIntoEnclosingGroup(final Object enclosing, final ExpandingConfigSource.Cache cache, + final SmallRyeConfig config, final Field field) { try { field.set(enclosing, new TreeMap<>()); } catch (IllegalAccessException e) { @@ -115,7 +122,7 @@ void getDefaultValueIntoEnclosingGroup(final Object enclosing, final SmallRyeCon } void generateGetDefaultValueIntoEnclosingGroup(final BytecodeCreator body, final ResultHandle enclosing, - final MethodDescriptor setter, final ResultHandle config) { + 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 index f1f27ddae03ef..26ae317032aa3 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/ObjectConfigType.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/ObjectConfigType.java @@ -4,9 +4,11 @@ import java.util.Map; import io.quarkus.deployment.AccessorFinder; +import io.quarkus.deployment.steps.ConfigurationSetup; 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; @@ -27,44 +29,52 @@ public Class getItemClass() { return expectedType; } - void getDefaultValueIntoEnclosingGroup(final Object enclosing, final SmallRyeConfig config, final Field field) { + void getDefaultValueIntoEnclosingGroup(final Object enclosing, final ExpandingConfigSource.Cache cache, + final SmallRyeConfig config, final Field field) { try { - field.set(enclosing, config.convert(defaultValue, expectedType)); + field.set(enclosing, config.convert(ExpandingConfigSource.expandValue(defaultValue, cache), expectedType)); } catch (IllegalAccessException e) { throw toError(e); } } void generateGetDefaultValueIntoEnclosingGroup(final BytecodeCreator body, final ResultHandle enclosing, - final MethodDescriptor setter, final ResultHandle config) { + final MethodDescriptor setter, final ResultHandle cache, final ResultHandle config) { body.invokeStaticMethod(setter, enclosing, - body.invokeVirtualMethod(SRC_CONVERT_METHOD, config, body.load(defaultValue), body.loadClass(expectedType))); + body.invokeVirtualMethod(SRC_CONVERT_METHOD, config, + cache == null ? body.load(defaultValue) + : body.invokeStaticMethod( + ConfigurationSetup.ECS_EXPAND_VALUE, + body.load(defaultValue), + cache), + body.loadClass(expectedType))); } public ResultHandle writeInitialization(final BytecodeCreator body, final AccessorFinder accessorFinder, - final ResultHandle smallRyeConfig) { - return body.checkCast(body.invokeVirtualMethod(SRC_CONVERT_METHOD, smallRyeConfig, body.load(defaultValue), + final ResultHandle cache, final ResultHandle smallRyeConfig) { + return body.checkCast(body.invokeVirtualMethod(SRC_CONVERT_METHOD, smallRyeConfig, + cache == null ? body.load(defaultValue) + : body.invokeStaticMethod( + ConfigurationSetup.ECS_EXPAND_VALUE, + body.load(defaultValue), + cache), body.loadClass(expectedType)), expectedType); } - void checkLoaded() { - if (expectedType == null) - throw notLoadedException(); - } - - public void acceptConfigurationValue(final NameIterator name, final SmallRyeConfig config) { + public void acceptConfigurationValue(final NameIterator name, final ExpandingConfigSource.Cache cache, + final SmallRyeConfig config) { if (isConsumeSegment()) name.previous(); - getContainer().acceptConfigurationValueIntoLeaf(this, name, config); + 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 config) { + final ResultHandle cache, final ResultHandle config) { if (isConsumeSegment()) body.invokeVirtualMethod(NI_PREV_METHOD, name); - getContainer().generateAcceptConfigurationValueIntoLeaf(body, this, name, config); + getContainer().generateAcceptConfigurationValueIntoLeaf(body, this, name, cache, config); // the iterator is not used after this point // if (isConsumeSegment()) body.invokeVirtualMethod(NI_NEXT_METHOD, name); } @@ -94,8 +104,12 @@ void generateAcceptConfigurationValueIntoMap(final BytecodeCreator body, final R generateGetValue(body, name, config)); } + public String getDefaultValueString() { + return defaultValue; + } + private T getValue(final NameIterator name, final SmallRyeConfig config, Class expectedType) { - return config.getOptionalValue(name.toString(), expectedType).orElse(config.convert(defaultValue, expectedType)); + return config.getOptionalValue(name.toString(), expectedType).orElse(null); } private ResultHandle generateGetValue(final BytecodeCreator body, final ResultHandle name, final ResultHandle config) { @@ -106,8 +120,6 @@ private ResultHandle generateGetValue(final BytecodeCreator body, final ResultHa OBJ_TO_STRING_METHOD, name), body.loadClass(expectedType)); - final ResultHandle defaultValue = body.invokeVirtualMethod(SRC_CONVERT_METHOD, config, body.load(this.defaultValue), - body.loadClass(expectedType)); - return body.invokeVirtualMethod(OPT_OR_ELSE_METHOD, optionalValue, defaultValue); + return body.invokeVirtualMethod(OPT_OR_ELSE_METHOD, optionalValue, body.loadNull()); } } 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 index bb3473fe329c3..b793477f75bf3 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/ObjectListConfigType.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/ObjectListConfigType.java @@ -9,11 +9,13 @@ import java.util.function.IntFunction; import io.quarkus.deployment.AccessorFinder; +import io.quarkus.deployment.steps.ConfigurationSetup; 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; @@ -32,41 +34,52 @@ public ObjectListConfigType(final String containingName, final CompoundConfigTyp super(containingName, container, consumeSegment, defaultValue, expectedType); } - public void acceptConfigurationValue(final NameIterator name, final SmallRyeConfig config) { + 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, config); + 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 config) { + final ResultHandle cache, final ResultHandle config) { final CompoundConfigType container = getContainer(); if (isConsumeSegment()) body.invokeVirtualMethod(NI_PREV_METHOD, name); - container.generateAcceptConfigurationValueIntoLeaf(body, this, name, config); + 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 SmallRyeConfig config, final Field field) { + void getDefaultValueIntoEnclosingGroup(final Object enclosing, final ExpandingConfigSource.Cache cache, + final SmallRyeConfig config, final Field field) { try { field.set(enclosing, defaultValue.isEmpty() ? Collections.emptyList() - : ConfigUtils.getDefaults(config, defaultValue, expectedType, ArrayListFactory.getInstance())); + : ConfigUtils.getDefaults( + config, + ExpandingConfigSource.expandValue(defaultValue, cache), + expectedType, + ArrayListFactory.getInstance())); } catch (IllegalAccessException e) { throw toError(e); } } void generateGetDefaultValueIntoEnclosingGroup(final BytecodeCreator body, final ResultHandle enclosing, - final MethodDescriptor setter, final ResultHandle config) { + final MethodDescriptor setter, final ResultHandle cache, final ResultHandle config) { final ResultHandle value; if (defaultValue.isEmpty()) { value = body.invokeStaticMethod(EMPTY_LIST_METHOD); } else { - value = body.invokeStaticMethod(CU_GET_DEFAULTS_METHOD, config, body.load(defaultValue), + value = body.invokeStaticMethod(CU_GET_DEFAULTS_METHOD, config, + cache == null ? body.load(defaultValue) + : body.invokeStaticMethod( + ConfigurationSetup.ECS_EXPAND_VALUE, + body.load(defaultValue), + cache), body.loadClass(expectedType), body.invokeStaticMethod(ALF_GET_INST_METHOD)); } body.invokeStaticMethod(setter, enclosing, value); @@ -98,7 +111,7 @@ void generateAcceptConfigurationValueIntoMap(final BytecodeCreator body, final R } public ResultHandle writeInitialization(final BytecodeCreator body, final AccessorFinder accessorFinder, - final ResultHandle config) { + final ResultHandle cache, final ResultHandle config) { return body.checkCast(body.invokeStaticMethod(CU_GET_DEFAULTS_METHOD, config, body.load(defaultValue), body.loadClass(expectedType), body.invokeStaticMethod(ALF_GET_INST_METHOD)), List.class); } 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 index 2f8a081c07425..bde112b3e353c 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/configuration/OptionalObjectConfigType.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/configuration/OptionalObjectConfigType.java @@ -7,9 +7,11 @@ import org.wildfly.common.Assert; import io.quarkus.deployment.AccessorFinder; +import io.quarkus.deployment.steps.ConfigurationSetup; 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; @@ -22,31 +24,35 @@ public OptionalObjectConfigType(final String containingName, final CompoundConfi super(containingName, container, consumeSegment, defaultValue, expectedType); } - public void acceptConfigurationValue(final NameIterator name, final SmallRyeConfig config) { + 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, config); + 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 config) { + final ResultHandle cache, final ResultHandle config) { final CompoundConfigType container = getContainer(); if (isConsumeSegment()) body.invokeVirtualMethod(NI_PREV_METHOD, name); - container.generateAcceptConfigurationValueIntoLeaf(body, this, name, config); + 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 SmallRyeConfig config, final Field field) { + 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 { - field.set(enclosing, Optional.ofNullable(config.convert(defaultValue, expectedType))); + field.set(enclosing, Optional.ofNullable(config.convert( + ExpandingConfigSource.expandValue(defaultValue, cache), + expectedType))); } } catch (IllegalAccessException e) { throw toError(e); @@ -54,7 +60,7 @@ void getDefaultValueIntoEnclosingGroup(final Object enclosing, final SmallRyeCon } void generateGetDefaultValueIntoEnclosingGroup(final BytecodeCreator body, final ResultHandle enclosing, - final MethodDescriptor setter, final ResultHandle config) { + final MethodDescriptor setter, final ResultHandle cache, final ResultHandle config) { ResultHandle optValue; if (defaultValue.isEmpty()) { optValue = body.invokeStaticMethod(OPT_EMPTY_METHOD); @@ -97,12 +103,17 @@ void generateAcceptConfigurationValueIntoMap(final BytecodeCreator body, final R } public ResultHandle writeInitialization(final BytecodeCreator body, final AccessorFinder accessorFinder, - final ResultHandle config) { + final ResultHandle cache, final ResultHandle config) { if (defaultValue.isEmpty()) { return body.invokeStaticMethod(OPT_EMPTY_METHOD); } else { return body.invokeStaticMethod(OPT_OF_NULLABLE_METHOD, body.invokeVirtualMethod(SRC_CONVERT_METHOD, config, - body.load(defaultValue), body.loadClass(expectedType))); + cache == null ? body.load(defaultValue) + : body.invokeStaticMethod( + ConfigurationSetup.ECS_EXPAND_VALUE, + body.load(defaultValue), + cache), + body.loadClass(expectedType))); } } } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/devmode/HotReplacementContext.java b/core/deployment/src/main/java/io/quarkus/deployment/devmode/HotReplacementContext.java index 286f649d4628d..037a1426844f9 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/devmode/HotReplacementContext.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/devmode/HotReplacementContext.java @@ -1,6 +1,7 @@ package io.quarkus.deployment.devmode; import java.nio.file.Path; +import java.util.List; public interface HotReplacementContext { @@ -8,9 +9,16 @@ public interface HotReplacementContext { Path getSourcesDir(); - Path getResourcesDir(); + List getResourcesDir(); Throwable getDeploymentProblem(); - void doScan() throws Exception; + /** + * + * @return {@code true} if a restart was performed, {@code false} otherwise + * @throws Exception + */ + boolean doScan() throws Exception; + + void addPreScanStep(Runnable runnable); } diff --git a/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/devmode/ReplacementDebugPage.java b/core/deployment/src/main/java/io/quarkus/deployment/devmode/ReplacementDebugPage.java similarity index 77% rename from extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/devmode/ReplacementDebugPage.java rename to core/deployment/src/main/java/io/quarkus/deployment/devmode/ReplacementDebugPage.java index ffc7c54af0982..c9b1dbaf77950 100644 --- a/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/devmode/ReplacementDebugPage.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/devmode/ReplacementDebugPage.java @@ -1,31 +1,10 @@ -/* - * Copyright 2018 Red Hat, Inc. - * - * Licensed 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.undertow.deployment.devmode; +package io.quarkus.deployment.devmode; import java.io.PrintWriter; import java.io.StringWriter; -import io.undertow.server.HttpServerExchange; -import io.undertow.util.Headers; - /** - * Generates an error page with a stack trace - * - * @author Stuart Douglas + * Generates an error page with a stack trace. */ public class ReplacementDebugPage { @@ -118,15 +97,7 @@ public class ReplacementDebugPage { " line-height: 1.5;\n" + "}\n"; - public static void handleRequest(HttpServerExchange exchange, final Throwable exception) { - String bodyText = generateHtml(exception); - - exchange.setStatusCode(500); - exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/html; charset=UTF-8"); - exchange.getResponseSender().send(bodyText); - } - - private static String generateHtml(final Throwable exception) { + public static String generateHtml(final Throwable exception) { String headerMessage = generateHeaderMessage(exception); String stackTrace = generateStackTrace(exception); 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 efbb6c1ffe318..dba00a466de20 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 @@ -29,7 +29,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.HashSet; import java.util.List; @@ -64,13 +63,16 @@ public class ApplicationArchiveBuildStep { private static final String JANDEX_INDEX = "META-INF/jandex.idx"; + // At least Jandex 2.1 is needed + private static final int REQUIRED_INDEX_VERSION = 8; + IndexDependencyConfiguration config; @ConfigRoot(phase = ConfigPhase.BUILD_TIME) static final class IndexDependencyConfiguration { /** * Artifacts on the class path that should also be indexed, which will allow classes in the index to be - * processed by quarkuss processors + * processed by Quarkus processors */ @ConfigItem(name = ConfigItem.PARENT) Map indexDependency; @@ -162,7 +164,6 @@ private static Collection getMarkerFilePaths(ClassLoader classLo ret.add(urlToPath(url)); } } - return ret; } @@ -187,11 +188,19 @@ private static Index handleFilePath(Path path) throws IOException { Path existing = path.resolve(JANDEX_INDEX); if (Files.exists(existing)) { try (FileInputStream in = new FileInputStream(existing.toFile())) { - IndexReader r = new IndexReader(in); - return r.read(); + IndexReader reader = new IndexReader(in); + if (reader.getIndexVersion() < REQUIRED_INDEX_VERSION) { + LOGGER.warnf("Re-indexing %s - at least Jandex 2.1 must be used to index an application dependency", path); + return indexFilePath(path); + } else { + return reader.read(); + } } } + return indexFilePath(path); + } + private static Index indexFilePath(Path path) throws IOException { Indexer indexer = new Indexer(); try (Stream stream = Files.walk(path)) { stream.forEach(path1 -> { @@ -208,23 +217,32 @@ private static Index handleFilePath(Path path) throws IOException { } private static Index handleJarPath(Path path) throws IOException { - Indexer indexer = new Indexer(); try (JarFile file = new JarFile(path.toFile())) { ZipEntry existing = file.getEntry(JANDEX_INDEX); if (existing != null) { try (InputStream in = file.getInputStream(existing)) { - IndexReader r = new IndexReader(in); - return r.read(); + IndexReader reader = new IndexReader(in); + if (reader.getIndexVersion() < REQUIRED_INDEX_VERSION) { + LOGGER.warnf("Re-indexing %s - at least Jandex 2.1 must be used to index an application dependency", + path); + return indexJar(file); + } else { + return reader.read(); + } } } + return indexJar(file); + } + } - Enumeration e = file.entries(); - while (e.hasMoreElements()) { - JarEntry entry = e.nextElement(); - if (entry.getName().endsWith(".class")) { - try (InputStream inputStream = file.getInputStream(entry)) { - indexer.index(inputStream); - } + private static Index indexJar(JarFile file) throws IOException { + Indexer indexer = new Indexer(); + Enumeration e = file.entries(); + while (e.hasMoreElements()) { + JarEntry entry = e.nextElement(); + if (entry.getName().endsWith(".class")) { + try (InputStream inputStream = file.getInputStream(entry)) { + indexer.index(inputStream); } } } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/index/IndexingUtil.java b/core/deployment/src/main/java/io/quarkus/deployment/index/IndexingUtil.java index b3a813d182a90..0b5f4864dfc5a 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/index/IndexingUtil.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/index/IndexingUtil.java @@ -41,9 +41,13 @@ public static void indexClass(String beanClass, Indexer indexer, IndexView quark for (DotName annotationName : beanInfo.annotations().keySet()) { if (!additionalIndex.contains(annotationName) && quarkusIndex.getClassByName(annotationName) == null) { try (InputStream annotationStream = IoUtil.readClass(classLoader, annotationName.toString())) { - log.debugf("Index annotation: %s", annotationName); - indexer.index(annotationStream); - additionalIndex.add(annotationName); + if (annotationStream == null) { + log.debugf("Could not index annotation: %s (missing class or dependency)", annotationName); + } else { + log.debugf("Index annotation: %s", annotationName); + indexer.index(annotationStream); + additionalIndex.add(annotationName); + } } catch (IOException e) { throw new IllegalStateException("Failed to index: " + beanClass, e); } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/logging/LogCleanupFilterBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/logging/LogCleanupFilterBuildItem.java index 9ce5f0066e06f..ac6f9b4292311 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/logging/LogCleanupFilterBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/logging/LogCleanupFilterBuildItem.java @@ -1,7 +1,8 @@ package io.quarkus.deployment.logging; -import org.jboss.builder.item.MultiBuildItem; +import java.util.Arrays; +import io.quarkus.builder.item.MultiBuildItem; import io.quarkus.runtime.logging.LogCleanupFilterElement; /** @@ -14,8 +15,12 @@ public final class LogCleanupFilterBuildItem extends MultiBuildItem { private LogCleanupFilterElement filterElement; - public LogCleanupFilterBuildItem(String loggerName, String messageStart) { - this.filterElement = new LogCleanupFilterElement(loggerName, messageStart); + public LogCleanupFilterBuildItem(String loggerName, String... messageStarts) { + if (messageStarts.length == 0) { + throw new IllegalArgumentException("messageStarts cannot be null"); + } + + this.filterElement = new LogCleanupFilterElement(loggerName, Arrays.asList(messageStarts)); } public LogCleanupFilterElement getFilterElement() { 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 9e98c3930d5e0..57b611bcd856c 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 @@ -19,6 +19,7 @@ import java.util.List; import java.util.function.Consumer; import java.util.logging.Level; +import java.util.stream.Collectors; import org.jboss.logmanager.EmbeddedConfigurator; @@ -67,11 +68,15 @@ void setUpDefaultLevels(List categories, void setUpDefaultLogCleanupFilters(List logCleanupFilters, Consumer configOutput) { for (LogCleanupFilterBuildItem logCleanupFilter : logCleanupFilters) { + String startsWithClause = logCleanupFilter.getFilterElement().getMessageStarts().stream() + // SmallRye Config escaping is pretty naive so we only need to escape commas + .map(s -> s.replace(",", "\\,")) + .collect(Collectors.joining(",")); configOutput.accept( new RunTimeConfigurationDefaultBuildItem( "quarkus.log.filter.\"" + logCleanupFilter.getFilterElement().getLoggerName() + "\".if-starts-with", - logCleanupFilter.getFilterElement().getMessageStart())); + startsWithClause)); } } @@ -94,8 +99,8 @@ void setupLoggingRuntimeInit(LoggingSetupTemplate setupTemplate, LogConfig log) @BuildStep @Record(ExecutionTime.STATIC_INIT) - void setupLoggingStaticInit(LoggingSetupTemplate setupTemplate, LogConfig log) { - setupTemplate.initializeLogging(log); + void setupLoggingStaticInit(LoggingSetupTemplate setupTemplate) { + setupTemplate.initializeLoggingForImageBuild(); } @BuildStep @@ -105,4 +110,10 @@ ConfigurationCustomConverterBuildItem setUpLevelConverter() { Level.class, LevelConverter.class); } + + // This is specifically to help out with presentations, to allow an env var to always override this value + @BuildStep + void setUpDarkeningDefault(Consumer rtcConsumer) { + rtcConsumer.accept(new RunTimeConfigurationDefaultBuildItem("quarkus.log.console.darken", "0")); + } } 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 71306296480ab..f1adbbbbe1cce 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 @@ -4,7 +4,6 @@ import static org.objectweb.asm.Opcodes.ACC_PRIVATE; import static org.objectweb.asm.Opcodes.ACC_PUBLIC; -import java.io.IOException; import java.io.InputStream; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationHandler; @@ -66,7 +65,7 @@ public AnnotationProxyBuilder builder(AnnotationInstan if (clazz == null) { try (InputStream annotationStream = IoUtil.readClass(classLoader, name.toString())) { clazz = indexer.index(annotationStream); - } catch (IOException e) { + } catch (Exception e) { throw new IllegalStateException("Failed to index: " + name, e); } } 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 9e80701c46387..3f5f043c113ec 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 @@ -43,7 +43,6 @@ import java.util.function.Function; import java.util.stream.Collectors; -import org.apache.commons.beanutils.PropertyUtils; import org.jboss.invocation.proxy.ProxyConfiguration; import org.jboss.invocation.proxy.ProxyFactory; import org.jboss.jandex.AnnotationValue; @@ -255,7 +254,8 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl return "Runtime proxy of " + returnType + " with id " + key; } throw new RuntimeException( - "You cannot invoke directly on an object returned from the bytecode recorded, you can only pass is back into the recorder as a parameter"); + "You cannot invoke " + method.getName() + + "() directly on an object returned from the bytecode recorder, you can only pass it back into the recorder as a parameter"); } }); ProxyInstance instance = new ProxyInstance(proxyInstance, key); @@ -397,16 +397,23 @@ private ResultHandle loadObjectInstance(MethodCreator method, Object param, Map< out = method.invokeVirtualMethod(ofMethod(StartupContext.class, "getValue", Object.class, String.class), method.getMethodParam(0), method.load(proxyId)); } else if (param instanceof Class) { - String name = classProxies.get(param); - if (name == null) { - name = ((Class) param).getName(); + if (!((Class) param).isPrimitive()) { + // Only try to load the class by name if it is not a primitive class + String name = classProxies.get(param); + if (name == null) { + name = ((Class) param).getName(); + } + ResultHandle currentThread = method.invokeStaticMethod(ofMethod(Thread.class, "currentThread", Thread.class)); + ResultHandle tccl = method.invokeVirtualMethod( + ofMethod(Thread.class, "getContextClassLoader", ClassLoader.class), + currentThread); + out = method.invokeStaticMethod( + ofMethod(Class.class, "forName", Class.class, String.class, boolean.class, ClassLoader.class), + method.load(name), method.load(true), tccl); + } else { + // Else load the primitive type by reference; double.class => Class var9 = Double.TYPE; + out = method.loadClass((Class) param); } - ResultHandle currentThread = method.invokeStaticMethod(ofMethod(Thread.class, "currentThread", Thread.class)); - ResultHandle tccl = method.invokeVirtualMethod(ofMethod(Thread.class, "getContextClassLoader", ClassLoader.class), - currentThread); - out = method.invokeStaticMethod( - ofMethod(Class.class, "forName", Class.class, String.class, boolean.class, ClassLoader.class), - method.load(name), method.load(true), tccl); } else if (expectedType == boolean.class) { out = method.load((boolean) param); } else if (expectedType == Boolean.class) { @@ -541,80 +548,84 @@ private ResultHandle loadObjectInstance(MethodCreator method, Object param, Map< } } Set handledProperties = new HashSet<>(); - PropertyDescriptor[] desc = PropertyUtils.getPropertyDescriptors(param); - for (PropertyDescriptor i : desc) { - if (i.getReadMethod() != null && i.getWriteMethod() == null) { - try { - //read only prop, we may still be able to do stuff with it if it is a collection - if (Collection.class.isAssignableFrom(i.getPropertyType())) { - //special case, a collection with only a read method - //we assume we can just add to the connection - handledProperties.add(i.getName()); - - Collection propertyValue = (Collection) PropertyUtils.getProperty(param, i.getName()); - if (!propertyValue.isEmpty()) { - ResultHandle prop = method.invokeVirtualMethod(MethodDescriptor.ofMethod(i.getReadMethod()), - out); - for (Object c : propertyValue) { - ResultHandle toAdd = loadObjectInstance(method, c, returnValueResults, Object.class); - method.invokeInterfaceMethod(COLLECTION_ADD, prop, toAdd); + try (PropertyUtils introspection = new PropertyUtils()) { + PropertyDescriptor[] desc = introspection.getPropertyDescriptors(param); + for (PropertyDescriptor i : desc) { + if (i.getReadMethod() != null && i.getWriteMethod() == null) { + try { + //read only prop, we may still be able to do stuff with it if it is a collection + if (Collection.class.isAssignableFrom(i.getPropertyType())) { + //special case, a collection with only a read method + //we assume we can just add to the connection + handledProperties.add(i.getName()); + + Collection propertyValue = (Collection) introspection.getProperty(param, i.getName()); + if (!propertyValue.isEmpty()) { + ResultHandle prop = method.invokeVirtualMethod(MethodDescriptor.ofMethod(i.getReadMethod()), + out); + for (Object c : propertyValue) { + ResultHandle toAdd = loadObjectInstance(method, c, returnValueResults, Object.class); + method.invokeInterfaceMethod(COLLECTION_ADD, prop, toAdd); + } } - } - - } else if (Map.class.isAssignableFrom(i.getPropertyType())) { - //special case, a map with only a read method - //we assume we can just add to the map - handledProperties.add(i.getName()); - Map propertyValue = (Map) PropertyUtils.getProperty(param, - i.getName()); - if (!propertyValue.isEmpty()) { - ResultHandle prop = method.invokeVirtualMethod(MethodDescriptor.ofMethod(i.getReadMethod()), - out); - for (Map.Entry entry : propertyValue.entrySet()) { - ResultHandle key = loadObjectInstance(method, entry.getKey(), returnValueResults, - Object.class); - ResultHandle val = entry.getValue() != null - ? loadObjectInstance(method, entry.getValue(), returnValueResults, Object.class) - : method.loadNull(); - method.invokeInterfaceMethod(MAP_PUT, prop, key, val); + } else if (Map.class.isAssignableFrom(i.getPropertyType())) { + //special case, a map with only a read method + //we assume we can just add to the map + + handledProperties.add(i.getName()); + Map propertyValue = (Map) introspection.getProperty(param, + i.getName()); + if (!propertyValue.isEmpty()) { + ResultHandle prop = method.invokeVirtualMethod(MethodDescriptor.ofMethod(i.getReadMethod()), + out); + for (Map.Entry entry : propertyValue.entrySet()) { + ResultHandle key = loadObjectInstance(method, entry.getKey(), returnValueResults, + Object.class); + ResultHandle val = entry.getValue() != null + ? loadObjectInstance(method, entry.getValue(), returnValueResults, Object.class) + : method.loadNull(); + method.invokeInterfaceMethod(MAP_PUT, prop, key, val); + } } } - } - } catch (Exception e) { - throw new RuntimeException(e); - } - } else if (i.getReadMethod() != null && i.getWriteMethod() != null) { - try { - handledProperties.add(i.getName()); - Object propertyValue = PropertyUtils.getProperty(param, i.getName()); - if (propertyValue == null) { - //we just assume properties are null by default - //TODO: is this a valid assumption? Should we check this by creating an instance? - continue; + } catch (Exception e) { + throw new RuntimeException(e); } - Class propertyType = i.getPropertyType(); - if (i.getReadMethod().getReturnType() != i.getWriteMethod().getParameterTypes()[0]) { - //this is a weird situation where the reader and writer are different types - //we iterate and try and find a valid setter method for the type we have - //OpenAPI does some weird stuff like this - - for (Method m : param.getClass().getMethods()) { - if (m.getName().equals(i.getWriteMethod().getName())) { - if (m.getParameterTypes().length > 0 - && m.getParameterTypes()[0].isAssignableFrom(param.getClass())) { - propertyType = m.getParameterTypes()[0]; - break; + } else if (i.getReadMethod() != null && i.getWriteMethod() != null) { + try { + handledProperties.add(i.getName()); + Object propertyValue = introspection.getProperty(param, i.getName()); + if (propertyValue == null) { + //we just assume properties are null by default + //TODO: is this a valid assumption? Should we check this by creating an instance? + continue; + } + Class propertyType = i.getPropertyType(); + if (i.getReadMethod().getReturnType() != i.getWriteMethod().getParameterTypes()[0]) { + //this is a weird situation where the reader and writer are different types + //we iterate and try and find a valid setter method for the type we have + //OpenAPI does some weird stuff like this + + for (Method m : param.getClass().getMethods()) { + if (m.getName().equals(i.getWriteMethod().getName())) { + if (m.getParameterTypes().length > 0 + && m.getParameterTypes()[0].isAssignableFrom(param.getClass())) { + propertyType = m.getParameterTypes()[0]; + break; + } } } } + ResultHandle val = loadObjectInstance(method, propertyValue, returnValueResults, + i.getPropertyType()); + method.invokeVirtualMethod( + ofMethod(param.getClass(), i.getWriteMethod().getName(), void.class, propertyType), out, + val); + } catch (Exception e) { + throw new RuntimeException(e); } - ResultHandle val = loadObjectInstance(method, propertyValue, returnValueResults, i.getPropertyType()); - method.invokeVirtualMethod( - ofMethod(param.getClass(), i.getWriteMethod().getName(), void.class, propertyType), out, val); - } catch (Exception e) { - throw new RuntimeException(e); } } } @@ -643,7 +654,7 @@ private boolean findLoaded(final BytecodeCreator body, final Object param) { return true; } for (ObjectLoader loader : loaders) { - ResultHandle handle = loader.load(body, param); + ResultHandle handle = loader.load(body, param, staticInit); if (handle != null) { loadedObjects.put(param, handle); return true; diff --git a/core/deployment/src/main/java/io/quarkus/deployment/recording/ObjectLoader.java b/core/deployment/src/main/java/io/quarkus/deployment/recording/ObjectLoader.java index ff5405d9179bb..7d36751081d63 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/recording/ObjectLoader.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/recording/ObjectLoader.java @@ -13,7 +13,8 @@ public interface ObjectLoader { * * @param body the body to use for bytecode generation (not {@code null}) * @param obj the object to substitute (not {@code null}) + * @param staticInit {@code true} if this loader is for a static init method, {@code false} otherwise * @return the result handle of the value, or {@code null} if this loader cannot load the given object */ - ResultHandle load(BytecodeCreator body, Object obj); + ResultHandle load(BytecodeCreator body, Object obj, boolean staticInit); } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/recording/PropertyUtils.java b/core/deployment/src/main/java/io/quarkus/deployment/recording/PropertyUtils.java new file mode 100644 index 0000000000000..e324c450f78da --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/recording/PropertyUtils.java @@ -0,0 +1,95 @@ +package io.quarkus.deployment.recording; + +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.lang.reflect.InvocationTargetException; + +import org.apache.commons.beanutils.PropertyUtilsBean; + +/** + *

+ * Delegates to PropertyUtilsBean from Apache Commons Beanutils; + * must be closed to release any internal caches. + *

+ * + *

+ * This is inspired by org.apache.commons.beanutils.PropertyUtils, but + * only exposing the selected methods we use and to avoid allocating some objects + * which aren't necessary in our limited use cases; also bypasses the classloader + * scoped cache in favour of an explicit cache control on close, to keep things + * a bit lighter. + *

+ * + * @see org.apache.commons.beanutils.PropertyUtils + * @see org.apache.commons.beanutils.PropertyUtilsBean + */ +final class PropertyUtils implements AutoCloseable { + + /** + * Implementation note: the reference to PropertyUtilsBean is static so to avoid allocating multiple of those, + * yet we close its cache when this instance is closed. + * We're effectively assuming non current, singleton usage. + * Although if this is not respected, worse that could happen is some extra cache misses. + */ + private static final PropertyUtilsBean propertyUtilsBean = new PropertyUtilsBean(); + + /** + *

+ * Return the value of the specified property of the specified bean, + * no matter which property reference format is used, with no + * type conversions. + *

+ * + *

+ * For more details see PropertyUtilsBean. + *

+ * + * @param bean Bean whose property is to be extracted + * @param name Possibly indexed and/or nested name of the property + * to be extracted + * @return the property value + * + * @throws IllegalAccessException if the caller does not have + * access to the property accessor method + * @throws IllegalArgumentException if bean or + * name is null + * @throws InvocationTargetException if the property accessor method + * throws an exception + * @throws NoSuchMethodException if an accessor method for this + * property cannot be found + */ + public Object getProperty(final Object bean, final String name) + throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { + return propertyUtilsBean.getNestedProperty(bean, name); + } + + /** + *

+ * Retrieve the property descriptors for the specified bean, + * introspecting and caching them the first time a particular bean class + * is encountered. + *

+ * + *

+ * For more details see PropertyUtilsBean. + *

+ * + * @param bean Bean for which property descriptors are requested + * @return the property descriptors + * @throws IllegalArgumentException if bean is null + */ + public PropertyDescriptor[] getPropertyDescriptors(final Object bean) { + return propertyUtilsBean.getPropertyDescriptors(bean); + } + + /** + * Clears all caches of PropertyUtilsBean and Introspector + * + * @see Introspector + * @see PropertyUtilsBean + */ + public void close() { + propertyUtilsBean.clearDescriptors(); + } + +} 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 new file mode 100644 index 0000000000000..5f3c56b0cd930 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/ApplicationInfoBuildStep.java @@ -0,0 +1,65 @@ +package io.quarkus.deployment.steps; + +import static io.quarkus.deployment.ApplicationInfoUtil.APPLICATION_INFO_PROPERTIES; +import static io.quarkus.deployment.ApplicationInfoUtil.ARTIFACT_ID_KEY; +import static io.quarkus.deployment.ApplicationInfoUtil.META_INF; +import static io.quarkus.deployment.ApplicationInfoUtil.VERSION_KEY; +import static io.quarkus.deployment.builditem.ApplicationInfoBuildItem.UNSET_VALUE; + +import java.io.File; +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 io.quarkus.deployment.ApplicationConfig; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.ApplicationInfoBuildItem; + +public class ApplicationInfoBuildStep { + + private static final String PROPERTIES_FILE_TO_READ = META_INF + File.separator + APPLICATION_INFO_PROPERTIES; + + @BuildStep + public ApplicationInfoBuildItem create(ApplicationConfig applicationConfig) { + final String userConfiguredName = applicationConfig.name; + final String userConfiguredVersion = applicationConfig.version; + + final Properties applicationInfoProperties = getApplicationInfoProperties(); + + return new ApplicationInfoBuildItem( + useIfNotEmpty(userConfiguredName, applicationInfoProperties, ARTIFACT_ID_KEY), + useIfNotEmpty(userConfiguredVersion, applicationInfoProperties, VERSION_KEY)); + } + + private Properties getApplicationInfoProperties() { + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + if (cl == null) { + cl = ApplicationInfoBuildStep.class.getClassLoader(); + } + try { + final Properties p = new Properties(); + // work around #1477 + final Enumeration resources = cl == null ? ClassLoader.getSystemResources(PROPERTIES_FILE_TO_READ) + : cl.getResources(PROPERTIES_FILE_TO_READ); + 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 p; + } catch (IOException e) { + throw new IllegalStateException("Cannot read application-info.properties", e); + } + } + + private String useIfNotEmpty(String value, Properties properties, String key) { + return (value != null) && !value.isEmpty() ? value : properties.getProperty(key, UNSET_VALUE); + } +} 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 index 8af0bf78f7dfe..d8e9da9218d27 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigurationSetup.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigurationSetup.java @@ -5,19 +5,24 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.UndeclaredThrowableException; import java.net.InetAddress; import java.net.InetSocketAddress; 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 java.util.regex.Pattern; @@ -26,6 +31,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.graalvm.nativeimage.ImageInfo; import org.jboss.logging.Logger; @@ -35,19 +41,27 @@ import org.wildfly.security.ssl.Protocol; import io.quarkus.deployment.AccessorFinder; +import io.quarkus.deployment.ApplicationArchive; 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.BuildTimeConfigurationBuildItem; +import io.quarkus.deployment.builditem.BuildTimeRunTimeFixedConfigurationBuildItem; import io.quarkus.deployment.builditem.BytecodeRecorderObjectLoaderBuildItem; -import io.quarkus.deployment.builditem.ConfigurationBuildItem; import io.quarkus.deployment.builditem.ConfigurationCustomConverterBuildItem; 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.substrate.RuntimeReinitializedClassBuildItem; +import io.quarkus.deployment.builditem.substrate.RuntimeInitializedClassBuildItem; +import io.quarkus.deployment.builditem.substrate.ServiceProviderBuildItem; +import io.quarkus.deployment.builditem.substrate.SubstrateResourceBuildItem; import io.quarkus.deployment.configuration.ConfigDefinition; import io.quarkus.deployment.configuration.ConfigPatternMap; +import io.quarkus.deployment.configuration.DefaultValuesConfigurationSource; import io.quarkus.deployment.configuration.LeafConfigType; import io.quarkus.deployment.recording.ObjectLoader; import io.quarkus.deployment.util.ServiceUtil; @@ -60,7 +74,10 @@ import io.quarkus.gizmo.MethodDescriptor; import io.quarkus.gizmo.ResultHandle; import io.quarkus.runtime.annotations.ConfigPhase; +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.CidrAddressConverter; import io.quarkus.runtime.configuration.ConverterFactory; import io.quarkus.runtime.configuration.DefaultConfigSource; @@ -84,11 +101,24 @@ public class ConfigurationSetup { private static final Logger log = Logger.getLogger("io.quarkus.configuration"); - public static final String CONFIG_HELPER = "io.quarkus.runtime.generated.ConfigHelper"; - public static final String CONFIG_HELPER_DATA = "io.quarkus.runtime.generated.ConfigHelperData"; - public static final String CONFIG_ROOT = "io.quarkus.runtime.generated.ConfigRoot"; - - public static final FieldDescriptor CONFIG_ROOT_FIELD = FieldDescriptor.of(CONFIG_HELPER_DATA, "configRoot", CONFIG_ROOT); + 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", @@ -96,12 +126,16 @@ public class ConfigurationSetup { 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 CF_GET_CONVERTER = MethodDescriptor.ofMethod(ConverterFactory.class, "getConverter", - Converter.class, SmallRyeConfig.class, Class.class); + private static final MethodDescriptor CF_GET_IMPLICIT_CONVERTER = MethodDescriptor.ofMethod(ConverterFactory.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, Config.class); + .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, @@ -110,18 +144,28 @@ public class ConfigurationSetup { "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_BUILD = MethodDescriptor.ofMethod(ImageInfo.class, "inImageBuildtimeCode", - boolean.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); - public static final MethodDescriptor GET_ROOT_METHOD = MethodDescriptor.ofMethod(CONFIG_HELPER, "getRoot", CONFIG_ROOT); + 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 FieldDescriptor ECS_WRAPPER = FieldDescriptor.of(ExpandingConfigSource.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 String CONFIG_ROOTS_LIST = "META-INF/quarkus-config-roots.list"; + private static final String[] NO_STRINGS = new String[0]; public ConfigurationSetup() { } @@ -162,42 +206,154 @@ public void setUpConverters(BuildProducer * Run before anything that consumes configuration; sets up the main configuration definition instance. * * @param converters the converters to set up - * @return the configuration build item + * @param runTimeConfigConsumer the run time config consumer + * @param buildTimeConfigConsumer the build time config consumer + * @param buildTimeRunTimeConfigConsumer the build time/run time fixed config consumer + * @param extensionClassLoaderBuildItem the extension class loader build item + * @param archiveRootBuildItem the application archive root */ @BuildStep - public ConfigurationBuildItem initializeConfiguration( + public void initializeConfiguration( List converters, - ExtensionClassLoaderBuildItem extensionClassLoaderBuildItem) throws IOException, ClassNotFoundException { + Consumer runTimeConfigConsumer, + Consumer buildTimeConfigConsumer, + Consumer buildTimeRunTimeConfigConsumer, + Consumer resourceConsumer, + Consumer niResourceConsumer, + Consumer runTimeDefaultConsumer, + ExtensionClassLoaderBuildItem extensionClassLoaderBuildItem, + ArchiveRootBuildItem archiveRootBuildItem) 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); + // populate it with all known types + for (Class clazz : ServiceUtil.classesNamedIn(extensionClassLoaderBuildItem.getExtensionClassLoader(), + CONFIG_ROOTS_LIST)) { + final ConfigRoot annotation = clazz.getAnnotation(ConfigRoot.class); + if (annotation == null) { + log.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 { + log.warnf("Unrecognized configuration phase \"%s\" on %s", phase, clazz); + } + } + } + + // now prepare & load the build configuration SmallRyeConfigBuilder builder = new SmallRyeConfigBuilder(); // expand properties - builder.withWrapper(ExpandingConfigSource::new); + final ExpandingConfigSource.Cache cache = new ExpandingConfigSource.Cache(); + builder.withWrapper(ExpandingConfigSource.wrapper(cache)); builder.addDefaultSources(); - builder.withSources(new ApplicationPropertiesConfigSource.InJar()); + final ApplicationPropertiesConfigSource.InJar inJar = new ApplicationPropertiesConfigSource.InJar(); + final DefaultValuesConfigurationSource defaultSource = new DefaultValuesConfigurationSource( + buildTimeConfig.getLeafPatterns()); + builder.withSources(inJar, defaultSource); for (ConfigurationCustomConverterBuildItem converter : converters) { withConverterHelper(builder, converter.getType(), converter.getPriority(), converter.getConverter()); } - final SmallRyeConfig src = (SmallRyeConfig) builder.build(); - final ConfigDefinition configDefinition = new ConfigDefinition(); - // populate it with all known types - for (Class clazz : ServiceUtil.classesNamedIn(extensionClassLoaderBuildItem.getExtensionClassLoader(), - "META-INF/quarkus-config-roots.list")) { - configDefinition.registerConfigRoot(clazz); - } + final SmallRyeConfig src = (SmallRyeConfig) builder.addDefaultSources() + .addDiscoveredSources().addDiscoveredConverters().build(); SmallRyeConfigProviderResolver.instance().registerConfig(src, Thread.currentThread().getContextClassLoader()); - configDefinition.loadConfiguration(src); - return new ConfigurationBuildItem(configDefinition); + final Set unmatched = new HashSet<>(); + ConfigDefinition.loadConfiguration(cache, src, unmatched, + buildTimeConfig, + buildTimeRunTimeConfig, // this one is only for generating a default-values config source + runTimeConfig); + // exclude any default config property names that aren't part of application.properties + final Set inJarPropertyNames = inJar.getPropertyNames(); + unmatched.removeIf(s -> !inJarPropertyNames.contains(s) && !s.startsWith("quarkus.")); + + // 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 SubstrateResourceBuildItem(BuildTimeConfigFactory.BUILD_TIME_CONFIG_NAME)); + + // produce defaults for user-provided config + unmatched.addAll(runTimeConfig.getLoadedProperties().keySet()); + final boolean old = ExpandingConfigSource.setExpanding(false); + try { + for (String propName : unmatched) { + runTimeDefaultConsumer + .accept(new RunTimeConfigurationDefaultBuildItem(propName, src.getValue(propName, String.class))); + } + } finally { + ExpandingConfigSource.setExpanding(old); + } + + // produce the config objects + runTimeConfigConsumer.accept(new RunTimeConfigurationBuildItem(runTimeConfig)); + buildTimeRunTimeConfigConsumer.accept(new BuildTimeRunTimeFixedConfigurationBuildItem(buildTimeRunTimeConfig)); + buildTimeConfigConsumer.accept(new BuildTimeConfigurationBuildItem(buildTimeConfig)); + } + + @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))); + } } @SuppressWarnings("unchecked") private static void withConverterHelper(final SmallRyeConfigBuilder builder, final Class type, final int priority, final Class> converterClass) { try { - builder.withConverter(type, priority, ((Class>) converterClass).newInstance()); + builder.withConverter(type, priority, + ((Class>) converterClass).getDeclaredConstructor().newInstance()); } catch (InstantiationException e) { throw toError(e); } catch (IllegalAccessException e) { throw toError(e); + } catch (NoSuchMethodException e) { + throw toError(e); + } catch (InvocationTargetException e) { + try { + throw e.getCause(); + } catch (RuntimeException | Error e2) { + throw e2; + } catch (Throwable t) { + throw new UndeclaredThrowableException(t); + } } + // Constructor.newInstance() can also throw an IllegalArgumentException, + // or a SecurityException, both of which already are RuntimeException, + // so we do not catch those and just let them propagate. } /** @@ -217,7 +373,8 @@ void setUpConfigFile(BuildProducer configSo @BuildStep RunTimeConfigurationSourceBuildItem writeDefaults( List defaults, - Consumer resourceConsumer) throws IOException { + Consumer resourceConsumer, + Consumer niResourceConsumer) throws IOException { final Properties properties = new Properties(); for (RunTimeConfigurationDefaultBuildItem item : defaults) { final String key = item.getKey(); @@ -239,6 +396,8 @@ RunTimeConfigurationSourceBuildItem writeDefaults( osw.flush(); resourceConsumer.accept( new GeneratedResourceBuildItem(DefaultConfigSource.DEFAULT_CONFIG_PROPERTIES_NAME, os.toByteArray())); + niResourceConsumer.accept( + new SubstrateResourceBuildItem(DefaultConfigSource.DEFAULT_CONFIG_PROPERTIES_NAME)); } } return new RunTimeConfigurationSourceBuildItem(DefaultConfigSource.class.getName(), OptionalInt.empty()); @@ -247,60 +406,126 @@ RunTimeConfigurationSourceBuildItem writeDefaults( /** * Generate the bytecode to load configuration objects at static init and run time. * - * @param configurationBuildItem the config build item + * @param runTimeConfigItem the config build item * @param classConsumer the consumer of generated classes * @param runTimeInitConsumer the consumer of runtime init classes */ @BuildStep void finalizeConfigLoader( - ConfigurationBuildItem configurationBuildItem, + RunTimeConfigurationBuildItem runTimeConfigItem, + BuildTimeRunTimeFixedConfigurationBuildItem buildTimeRunTimeConfigItem, Consumer classConsumer, - Consumer runTimeInitConsumer, + Consumer runTimeInitConsumer, Consumer objectLoaderConsumer, List converters, List runTimeSources) { final ClassOutput classOutput = new ClassOutput() { public void write(final String name, final byte[] data) { - classConsumer.accept(new GeneratedClassBuildItem(false, name, data)); + classConsumer.accept(new GeneratedClassBuildItem(true, name, data)); } }; - // Get the set of run time and static init leaf keys - final ConfigDefinition configDefinition = configurationBuildItem.getConfigDefinition(); - - final ConfigPatternMap allLeafPatterns = configDefinition.getLeafPatterns(); - final ConfigPatternMap runTimePatterns = new ConfigPatternMap<>(); - final ConfigPatternMap staticInitPatterns = new ConfigPatternMap<>(); - for (String childName : allLeafPatterns.childNames()) { - ConfigPhase phase = configDefinition.getPhaseByKey(childName); - if (phase.isReadAtMain()) { - runTimePatterns.addChild(childName, allLeafPatterns.getChild(childName)); - } - if (phase.isReadAtStaticInit()) { - staticInitPatterns.addChild(childName, allLeafPatterns.getChild(childName)); + + // 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 (LeafConfigType item : runTimePatterns) { + final Class typeClass = item.getItemClass(); + if (!typeClass.isPrimitive() && encountered.add(typeClass) + && ConverterFactory.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); + + // 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(CF_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); - AccessorFinder accessorMaker = new AccessorFinder(); + // create the actual config object + final ResultHandle config = clinit.checkCast(clinit.invokeVirtualMethod(SRCB_BUILD, builder), + SmallRyeConfig.class); - configDefinition.generateConfigRootClass(classOutput, accessorMaker); + // create the config root + clinit.writeStaticField(BUILD_TIME_CONFIG_FIELD, clinit + .newInstance(MethodDescriptor.ofConstructor(BUILD_TIME_CONFIG_ROOT, SmallRyeConfig.class), config)); - final FieldDescriptor convertersField; - // This must be a separate class, because CONFIG_HELPER is re-initialized at run time (native image). - try (final ClassCreator cc = new ClassCreator(classOutput, CONFIG_HELPER_DATA, null, Object.class.getName())) { - convertersField = cc.getFieldCreator("$CONVERTERS", Converter[].class) - .setModifiers(Opcodes.ACC_STATIC | Opcodes.ACC_VOLATILE).getFieldDescriptor(); - cc.getFieldCreator(CONFIG_ROOT_FIELD).setModifiers(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_VOLATILE) - .getFieldDescriptor(); + // write out the parsing for the stored build time config + writeParsing(cc, clinit, config, null, buildTimePatterns); + + clinit.returnValue(null); + } } - try (final ClassCreator cc = new ClassCreator(classOutput, CONFIG_HELPER, null, Object.class.getName())) { - final MethodDescriptor createAndRegisterConfig; + // 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 - // this has to be on the static init class, which is visible at both static init and execution time - try (MethodCreator carc = cc.getMethodCreator("createAndRegisterConfig", SmallRyeConfig.class)) { - carc.setModifiers(Opcodes.ACC_STATIC); + 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)); @@ -321,6 +546,12 @@ public void write(final String name, final byte[] data) { 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 for (ConfigurationCustomConverterBuildItem converter : converters) { carc.invokeVirtualMethod( SRCB_WITH_CONVERTER, @@ -329,32 +560,17 @@ public void write(final String name, final byte[] data) { carc.load(converter.getPriority()), carc.newInstance(MethodDescriptor.ofConstructor(converter.getConverter()))); } - // todo: add custom sources - final ResultHandle wrapper = carc.readStaticField(ECS_WRAPPER); - carc.invokeVirtualMethod(SRCB_WITH_WRAPPER, builder, wrapper); - - // Traverse all known config types and ensure we have converters for them when image building runs - // This code is specific to native image - - HashSet> encountered = new HashSet<>(); - ArrayList> configTypes = new ArrayList<>(); - for (LeafConfigType item : allLeafPatterns) { - final Class typeClass = item.getItemClass(); - if (!typeClass.isPrimitive() && encountered.add(typeClass)) { - configTypes.add(typeClass); - } - } - // stability - configTypes.sort(Comparator.comparing(Class::getName)); - int cnt = configTypes.size(); - // At image runtime, load the converters array and register it with the config builder - // This code is specific to native image + // property expansion + final ResultHandle cache = carc.newInstance(ECS_CACHE_CONSTRUCT); + final ResultHandle wrapper = carc.invokeStaticMethod(ECS_WRAPPER, cache); + 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(convertersField); - for (int i = 0; i < cnt; i++) { + 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, @@ -368,79 +584,161 @@ public void write(final String name, final byte[] data) { // Build the config final ResultHandle config = carc.checkCast(carc.invokeVirtualMethod(SRCB_BUILD, builder), SmallRyeConfig.class); - final ResultHandle providerResolver = carc.newInstance(SCPR_CONSTRUCT, config); - carc.invokeStaticMethod(CPR_SET_INSTANCE, providerResolver); - - // At image build time, record all the implicit converts and store them in the converters array - // This actually happens before the above `if` block, despite necessarily coming later in the method sequence - // This code is specific to native image - - final BranchResult imgBuild = carc.ifNonZero(carc.invokeStaticMethod(II_IN_IMAGE_BUILD)); - try (BytecodeCreator inImageBuild = imgBuild.trueBranch()) { - final ResultHandle array = inImageBuild.newArray(Converter.class, inImageBuild.load(cnt)); - for (int i = 0; i < cnt; i++) { - inImageBuild.writeArrayValue(array, i, inImageBuild.invokeStaticMethod(CF_GET_CONVERTER, config, - inImageBuild.loadClass(configTypes.get(i)))); - } - inImageBuild.writeStaticField(convertersField, array); - } - carc.returnValue(carc.checkCast(config, SmallRyeConfig.class)); - createAndRegisterConfig = carc.getMethodDescriptor(); - } + // 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()); - // helper to ensure the config is instantiated before it is read - try (MethodCreator getRoot = cc.getMethodCreator("getRoot", CONFIG_ROOT)) { - getRoot.setModifiers(Opcodes.ACC_STATIC | Opcodes.ACC_PUBLIC); + // create the config root + carc.writeStaticField(RUN_TIME_CONFIG_FIELD, + carc.newInstance(MethodDescriptor.ofConstructor(RUN_TIME_CONFIG_ROOT, SmallRyeConfig.class), config)); - getRoot.returnValue(getRoot.readStaticField(CONFIG_ROOT_FIELD)); + writeParsing(cc, carc, config, cache, runTimePatterns); + + carc.returnValue(null); } + } - // static init block - try (MethodCreator ccInit = cc.getMethodCreator("", void.class)) { - ccInit.setModifiers(Opcodes.ACC_STATIC); - - // write out the parsing - final BranchResult ccIfImage = ccInit.ifNonZero(ccInit - .invokeStaticMethod(MethodDescriptor.ofMethod(ImageInfo.class, "inImageRuntimeCode", boolean.class))); - try (BytecodeCreator ccIsNotImage = ccIfImage.falseBranch()) { - // common case: JVM mode, or image-building initialization - final ResultHandle mccConfig = ccIsNotImage.invokeStaticMethod(createAndRegisterConfig); - ccIsNotImage.newInstance(MethodDescriptor.ofConstructor(CONFIG_ROOT, SmallRyeConfig.class), mccConfig); - writeParsing(cc, ccIsNotImage, mccConfig, staticInitPatterns); - } - try (BytecodeCreator ccIsImage = ccIfImage.trueBranch()) { - // native image run time only (class reinitialization) - final ResultHandle mccConfig = ccIsImage.invokeStaticMethod(createAndRegisterConfig); - writeParsing(cc, ccIsImage, mccConfig, runTimePatterns); - } - ccInit.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 ConfigDefinition.RootInfo rootInfo = configDefinition.getInstanceInfo(obj); - if (rootInfo == null) + public ResultHandle load(final BytecodeCreator body, final Object obj, final 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 null; - - if (!rootInfo.getConfigPhase().isAvailableAtRun()) { - String msg = String.format( - "You are trying to use a ConfigRoot[%s] at runtime whose phase[%s] does not allow this", - rootInfo.getRootClass().getName(), rootInfo.getConfigPhase()); - throw new IllegalStateException(msg); } + final FieldDescriptor fieldDescriptor = rootInfo.getFieldDescriptor(); - final ResultHandle configRoot = body.invokeStaticMethod(GET_ROOT_METHOD); + final ResultHandle configRoot = body + .readStaticField(buildTime ? BUILD_TIME_CONFIG_FIELD : RUN_TIME_CONFIG_FIELD); return body.readInstanceField(fieldDescriptor, configRoot); } })); - runTimeInitConsumer.accept(new RuntimeReinitializedClassBuildItem(CONFIG_HELPER)); + 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 ConfigPatternMap keyMap) { + final ResultHandle cache, final ConfigPatternMap keyMap) { // setup // Iterable iterable = config.getPropertyNames(); final ResultHandle iterable = body.invokeVirtualMethod( @@ -467,9 +765,17 @@ private void writeParsing(final ClassCreator cc, final BytecodeCreator body, fin .continueScope(loop); // keyIter.next(); // skip "quarkus" hasNext.invokeVirtualMethod(NI_NEXT, keyIter); - // parse(config, keyIter); - hasNext.invokeStaticMethod(generateParserBody(cc, keyMap, new StringBuilder("parseKey"), new HashMap<>()), - config, 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); } @@ -480,22 +786,30 @@ private void writeParsing(final ClassCreator cc, final BytecodeCreator body, fin } private MethodDescriptor generateParserBody(final ClassCreator cc, final ConfigPatternMap keyMap, - final StringBuilder methodName, final Map parseMethodCache) { + final StringBuilder methodName, final Map parseMethodCache, final boolean expand) { final String methodNameStr = methodName.toString(); final MethodDescriptor existing = parseMethodCache.get(methodNameStr); - if (existing != null) + if (existing != null) { return existing; - try (MethodCreator body = cc.getMethodCreator(methodName.toString(), void.class, SmallRyeConfig.class, - NameIterator.class)) { + } + 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 keyIter = body.getMethodParam(1); + 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, config); + matched.generateAcceptConfigurationValue(matchedBody, keyIter, cache, config); } else { // todo: unknown name warning goes here } @@ -519,8 +833,15 @@ private MethodDescriptor generateParserBody(final ClassCreator cc, final ConfigP // (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), config, keyIter); + generateParserBody(cc, keyMap.getChild(name), methodName, parseMethodCache, expand), + args); methodName.setLength(length); // return; nameMatched.returnValue(null); @@ -537,9 +858,16 @@ private MethodDescriptor generateParserBody(final ClassCreator cc, final ConfigP // (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), - config, keyIter); + generateParserBody(cc, keyMap.getChild(ConfigPatternMap.WILD_CARD), methodName, parseMethodCache, + expand), + args); methodName.setLength(length); // return; matchedBody.returnValue(null); 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 59be7ea0fccdc..af95a00ca2e7e 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 @@ -26,8 +26,8 @@ import java.util.stream.Collectors; import org.graalvm.nativeimage.ImageInfo; -import org.jboss.builder.Version; +import io.quarkus.builder.Version; import io.quarkus.deployment.ClassOutput; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; @@ -188,9 +188,18 @@ MainClassBuildItem build(List staticInitTasks, startupContext = mv.readStaticField(scField.getFieldDescriptor()); tryBlock = mv.tryBlock(); + + // Load the run time configuration + tryBlock.invokeStaticMethod(ConfigurationSetup.CREATE_RUN_TIME_CONFIG); + for (MainBytecodeRecorderBuildItem holder : mainMethod) { final BytecodeRecorderImpl recorder = holder.getBytecodeRecorder(); if (!recorder.isEmpty()) { + // Register substitutions in all recorders + for (ObjectSubstitutionBuildItem sub : substitutions) { + ObjectSubstitutionBuildItem.Holder holder1 = sub.holder; + recorder.registerSubstitution(holder1.from, holder1.to, holder1.substitution); + } for (BytecodeRecorderObjectLoaderBuildItem item : loaders) { recorder.registerObjectLoader(item.getObjectLoader()); } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/ReflectionDiagnosticProcessor.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/ReflectionDiagnosticProcessor.java index d9ed8b0b13da8..385652f0d1226 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/ReflectionDiagnosticProcessor.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/ReflectionDiagnosticProcessor.java @@ -1,13 +1,16 @@ package io.quarkus.deployment.steps; import java.nio.charset.StandardCharsets; -import java.util.HashSet; +import java.util.Arrays; +import java.util.Collection; import java.util.List; -import java.util.Set; +import java.util.stream.Collectors; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.GeneratedResourceBuildItem; import io.quarkus.deployment.builditem.substrate.ReflectiveClassBuildItem; +import io.quarkus.deployment.builditem.substrate.ReflectiveFieldBuildItem; +import io.quarkus.deployment.builditem.substrate.ReflectiveMethodBuildItem; /** * writes a list of all reflective classes to META-INF, if the quarkus.debug.reflection system property is set @@ -18,20 +21,34 @@ public class ReflectionDiagnosticProcessor { @BuildStep - public GeneratedResourceBuildItem writeReflectionData(List classes) { + public List writeReflectionData( + List classes, + List methods, + List fields) { if (Boolean.getBoolean("quarkus.debug.reflection")) { - StringBuilder sb = new StringBuilder(); - Set seen = new HashSet<>(); - for (ReflectiveClassBuildItem i : classes) { - for (String j : i.getClassNames()) { - if (seen.add(j)) { - sb.append(j); - sb.append("\n"); - } - } - } - return new GeneratedResourceBuildItem("META-INF/reflective-classes.txt", - sb.toString().getBytes(StandardCharsets.UTF_8)); + String classNames = classes.stream() + .map(ReflectiveClassBuildItem::getClassNames) + .flatMap(Collection::stream) + .sorted() + .distinct() + .collect(Collectors.joining("\n", "", "\n")); + String methodNames = methods.stream() + .map(m -> m.getDeclaringClass() + "#" + m.getName() + "(" + String.join(",", m.getParams()) + ")") + .sorted() + .distinct() + .collect(Collectors.joining("\n", "", "\n")); + String fieldsNames = fields.stream() + .map(m -> m.getDeclaringClass() + "#" + m.getName()) + .sorted() + .distinct() + .collect(Collectors.joining("\n", "", "\n")); + return Arrays.asList( + new GeneratedResourceBuildItem("META-INF/reflective-classes.txt", + classNames.getBytes(StandardCharsets.UTF_8)), + new GeneratedResourceBuildItem("META-INF/reflective-methods.txt", + methodNames.getBytes(StandardCharsets.UTF_8)), + new GeneratedResourceBuildItem("META-INF/reflective-fields.txt", + fieldsNames.getBytes(StandardCharsets.UTF_8))); } else { return null; } 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 ffca6a4f7c10d..8ff4e9f085004 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 @@ -70,7 +70,8 @@ public void build() throws Exception { String unindexedClassesWarn = unindexedClasses.stream().map(d -> "\t- " + d).collect(Collectors.joining("\n")); log.warnf( "Unable to properly register the hierarchy of the following classes for reflection as they are not in the Jandex index:%n%s" - + "%nConsider adding them to the index either by creating a Jandex index for your dependency or via quarkus.index-dependency properties.", + + "%nConsider adding them to the index either by creating a Jandex index " + + "for your dependency via the Maven plugin, an empty META-INF/beans.xml or quarkus.index-dependency properties.\");.", unindexedClassesWarn); } } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/SubstrateAutoFeatureStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/SubstrateAutoFeatureStep.java index 2007132326f27..d4c726ae2ab70 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/SubstrateAutoFeatureStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/SubstrateAutoFeatureStep.java @@ -95,31 +95,6 @@ SubstrateOutputBuildItem generateFeature(ClassOutputBuildItem output, ResultHandle thisClass = overallCatch.loadClass(GRAAL_AUTOFEATURE); ResultHandle cl = overallCatch.invokeVirtualMethod(ofMethod(Class.class, "getClassLoader", ClassLoader.class), thisClass); - // FIXME: probably those two hard-coded should be produced by some build step - { - TryBlock tc = overallCatch.tryBlock(); - ResultHandle clazz = tc.invokeStaticMethod( - ofMethod(Class.class, "forName", Class.class, String.class, boolean.class, ClassLoader.class), - tc.load("org.wildfly.common.net.HostName"), tc.load(false), cl); - tc.writeArrayValue(array, 0, clazz); - tc.invokeStaticMethod(MethodDescriptor.ofMethod("org.graalvm.nativeimage.RuntimeClassInitialization", - "rerunClassInitialization", void.class, Class[].class), array); - - CatchBlockCreator cc = tc.addCatch(Throwable.class); - cc.invokeVirtualMethod(ofMethod(Throwable.class, "printStackTrace", void.class), cc.getCaughtException()); - } - { - TryBlock tc = overallCatch.tryBlock(); - ResultHandle clazz = tc.invokeStaticMethod( - ofMethod(Class.class, "forName", Class.class, String.class, boolean.class, ClassLoader.class), - tc.load("org.wildfly.common.os.Process"), tc.load(false), cl); - tc.writeArrayValue(array, 0, clazz); - tc.invokeStaticMethod(MethodDescriptor.ofMethod("org.graalvm.nativeimage.RuntimeClassInitialization", - "rerunClassInitialization", void.class, Class[].class), array); - - CatchBlockCreator cc = tc.addCatch(Throwable.class); - cc.invokeVirtualMethod(ofMethod(Throwable.class, "printStackTrace", void.class), cc.getCaughtException()); - } for (String i : runtimeReinitializedClassBuildItems.stream().map(RuntimeReinitializedClassBuildItem::getClassName) .collect(Collectors.toList())) { TryBlock tc = overallCatch.tryBlock(); @@ -192,7 +167,7 @@ SubstrateOutputBuildItem generateFeature(ClassOutputBuildItem output, final Map reflectiveClasses = new LinkedHashMap<>(); for (ReflectiveClassBuildItem i : reflectiveClassBuildItems) { - addReflectiveClass(reflectiveClasses, i.isConstructors(), i.isMethods(), i.isFields(), + addReflectiveClass(reflectiveClasses, i.isConstructors(), i.isMethods(), i.isFields(), i.areFinalFieldsWritable(), i.getClassNames().toArray(new String[0])); } for (ReflectiveFieldBuildItem i : reflectiveFields) { @@ -203,7 +178,8 @@ SubstrateOutputBuildItem generateFeature(ClassOutputBuildItem output, } for (ServiceProviderBuildItem i : serviceProviderBuildItems) { - addReflectiveClass(reflectiveClasses, true, false, false, i.providers().toArray(new String[] {})); + addReflectiveClass(reflectiveClasses, true, false, false, false, + i.providers().toArray(new String[] {})); } for (Map.Entry entry : reflectiveClasses.entrySet()) { @@ -271,7 +247,9 @@ SubstrateOutputBuildItem generateFeature(ClassOutputBuildItem output, } if (entry.getValue().fields) { tc.invokeStaticMethod( - ofMethod("org/graalvm/nativeimage/RuntimeReflection", "register", void.class, Field[].class), fields); + ofMethod("org/graalvm/nativeimage/RuntimeReflection", "register", void.class, + boolean.class, Field[].class), + tc.load(entry.getValue().finalFieldsWritable), fields); } else if (!entry.getValue().fieldSet.isEmpty()) { ResultHandle farray = tc.newArray(Field.class, tc.load(1)); for (String field : entry.getValue().fieldSet) { @@ -301,7 +279,7 @@ public void addReflectiveMethod(Map reflectiveClasses, R String cl = methodInfo.getDeclaringClass(); ReflectionInfo existing = reflectiveClasses.get(cl); if (existing == null) { - reflectiveClasses.put(cl, existing = new ReflectionInfo(false, false, false)); + reflectiveClasses.put(cl, existing = new ReflectionInfo(false, false, false, false)); } if (methodInfo.getName().equals("")) { existing.ctorSet.add(methodInfo); @@ -311,12 +289,12 @@ public void addReflectiveMethod(Map reflectiveClasses, R } public void addReflectiveClass(Map reflectiveClasses, boolean constructors, boolean method, - boolean fields, + boolean fields, boolean finalFieldsWritable, String... className) { for (String cl : className) { ReflectionInfo existing = reflectiveClasses.get(cl); if (existing == null) { - reflectiveClasses.put(cl, new ReflectionInfo(constructors, method, fields)); + reflectiveClasses.put(cl, new ReflectionInfo(constructors, method, fields, finalFieldsWritable)); } else { if (constructors) { existing.constructors = true; @@ -335,7 +313,7 @@ public void addReflectiveField(Map reflectiveClasses, Re String cl = fieldInfo.getDeclaringClass(); ReflectionInfo existing = reflectiveClasses.get(cl); if (existing == null) { - reflectiveClasses.put(cl, existing = new ReflectionInfo(false, false, false)); + reflectiveClasses.put(cl, existing = new ReflectionInfo(false, false, false, false)); } existing.fieldSet.add(fieldInfo.getName()); } @@ -344,14 +322,16 @@ static final class ReflectionInfo { boolean constructors; boolean methods; boolean fields; + boolean finalFieldsWritable; Set fieldSet = new HashSet<>(); Set methodSet = new HashSet<>(); Set ctorSet = new HashSet<>(); - private ReflectionInfo(boolean constructors, boolean methods, boolean fields) { + private ReflectionInfo(boolean constructors, boolean methods, boolean fields, boolean finalFieldsWritable) { this.methods = methods; this.fields = fields; this.constructors = constructors; + this.finalFieldsWritable = finalFieldsWritable; } } } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/SubstrateConfigBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/SubstrateConfigBuildStep.java index f5367dfb75442..a50d9e5b20b40 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/SubstrateConfigBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/SubstrateConfigBuildStep.java @@ -28,6 +28,8 @@ 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.ExtensionSslNativeSupportBuildItem; import io.quarkus.deployment.builditem.JavaLibraryPathAdditionalPathBuildItem; import io.quarkus.deployment.builditem.SslNativeConfigBuildItem; @@ -39,14 +41,19 @@ import io.quarkus.deployment.builditem.substrate.SubstrateProxyDefinitionBuildItem; import io.quarkus.deployment.builditem.substrate.SubstrateResourceBundleBuildItem; import io.quarkus.deployment.builditem.substrate.SubstrateSystemPropertyBuildItem; +import io.quarkus.runtime.ssl.SslContextConfigurationTemplate; //TODO: this should go away, once we decide on which one of the API's we want class SubstrateConfigBuildStep { private static final Logger log = Logger.getLogger(SubstrateConfigBuildStep.class); + private static final String LIB_SUN_EC = "libsunec.so"; + @BuildStep - void build(List substrateConfigBuildItems, + @Record(ExecutionTime.STATIC_INIT) + void build(SslContextConfigurationTemplate sslContextConfigurationTemplate, + List substrateConfigBuildItems, SslNativeConfigBuildItem sslNativeConfig, List extensionSslNativeSupport, BuildProducer proxy, @@ -77,6 +84,10 @@ void build(List substrateConfigBuildItems, Boolean sslNativeEnabled = isSslNativeEnabled(sslNativeConfig, extensionSslNativeSupport); + // For now, we enable SSL native if it hasn't been explicitly disabled + // it's probably overly conservative but it's a first step in the right direction + sslContextConfigurationTemplate.setSslNativeEnabled(!sslNativeConfig.isExplicitlyDisabled()); + if (sslNativeEnabled) { // This is an ugly hack but for now it's the only way to make the SunEC library // available to the native image. @@ -89,11 +100,12 @@ void build(List substrateConfigBuildItems, if (graalVmHome != null) { Path graalVmLibDirectory = Paths.get(graalVmHome, "jre", "lib"); Path linuxLibDirectory = graalVmLibDirectory.resolve("amd64"); + Path linuxPath = linuxLibDirectory.resolve(LIB_SUN_EC); // We add . as it might be useful in a containerized world // FIXME: it seems GraalVM does not support having multiple paths in java.library.path //javaLibraryPathAdditionalPath.produce(new JavaLibraryPathAdditionalPathBuildItem(".")); - if (Files.exists(linuxLibDirectory)) { + if (Files.exists(linuxPath)) { // On Linux, the SunEC library is in jre/lib/amd64/ // This is useful for testing or if you have a similar environment in production javaLibraryPathAdditionalPath diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/ThreadPoolSetup.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/ThreadPoolSetup.java index 28920171b6a87..872307136325c 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/ThreadPoolSetup.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/ThreadPoolSetup.java @@ -20,20 +20,24 @@ import io.quarkus.deployment.annotations.ExecutionTime; import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.builditem.ExecutorBuildItem; +import io.quarkus.deployment.builditem.LaunchModeBuildItem; import io.quarkus.deployment.builditem.ShutdownContextBuildItem; import io.quarkus.deployment.builditem.substrate.RuntimeInitializedClassBuildItem; import io.quarkus.runtime.ExecutorTemplate; +import io.quarkus.runtime.ThreadPoolConfig; /** + * */ public class ThreadPoolSetup { @BuildStep @Record(value = ExecutionTime.RUNTIME_INIT, optional = true) - public ExecutorBuildItem createExecutor(ExecutorTemplate setupTemplate, ShutdownContextBuildItem shutdownContextBuildItem) { - return new ExecutorBuildItem(setupTemplate.setupRunTime(shutdownContextBuildItem, - // build time default config constants - static method calls are not proxied - ExecutorTemplate.getIntConfigVal(ExecutorTemplate.CORE_POOL_SIZE,-1),ExecutorTemplate.getIntConfigVal(ExecutorTemplate.MAX_POOL_SIZE,-1),ExecutorTemplate.getIntConfigVal(ExecutorTemplate.QUEUE_SIZE,Integer.MAX_VALUE),ExecutorTemplate.getFloatConfigVal(ExecutorTemplate.GROWTH_RESISTANCE,0f),ExecutorTemplate.getIntConfigVal(ExecutorTemplate.KEEP_ALIVE_MILLIS,60_000))); + public ExecutorBuildItem createExecutor(ExecutorTemplate setupTemplate, ShutdownContextBuildItem shutdownContextBuildItem, + LaunchModeBuildItem launchModeBuildItem, + ThreadPoolConfig threadPoolConfig) { + return new ExecutorBuildItem( + setupTemplate.setupRunTime(shutdownContextBuildItem, threadPoolConfig, launchModeBuildItem.getLaunchMode())); } @BuildStep diff --git a/core/deployment/src/main/java/io/quarkus/deployment/test/TestScopeSetup.java b/core/deployment/src/main/java/io/quarkus/deployment/test/TestScopeSetup.java new file mode 100644 index 0000000000000..3663d099a3e53 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/test/TestScopeSetup.java @@ -0,0 +1,8 @@ +package io.quarkus.deployment.test; + +public interface TestScopeSetup { + + void setup(); + + void tearDown(); +} 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 new file mode 100644 index 0000000000000..110c01293725c --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/util/FileUtil.java @@ -0,0 +1,39 @@ +package io.quarkus.deployment.util; + +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; + +public class FileUtil { + + public static void deleteDirectory(final Path directory) throws IOException { + if (!Files.isDirectory(directory)) { + return; + } + Files.walkFileTree(directory, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + try { + Files.delete(file); + } catch (IOException e) { + // ignored + } + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { + try { + Files.delete(dir); + } catch (IOException e) { + // ignored + } + return FileVisitResult.CONTINUE; + } + + }); + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/util/ServiceUtil.java b/core/deployment/src/main/java/io/quarkus/deployment/util/ServiceUtil.java index 133b08967ba0b..49b7d67984c55 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/util/ServiceUtil.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/util/ServiceUtil.java @@ -2,11 +2,15 @@ import java.io.BufferedInputStream; import java.io.BufferedReader; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URL; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; @@ -39,20 +43,7 @@ public static Set classNamesNamedIn(ClassLoader classLoader, String file try (BufferedInputStream bis = new BufferedInputStream(is)) { try (InputStreamReader isr = new InputStreamReader(bis, StandardCharsets.UTF_8)) { try (BufferedReader br = new BufferedReader(isr)) { - String line; - while ((line = br.readLine()) != null) { - int commentMarkerIndex = line.indexOf('#'); - if (commentMarkerIndex > 0) { - line = line.substring(commentMarkerIndex); - } - line = line.trim(); - - if (line.isEmpty()) { - continue; - } - - classNames.add(line); - } + readStream(classNames, br); } } } @@ -61,4 +52,35 @@ public static Set classNamesNamedIn(ClassLoader classLoader, String file return Collections.unmodifiableSet(classNames); } + + public static Set classNamesNamedIn(Path path) throws IOException { + final Set set = new LinkedHashSet<>(); + if (!Files.exists(path)) { + return Collections.emptySet(); + } + try (BufferedReader br = Files.newBufferedReader(path, StandardCharsets.UTF_8)) { + readStream(set, br); + } catch (NoSuchFileException | FileNotFoundException e) { + // unlikely + return Collections.emptySet(); + } + return set; + } + + private static void readStream(final Set classNames, final BufferedReader br) throws IOException { + String line; + while ((line = br.readLine()) != null) { + int commentMarkerIndex = line.indexOf('#'); + if (commentMarkerIndex > 0) { + line = line.substring(commentMarkerIndex); + } + line = line.trim(); + + if (line.isEmpty()) { + continue; + } + + classNames.add(line); + } + } } 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 f7668a73694b1..360173f60a060 100644 --- a/core/deployment/src/main/java/io/quarkus/runner/RuntimeClassLoader.java +++ b/core/deployment/src/main/java/io/quarkus/runner/RuntimeClassLoader.java @@ -61,7 +61,7 @@ public class RuntimeClassLoader extends ClassLoader implements ClassOutput, Tran private volatile Map>> bytecodeTransformers = null; - private final Path applicationClasses; + private final List applicationClassDirectories; private final Path frameworkClassesPath; private final Path transformerCache; @@ -73,63 +73,48 @@ public class RuntimeClassLoader extends ClassLoader implements ClassOutput, Tran registerAsParallelCapable(); } - public RuntimeClassLoader(ClassLoader parent, Path applicationClasses, Path frameworkClassesPath, Path transformerCache) { + public RuntimeClassLoader(ClassLoader parent, List applicationClassesDirectories, Path frameworkClassesDirectory, + Path transformerCache) { super(parent); - this.applicationClasses = applicationClasses; - this.frameworkClassesPath = frameworkClassesPath; + this.applicationClassDirectories = applicationClassesDirectories; + this.frameworkClassesPath = frameworkClassesDirectory; this.transformerCache = transformerCache; } @Override public Enumeration getResources(String nm) throws IOException { - String name; - if (nm.startsWith("/")) { - name = nm.substring(1); - } else { - name = nm; - } + String name = sanitizeName(nm); + + List resources = new ArrayList<>(); // TODO: some superugly hack for bean provider - byte[] data = resources.get(name); - if (data != null) { - URL url = new URL(null, "quarkus:" + name + "/", new URLStreamHandler() { - @Override - protected URLConnection openConnection(final URL u) throws IOException { - return new URLConnection(u) { - @Override - public void connect() throws IOException { - } - - @Override - public InputStream getInputStream() throws IOException { - return new ByteArrayInputStream(resources.get(name)); - } - }; - } - }); - return Collections.enumeration(Collections.singleton(url)); + URL resource = getQuarkusResource(name); + if (resource != null) { + resources.add(resource); } URL appResource = findApplicationResource(name); if (appResource != null) { - List resources = new ArrayList<>(); resources.add(appResource); - for (Enumeration e = super.getResources(name); e.hasMoreElements();) { - resources.add(e.nextElement()); - } - return Collections.enumeration(resources); } - return super.getResources(name); + + for (Enumeration e = super.getResources(name); e.hasMoreElements();) { + resources.add(e.nextElement()); + } + + return Collections.enumeration(resources); } @Override public URL getResource(String nm) { - String name; - if (nm.startsWith("/")) { - name = nm.substring(1); - } else { - name = nm; + String name = sanitizeName(nm); + + // TODO: some superugly hack for bean provider + URL resource = getQuarkusResource(name); + if (resource != null) { + return resource; } + URL appResource = findApplicationResource(name); if (appResource != null) { return appResource; @@ -139,15 +124,18 @@ public URL getResource(String nm) { @Override public InputStream getResourceAsStream(String nm) { - String name; - if (nm.startsWith("/")) { - name = nm.substring(1); - } else { - name = nm; - } + String name = sanitizeName(nm); + byte[] data = resources.get(name); - if (data != null) + if (data != null) { return new ByteArrayInputStream(data); + } + + data = findApplicationResourceContent(name); + if (data != null) { + return new ByteArrayInputStream(data); + } + return super.getResourceAsStream(name); } @@ -157,21 +145,45 @@ protected Class loadClass(String name, boolean resolve) throws ClassNotFoundE if (ex != null) { return ex; } - if (appClasses.containsKey(name)) { + + if (appClasses.containsKey(name) + || (!frameworkClasses.contains(name) && getClassInApplicationClassPaths(name) != null)) { return findClass(name); } - if (frameworkClasses.contains(name)) { - return super.loadClass(name, resolve); + + return super.loadClass(name, resolve); + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + Class existing = findLoadedClass(name); + if (existing != null) { + return existing; } - final String fileName = name.replace('.', '/') + ".class"; - Path classLoc = applicationClasses.resolve(fileName); - if (Files.exists(classLoc)) { + byte[] bytes = appClasses.get(name); + if (bytes != null) { + try { + definePackage(name); + return defineClass(name, bytes, 0, bytes.length); + } catch (Error e) { + //potential race conditions if another thread is loading the same class + existing = findLoadedClass(name); + if (existing != null) { + return existing; + } + throw e; + } + } + + Path classLoc = getClassInApplicationClassPaths(name); + + if (classLoc != null) { CompletableFuture> res = new CompletableFuture<>(); - Future> existing = loadingClasses.putIfAbsent(name, res); - if (existing != null) { + Future> loadingClass = loadingClasses.putIfAbsent(name, res); + if (loadingClass != null) { try { - return existing.get(); + return loadingClass.get(); } catch (Exception e) { throw new ClassNotFoundException("Failed to load " + name, e); } @@ -187,8 +199,9 @@ protected Class loadClass(String name, boolean resolve) throws ClassNotFoundE } catch (IOException e) { throw new ClassNotFoundException("Failed to load class", e); } - byte[] bytes = out.toByteArray(); + bytes = out.toByteArray(); bytes = handleTransform(name, bytes); + definePackage(name); Class clazz = defineClass(name, bytes, 0, bytes.length); res.complete(clazz); return clazz; @@ -200,77 +213,8 @@ protected Class loadClass(String name, boolean resolve) throws ClassNotFoundE throw e; } } - return super.loadClass(name, resolve); - } - - private byte[] handleTransform(String name, byte[] bytes) { - if (bytecodeTransformers == null || bytecodeTransformers.isEmpty()) { - return bytes; - } - List> transformers = bytecodeTransformers.get(name); - if (transformers == null) { - return bytes; - } - - Path hashPath = null; - if (transformerCache != null) { - - try { - MessageDigest md = MessageDigest.getInstance("MD5"); - byte[] thedigest = md.digest(bytes); - String hash = Base64.getUrlEncoder().encodeToString(thedigest); - hashPath = transformerCache.resolve(hash); - if (Files.exists(hashPath)) { - return readFileContent(hashPath); - } - } catch (Exception e) { - log.error("Unable to load transformed class from cache", e); - } - } - ClassReader cr = new ClassReader(bytes); - ClassWriter writer = new QuarkusClassWriter(cr, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); - ClassVisitor visitor = writer; - for (BiFunction i : transformers) { - visitor = i.apply(name, visitor); - } - cr.accept(visitor, 0); - byte[] data = writer.toByteArray(); - if (hashPath != null) { - try { - - File file = hashPath.toFile(); - file.getParentFile().mkdirs(); - try (FileOutputStream out = new FileOutputStream(file)) { - out.write(data); - } - } catch (Exception e) { - log.error("Unable to write class to cache", e); - } - } - return data; - } - - @Override - protected Class findClass(String name) throws ClassNotFoundException { - Class existing = findLoadedClass(name); - if (existing != null) { - return existing; - } - byte[] bytes = appClasses.get(name); - if (bytes == null) { - throw new ClassNotFoundException(name); - } - try { - return defineClass(name, bytes, 0, bytes.length); - } catch (Error e) { - //potential race conditions if another thread is loading the same class - existing = findLoadedClass(name); - if (existing != null) { - return existing; - } - throw e; - } + throw new ClassNotFoundException(name); } @Override @@ -312,6 +256,7 @@ public void writeClass(boolean applicationClass, String className, byte[] data) } } + @Override public void setTransformers(Map>> functions) { this.bytecodeTransformers = functions; } @@ -321,7 +266,30 @@ public void writeResource(String name, byte[] data) throws IOException { resources.put(name, data); } - public static byte[] readFileContent(final Path path) throws IOException { + private void definePackage(String name) { + final String pkgName = getPackageNameFromClassName(name); + if ((pkgName != null) && getPackage(pkgName) == null) { + synchronized (getClassLoadingLock(pkgName)) { + if (getPackage(pkgName) == null) { + // this could certainly be improved to use the actual manifest + definePackage(pkgName, null, null, null, null, null, null, null); + } + } + } + } + + private String getPackageNameFromClassName(String className) { + final int index = className.lastIndexOf('.'); + if (index == -1) { + // we return null here since in this case no package is defined + // this is same behavior as Package.getPackage(clazz) exhibits + // when the class is in the default package + return null; + } + return className.substring(0, index); + } + + private static byte[] readFileContent(final Path path) { final File file = path.toFile(); final long fileLength = file.length(); if (fileLength > Integer.MAX_VALUE) { @@ -338,17 +306,136 @@ public static byte[] readFileContent(final Path path) throws IOException { out.write(buf, 0, r); } return out.toByteArray(); + } catch (IOException e) { + throw new IllegalArgumentException("Unable to read file " + path, e); + } + } + + private byte[] handleTransform(String name, byte[] bytes) { + if (bytecodeTransformers == null || bytecodeTransformers.isEmpty()) { + return bytes; + } + List> transformers = bytecodeTransformers.get(name); + if (transformers == null) { + return bytes; + } + + Path hashPath = null; + if (transformerCache != null) { + + try { + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] thedigest = md.digest(bytes); + String hash = Base64.getUrlEncoder().encodeToString(thedigest); + hashPath = transformerCache.resolve(hash); + if (Files.exists(hashPath)) { + return readFileContent(hashPath); + } + } catch (Exception e) { + log.error("Unable to load transformed class from cache", e); + } + } + + ClassReader cr = new ClassReader(bytes); + ClassWriter writer = new QuarkusClassWriter(cr, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); + ClassVisitor visitor = writer; + for (BiFunction i : transformers) { + visitor = i.apply(name, visitor); + } + cr.accept(visitor, 0); + byte[] data = writer.toByteArray(); + if (hashPath != null) { + try { + + File file = hashPath.toFile(); + file.getParentFile().mkdirs(); + try (FileOutputStream out = new FileOutputStream(file)) { + out.write(data); + } + } catch (Exception e) { + log.error("Unable to write class to cache", e); + } + } + return data; + } + + private String sanitizeName(String name) { + if (name.startsWith("/")) { + return name.substring(1); } + + return name; + } + + private Path getClassInApplicationClassPaths(String name) { + final String fileName = name.replace('.', '/') + ".class"; + Path classLocation; + for (Path i : applicationClassDirectories) { + classLocation = i.resolve(fileName); + if (Files.exists(classLocation)) { + return classLocation; + } + } + return null; } private URL findApplicationResource(String name) { - Path resourcePath = applicationClasses.resolve(name); + Path resourcePath = null; + + for (Path i : applicationClassDirectories) { + resourcePath = i.resolve(name); + if (Files.exists(resourcePath)) { + break; + } + } try { - return Files.exists(resourcePath) ? resourcePath.toUri() + return resourcePath != null && Files.exists(resourcePath) ? resourcePath.toUri() .toURL() : null; } catch (MalformedURLException e) { throw new RuntimeException(e); } } + private byte[] findApplicationResourceContent(String name) { + Path resourcePath = null; + + for (Path i : applicationClassDirectories) { + resourcePath = i.resolve(name); + if (Files.exists(resourcePath)) { + return readFileContent(resourcePath); + } + } + + return null; + } + + private URL getQuarkusResource(String name) { + byte[] data = resources.get(name); + if (data != null) { + String path = "quarkus:" + name + "/"; + + try { + URL url = new URL(null, path, new URLStreamHandler() { + @Override + protected URLConnection openConnection(final URL u) throws IOException { + return new URLConnection(u) { + @Override + public void connect() throws IOException { + } + + @Override + public InputStream getInputStream() throws IOException { + return new ByteArrayInputStream(resources.get(name)); + } + }; + } + }); + + return url; + } catch (MalformedURLException e) { + throw new IllegalArgumentException("Invalid URL: " + path); + } + } + return null; + } } 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 9dd58fed4b9f7..093484eb0843c 100644 --- a/core/deployment/src/main/java/io/quarkus/runner/RuntimeRunner.java +++ b/core/deployment/src/main/java/io/quarkus/runner/RuntimeRunner.java @@ -27,10 +27,10 @@ import java.util.function.BiFunction; import java.util.function.Consumer; -import org.jboss.builder.BuildChainBuilder; -import org.jboss.builder.BuildResult; import org.objectweb.asm.ClassVisitor; +import io.quarkus.builder.BuildChainBuilder; +import io.quarkus.builder.BuildResult; import io.quarkus.deployment.ClassOutput; import io.quarkus.deployment.QuarkusAugmentor; import io.quarkus.deployment.builditem.ApplicationClassNameBuildItem; @@ -59,7 +59,10 @@ public RuntimeRunner(Builder builder) { this.chainCustomizers = new ArrayList<>(builder.chainCustomizers); this.launchMode = builder.launchMode; if (builder.classOutput == null) { - RuntimeClassLoader runtimeClassLoader = new RuntimeClassLoader(builder.classLoader, target, + List allPaths = new ArrayList<>(); + allPaths.add(target); + allPaths.addAll(builder.additionalHotDeploymentPaths); + RuntimeClassLoader runtimeClassLoader = new RuntimeClassLoader(builder.classLoader, allPaths, builder.frameworkClassesPath, builder.transformerCache); this.loader = runtimeClassLoader; this.classOutput = runtimeClassLoader; @@ -146,6 +149,10 @@ public static class Builder { private Path transformerCache; private LaunchMode launchMode = LaunchMode.NORMAL; private final List additionalArchives = new ArrayList<>(); + /** + * additional classes directories that may be hot deployed + */ + private final List additionalHotDeploymentPaths = new ArrayList<>(); private final List> chainCustomizers = new ArrayList<>(); private ClassOutput classOutput; private TransformerTarget transformerTarget; @@ -175,6 +182,11 @@ public Builder addAdditionalArchive(Path additionalArchive) { return this; } + public Builder addAdditionalHotDeploymentPath(Path additionalPath) { + this.additionalHotDeploymentPaths.add(additionalPath); + return this; + } + public Builder addAdditionalArchives(Collection additionalArchive) { this.additionalArchives.addAll(additionalArchives); return this; diff --git a/core/devmode/pom.xml b/core/devmode/pom.xml index ef2345c3ea92c..49452486a4c6a 100644 --- a/core/devmode/pom.xml +++ b/core/devmode/pom.xml @@ -32,7 +32,7 @@ io.quarkus - quarkus-core + quarkus-core-deployment org.jboss.logmanager 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 f42c62fb59b96..d1f50f772d61e 100644 --- a/core/devmode/src/main/java/io/quarkus/dev/ClassLoaderCompiler.java +++ b/core/devmode/src/main/java/io/quarkus/dev/ClassLoaderCompiler.java @@ -16,21 +16,18 @@ package io.quarkus.dev; -import java.io.BufferedReader; import java.io.File; import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; import java.net.MalformedURLException; -import java.net.URI; -import java.net.URISyntaxException; import java.net.URL; import java.net.URLClassLoader; +import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Deque; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -39,6 +36,8 @@ import java.util.jar.JarFile; import java.util.jar.Manifest; +import org.jboss.logging.Logger; + /** * Class that handles compilation of source files * @@ -46,12 +45,18 @@ */ public class ClassLoaderCompiler { - public static final String DEV_MODE_CLASS_PATH = "META-INF/dev-mode-class-path.txt"; + private static final Logger log = Logger.getLogger(ClassLoaderCompiler.class); + private final List compilationProviders; - private final CompilationProvider.Context compilationContext; + /** + * map of compilation contexts to source directories + */ + private final Map compilationContexts = new HashMap<>(); private final Set allHandledExtensions; - public ClassLoaderCompiler(ClassLoader classLoader, File outputDirectory, List compilationProviders) + public ClassLoaderCompiler(ClassLoader classLoader, + List compilationProviders, + DevModeContext context) throws IOException { this.compilationProviders = compilationProviders; @@ -64,29 +69,30 @@ public ClassLoaderCompiler(ClassLoader classLoader, File outputDirectory, List parsedFiles = new HashSet<>(); Deque toParse = new ArrayDeque<>(); for (URL url : urls) { - toParse.add(new File(url.getPath()).getAbsolutePath()); + toParse.add(new File(URLDecoder.decode(url.getPath(), StandardCharsets.UTF_8.name())).getAbsolutePath()); } Set classPathElements = new HashSet<>(); - classPathElements.add(outputDirectory); + for (DevModeContext.ModuleInfo i : context.getModules()) { + if (i.getClassesPath() != null) { + classPathElements.add(new File(i.getClassesPath())); + } + } while (!toParse.isEmpty()) { String s = toParse.poll(); if (!parsedFiles.contains(s)) { parsedFiles.add(s); File file = new File(s); - if (file.exists() && file.getName().endsWith(".jar")) { + if (!file.exists()) { + continue; + } + if (file.isDirectory()) { + classPathElements.add(file); + } else if (file.getName().endsWith(".jar")) { classPathElements.add(file); if (!file.isDirectory() && file.getName().endsWith(".jar")) { try (JarFile jar = new JarFile(file)) { @@ -116,11 +122,20 @@ public ClassLoaderCompiler(ClassLoader classLoader, File outputDirectory, List(); for (CompilationProvider compilationProvider : compilationProviders) { - allHandledExtensions.add(compilationProvider.handledExtension()); + allHandledExtensions.addAll(compilationProvider.handledExtensions()); } } @@ -128,10 +143,11 @@ public Set allHandledExtensions() { return allHandledExtensions; } - public void compile(Map> extensionToChangedFiles) { + public void compile(String sourceDir, Map> extensionToChangedFiles) { + CompilationProvider.Context compilationContext = compilationContexts.get(sourceDir); for (String extension : extensionToChangedFiles.keySet()) { for (CompilationProvider compilationProvider : compilationProviders) { - if (extension.equals(compilationProvider.handledExtension())) { + if (compilationProvider.handledExtensions().contains(extension)) { compilationProvider.compile(extensionToChangedFiles.get(extension), compilationContext); break; } 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 33f1fdb09c25a..fa6e256b2e149 100644 --- a/core/devmode/src/main/java/io/quarkus/dev/CompilationProvider.java +++ b/core/devmode/src/main/java/io/quarkus/dev/CompilationProvider.java @@ -5,7 +5,7 @@ public interface CompilationProvider { - String handledExtension(); + Set handledExtensions(); void compile(Set files, Context context); diff --git a/core/devmode/src/main/java/io/quarkus/dev/DevModeContext.java b/core/devmode/src/main/java/io/quarkus/dev/DevModeContext.java new file mode 100644 index 0000000000000..e514d40a98f1b --- /dev/null +++ b/core/devmode/src/main/java/io/quarkus/dev/DevModeContext.java @@ -0,0 +1,64 @@ +package io.quarkus.dev; + +import java.io.Serializable; +import java.net.URL; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Object that is used to pass context data from the plugin doing the invocation + * into the dev mode process using java serialization. + * + * There is no need to worry about compat as both sides will always be using the same version + */ +public class DevModeContext implements Serializable { + + private final List classPath = new ArrayList<>(); + private final List modules = new ArrayList<>(); + private final Map systemProperties = new HashMap<>(); + + public List getClassPath() { + return classPath; + } + + public List getModules() { + return modules; + } + + public Map getSystemProperties() { + return systemProperties; + } + + public static class ModuleInfo implements Serializable { + private final String name; + private final String sourcePath; + private final String classesPath; + private final String resourcePath; + + public ModuleInfo(String name, String sourcePath, String classesPath, String resourcePath) { + this.name = name; + this.sourcePath = sourcePath; + this.classesPath = classesPath; + this.resourcePath = resourcePath; + } + + public String getName() { + return name; + } + + public String getSourcePath() { + return sourcePath; + } + + public String getClassesPath() { + return classesPath; + } + + public String getResourcePath() { + return resourcePath; + } + } + +} 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 e3282235fa0fb..021002527b970 100644 --- a/core/devmode/src/main/java/io/quarkus/dev/DevModeMain.java +++ b/core/devmode/src/main/java/io/quarkus/dev/DevModeMain.java @@ -17,18 +17,33 @@ package io.quarkus.dev; import java.io.Closeable; +import java.io.DataInputStream; import java.io.File; import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; 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.List; +import java.util.Map; +import java.util.concurrent.locks.LockSupport; +import java.util.function.Consumer; +import java.util.logging.Handler; -import org.eclipse.microprofile.config.Config; import org.jboss.logging.Logger; +import io.quarkus.builder.BuildChainBuilder; +import io.quarkus.builder.BuildContext; +import io.quarkus.builder.BuildStep; +import io.quarkus.deployment.builditem.ApplicationClassPredicateBuildItem; import io.quarkus.runner.RuntimeRunner; import io.quarkus.runtime.LaunchMode; import io.quarkus.runtime.Timing; -import io.smallrye.config.PropertiesConfigSource; +import io.quarkus.runtime.logging.InitialConfigurator; import io.smallrye.config.SmallRyeConfigProviderResolver; /** @@ -36,6 +51,7 @@ */ public class DevModeMain { + public static final String DEV_MODE_CONTEXT = "META-INF/dev-mode-context.dat"; private static final Logger log = Logger.getLogger(DevModeMain.class); private static volatile ClassLoader currentAppClassLoader; @@ -43,23 +59,34 @@ public class DevModeMain { private static File classesRoot; private static File wiringDir; private static File cacheDir; + private static DevModeContext context; - private static Closeable closeable; + private static Closeable runner; static volatile Throwable deploymentProblem; static RuntimeUpdatesProcessor runtimeUpdatesProcessor; public static void main(String... args) throws Exception { - Timing.staticInitStarted(); + try (InputStream devModeCp = DevModeMain.class.getClassLoader().getResourceAsStream(DEV_MODE_CONTEXT)) { + context = (DevModeContext) new ObjectInputStream(new DataInputStream(devModeCp)).readObject(); + } catch (Exception e) { + throw new RuntimeException(e); + } + //propagate system props + for (Map.Entry i : context.getSystemProperties().entrySet()) { + if (!System.getProperties().containsKey(i.getKey())) { + System.setProperty(i.getKey(), i.getValue()); + } + } //the path that contains the compiled classes classesRoot = new File(args[0]); wiringDir = new File(args[1]); cacheDir = new File(args[2]); - runtimeUpdatesProcessor = RuntimeCompilationSetup.setup(); + runtimeUpdatesProcessor = RuntimeCompilationSetup.setup(context); if (runtimeUpdatesProcessor != null) { - runtimeUpdatesProcessor.scanForChangedClasses(); + runtimeUpdatesProcessor.checkForChangedClasses(); } //TODO: we can't handle an exception on startup with hot replacement, as Undertow might not have started @@ -68,9 +95,9 @@ public static void main(String... args) throws Exception { @Override public void run() { synchronized (DevModeMain.class) { - if (closeable != null) { + if (runner != null) { try { - closeable.close(); + runner.close(); } catch (IOException e) { e.printStackTrace(); } @@ -85,6 +112,8 @@ public void run() { } } }, "Quarkus Shutdown Thread")); + + LockSupport.park(); } private static synchronized void doStart() { @@ -95,15 +124,40 @@ private static synchronized void doStart() { //we can potentially throw away this class loader, and reload the app try { Thread.currentThread().setContextClassLoader(runtimeCl); - RuntimeRunner runner = RuntimeRunner.builder() + RuntimeRunner.Builder builder = RuntimeRunner.builder() .setLaunchMode(LaunchMode.DEVELOPMENT) .setClassLoader(runtimeCl) .setTarget(classesRoot.toPath()) .setFrameworkClassesPath(wiringDir.toPath()) - .setTransformerCache(cacheDir.toPath()) + .setTransformerCache(cacheDir.toPath()); + + List addAdditionalHotDeploymentPaths = new ArrayList<>(); + for (DevModeContext.ModuleInfo i : context.getModules()) { + if (i.getClassesPath() != null) { + Path classesPath = Paths.get(i.getClassesPath()); + addAdditionalHotDeploymentPaths.add(classesPath); + builder.addAdditionalHotDeploymentPath(classesPath); + } + } + // Make it possible to identify wiring classes generated for classes from additional hot deployment paths + builder.addChainCustomizer(new Consumer() { + @Override + public void accept(BuildChainBuilder buildChainBuilder) { + buildChainBuilder.addBuildStep(new BuildStep() { + @Override + public void execute(BuildContext context) { + context.produce(new ApplicationClassPredicateBuildItem(n -> { + return getClassInApplicationClassPaths(n, addAdditionalHotDeploymentPaths) != null; + })); + } + }).produces(ApplicationClassPredicateBuildItem.class).build(); + } + }); + + RuntimeRunner runner = builder .build(); runner.run(); - closeable = runner; + DevModeMain.runner = runner; deploymentProblem = null; } finally { Thread.currentThread().setContextClassLoader(old); @@ -111,15 +165,20 @@ private static synchronized void doStart() { } catch (Throwable t) { deploymentProblem = t; log.error("Failed to start quarkus", t); + + // if the log handler is not activated, activate it with a default configuration to flush the messages + if (!InitialConfigurator.DELAYED_HANDLER.isActivated()) { + InitialConfigurator.DELAYED_HANDLER.setHandlers(new Handler[] { InitialConfigurator.createDefaultHandler() }); + } } } public static synchronized void restartApp() { - if (closeable != null) { + if (runner != null) { ClassLoader old = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(runtimeCl); try { - closeable.close(); + runner.close(); } catch (IOException e) { e.printStackTrace(); } finally { @@ -127,7 +186,7 @@ public static synchronized void restartApp() { } } SmallRyeConfigProviderResolver.instance().releaseConfig(SmallRyeConfigProviderResolver.instance().getConfig()); - closeable = null; + DevModeMain.runner = null; Timing.restart(); doStart(); } @@ -135,4 +194,16 @@ public static synchronized void restartApp() { public static ClassLoader getCurrentAppClassLoader() { return currentAppClassLoader; } + + private static Path getClassInApplicationClassPaths(String name, List addAdditionalHotDeploymentPaths) { + final String fileName = name.replace('.', '/') + ".class"; + Path classLocation; + for (Path i : addAdditionalHotDeploymentPaths) { + classLocation = i.resolve(fileName); + if (Files.exists(classLocation)) { + return classLocation; + } + } + return null; + } } diff --git a/core/devmode/src/main/java/io/quarkus/dev/JavaCompilationProvider.java b/core/devmode/src/main/java/io/quarkus/dev/JavaCompilationProvider.java index 3ef0d414d4828..75f885a5e1225 100644 --- a/core/devmode/src/main/java/io/quarkus/dev/JavaCompilationProvider.java +++ b/core/devmode/src/main/java/io/quarkus/dev/JavaCompilationProvider.java @@ -23,8 +23,8 @@ public class JavaCompilationProvider implements CompilationProvider { private static final List COMPILER_OPTIONS = Arrays.asList("-g", "-parameters"); @Override - public String handledExtension() { - return ".java"; + public Set handledExtensions() { + return Collections.singleton(".java"); } @Override @@ -49,7 +49,7 @@ public void compile(Set filesToCompile, Context context) { for (Diagnostic diagnostic : diagnostics.getDiagnostics()) { System.out.format("%s, line %d in %s", diagnostic.getMessage(null), diagnostic.getLineNumber(), - diagnostic.getSource().getName()); + diagnostic.getSource() == null ? "[unknown source]" : diagnostic.getSource().getName()); } } catch (IOException e) { throw new RuntimeException("Cannot close file manager", e); diff --git a/core/devmode/src/main/java/io/quarkus/dev/RuntimeCompilationSetup.java b/core/devmode/src/main/java/io/quarkus/dev/RuntimeCompilationSetup.java index 41bcc07ab9ac2..2714984dbd853 100644 --- a/core/devmode/src/main/java/io/quarkus/dev/RuntimeCompilationSetup.java +++ b/core/devmode/src/main/java/io/quarkus/dev/RuntimeCompilationSetup.java @@ -16,8 +16,6 @@ package io.quarkus.dev; -import java.io.File; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.ServiceLoader; @@ -30,25 +28,20 @@ public class RuntimeCompilationSetup { private static Logger log = Logger.getLogger(RuntimeCompilationSetup.class.getName()); - public static RuntimeUpdatesProcessor setup() throws Exception { - String classesDir = System.getProperty("quarkus.runner.classes"); - String sourcesDir = System.getProperty("quarkus.runner.sources"); - String resourcesDir = System.getProperty("quarkus.runner.resources"); - if (classesDir != null) { + public static RuntimeUpdatesProcessor setup(DevModeContext context) throws Exception { + if (!context.getModules().isEmpty()) { ServiceLoader serviceLoader = ServiceLoader.load(CompilationProvider.class); List compilationProviders = new ArrayList<>(); serviceLoader.iterator().forEachRemaining(compilationProviders::add); - ClassLoaderCompiler compiler = null; + ClassLoaderCompiler compiler; try { - compiler = new ClassLoaderCompiler(Thread.currentThread().getContextClassLoader(), new File(classesDir), - compilationProviders); + compiler = new ClassLoaderCompiler(Thread.currentThread().getContextClassLoader(), + compilationProviders, context); } catch (Exception e) { log.log(Level.SEVERE, "Failed to create compiler, runtime compilation will be unavailable", e); return null; } - RuntimeUpdatesProcessor processor = new RuntimeUpdatesProcessor(Paths.get(classesDir), - sourcesDir == null ? null : Paths.get(sourcesDir), resourcesDir == null ? null : Paths.get(resourcesDir), - compiler); + RuntimeUpdatesProcessor processor = new RuntimeUpdatesProcessor(context, compiler); for (HotReplacementSetup service : ServiceLoader.load(HotReplacementSetup.class)) { service.setupHotDeployment(processor); 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 16d6ede9d4ad4..40585c580113d 100644 --- a/core/devmode/src/main/java/io/quarkus/dev/RuntimeUpdatesProcessor.java +++ b/core/devmode/src/main/java/io/quarkus/dev/RuntimeUpdatesProcessor.java @@ -23,13 +23,16 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentSkipListSet; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -40,37 +43,51 @@ public class RuntimeUpdatesProcessor implements HotReplacementContext { - private final Path classesDir; - private final Path sourcesDir; - private final Path resourcesDir; + private final DevModeContext context; private final ClassLoaderCompiler compiler; private volatile long lastChange = System.currentTimeMillis(); private volatile Set configFilePaths = Collections.emptySet(); - private final Map configFileTimestamps = new ConcurrentHashMap<>(); + private final Map configFileTimestamps = new ConcurrentHashMap<>(); private static final Logger log = Logger.getLogger(RuntimeUpdatesProcessor.class.getPackage().getName()); + private final List preScanSteps = new CopyOnWriteArrayList<>(); - public RuntimeUpdatesProcessor(Path classesDir, Path sourcesDir, Path resourcesDir, ClassLoaderCompiler compiler) { - this.classesDir = classesDir; - this.sourcesDir = sourcesDir; - this.resourcesDir = resourcesDir; + public RuntimeUpdatesProcessor(DevModeContext context, ClassLoaderCompiler compiler) { + this.context = context; this.compiler = compiler; } @Override public Path getClassesDir() { - return classesDir; + //TODO: fix all these + for (DevModeContext.ModuleInfo i : context.getModules()) { + return Paths.get(i.getResourcePath()); + } + return null; } @Override public Path getSourcesDir() { - return sourcesDir; + //TODO: fix all these + for (DevModeContext.ModuleInfo i : context.getModules()) { + if (i.getSourcePath() != null) { + return Paths.get(i.getSourcePath()); + } + } + return null; } @Override - public Path getResourcesDir() { - return resourcesDir; + public List getResourcesDir() { + List ret = new ArrayList<>(); + for (DevModeContext.ModuleInfo i : context.getModules()) { + if (i.getResourcePath() != null) { + ret.add(Paths.get(i.getResourcePath())); + } + } + Collections.reverse(ret); //make sure the actual project is before dependencies + return ret; } @Override @@ -78,59 +95,72 @@ public Throwable getDeploymentProblem() { return DevModeMain.deploymentProblem; } - public void doScan() throws IOException { + @Override + public boolean doScan() throws IOException { final long startNanoseconds = System.nanoTime(); - final ConcurrentMap changedClasses = scanForChangedClasses(); - if (changedClasses == null) - return; + for (Runnable i : preScanSteps) { + try { + i.run(); + } catch (Throwable t) { + log.error("Pre Scan step failed", t); + } + } - DevModeMain.restartApp(); - log.infof("Hot replace total time: %ss ", Timing.convertToBigDecimalSeconds(System.nanoTime() - startNanoseconds)); + boolean classChanged = checkForChangedClasses(); + boolean configFileChanged = checkForConfigFileChange(); + + if (classChanged || configFileChanged) { + DevModeMain.restartApp(); + log.infof("Hot replace total time: %ss ", Timing.convertToBigDecimalSeconds(System.nanoTime() - startNanoseconds)); + return true; + } + return false; } - ConcurrentMap scanForChangedClasses() throws IOException { - final Set changedSourceFiles; - - if (sourcesDir != null) { - try (final Stream sourcesStream = Files.walk(sourcesDir)) { - changedSourceFiles = sourcesStream - .parallel() - .filter(p -> matchingHandledExtension(p).isPresent()) - .filter(p -> wasRecentlyModified(p)) - .map(Path::toFile) - //Needing a concurrent Set, not many standard options: - .collect(Collectors.toCollection(ConcurrentSkipListSet::new)); + @Override + public void addPreScanStep(Runnable runnable) { + preScanSteps.add(runnable); + } + + boolean checkForChangedClasses() throws IOException { + + for (DevModeContext.ModuleInfo i : context.getModules()) { + if (i.getSourcePath() != null) { + final Set changedSourceFiles; + try (final Stream sourcesStream = Files.walk(Paths.get(i.getSourcePath()))) { + changedSourceFiles = sourcesStream + .parallel() + .filter(p -> matchingHandledExtension(p).isPresent()) + .filter(p -> wasRecentlyModified(p, i)) + .map(Path::toFile) + //Needing a concurrent Set, not many standard options: + .collect(Collectors.toCollection(ConcurrentSkipListSet::new)); + } + if (!changedSourceFiles.isEmpty()) { + log.info("Changed source files detected, recompiling " + changedSourceFiles); + try { + compiler.compile(i.getSourcePath(), changedSourceFiles.stream() + .collect(groupingBy(this::getFileExtension, Collectors.toSet()))); + } catch (Exception e) { + DevModeMain.deploymentProblem = e; + return false; + } + } } - } else { - changedSourceFiles = Collections.EMPTY_SET; } - if (!changedSourceFiles.isEmpty()) { - log.info("Changes source files detected, recompiling " + changedSourceFiles); - try { - compiler.compile(changedSourceFiles.stream() - .collect(groupingBy(this::getFileExtension, Collectors.toSet()))); - } catch (Exception e) { - DevModeMain.deploymentProblem = e; - return null; + for (DevModeContext.ModuleInfo i : context.getModules()) { + if (i.getClassesPath() != null) { + try (final Stream classesStream = Files.walk(Paths.get(i.getClassesPath()))) { + if (classesStream.parallel().anyMatch(p -> p.toString().endsWith(".class") && wasRecentlyModified(p, i))) { + // At least one class was recently modified + lastChange = System.currentTimeMillis(); + return true; + } + } } } - final ConcurrentMap changedClasses; - try (final Stream classesStream = Files.walk(classesDir)) { - changedClasses = classesStream - .parallel() - .filter(p -> p.toString().endsWith(".class")) - .filter(p -> wasRecentlyModified(p)) - .collect(Collectors.toConcurrentMap( - p -> pathToClassName(p), - p -> CopyUtils.readFileContentNoIOExceptions(p))); - } - if (changedClasses.isEmpty() && !checkForConfigFileChange()) { - return null; - } - - lastChange = System.currentTimeMillis(); - return changedClasses; + return false; } private Optional matchingHandledExtension(Path p) { @@ -147,44 +177,69 @@ private String getFileExtension(File file) { } private boolean checkForConfigFileChange() { - boolean ret = false; - boolean doCopy = true; - Path root = resourcesDir; - if (root == null) { - root = classesDir; - doCopy = false; - } - for (String i : configFilePaths) { - Path config = root.resolve(i); - if (Files.exists(config)) { - try { - long value = Files.getLastModifiedTime(config).toMillis(); - Long existing = configFileTimestamps.get(i); - if (value > existing) { - ret = true; - if (doCopy) { - Path target = classesDir.resolve(i); - byte[] data = CopyUtils.readFileContent(config); - try (FileOutputStream out = new FileOutputStream(target.toFile())) { - out.write(data); + boolean configFilesHaveChanged = false; + for (DevModeContext.ModuleInfo module : context.getModules()) { + boolean doCopy = true; + String rootPath = module.getResourcePath(); + if (rootPath == null) { + rootPath = module.getClassesPath(); + doCopy = false; + } + if (rootPath == null) { + continue; + } + Path root = Paths.get(rootPath); + Path classesDir = Paths.get(module.getClassesPath()); + + for (String configFilePath : configFilePaths) { + Path config = root.resolve(configFilePath); + if (Files.exists(config)) { + try { + long value = Files.getLastModifiedTime(config).toMillis(); + Long existing = configFileTimestamps.get(config); + if (value > existing) { + configFilesHaveChanged = true; + log.infof("Config file change detected: %s", config); + if (doCopy) { + Path target = classesDir.resolve(configFilePath); + byte[] data = CopyUtils.readFileContent(config); + try (FileOutputStream out = new FileOutputStream(target.toFile())) { + out.write(data); + } } + configFileTimestamps.put(config, value); } + } catch (IOException e) { + throw new RuntimeException(e); + } + } else { + configFileTimestamps.put(config, 0L); + Path target = classesDir.resolve(configFilePath); + try { + Files.deleteIfExists(target); + } catch (IOException e) { + throw new RuntimeException(e); } - } catch (IOException e) { - throw new RuntimeException(e); } } } - return ret; + + return configFilesHaveChanged; } - private boolean wasRecentlyModified(final Path p) { + private boolean wasRecentlyModified(final Path p, DevModeContext.ModuleInfo module) { try { long sourceMod = Files.getLastModifiedTime(p).toMillis(); boolean recent = sourceMod > lastChange; if (recent) { return true; } + if (module.getSourcePath() == null || module.getClassesPath() == null) { + return false; + } + Path sourcesDir = Paths.get(module.getSourcePath()); + Path classesDir = Paths.get(module.getClassesPath()); + Optional matchingExtension = matchingHandledExtension(p); if (matchingExtension.isPresent()) { String pathName = sourcesDir.relativize(p).toString(); @@ -202,38 +257,33 @@ private boolean wasRecentlyModified(final Path p) { } } - private String pathToClassName(final Path path) { - String pathName = classesDir.relativize(path).toString(); - String className = pathName.substring(0, pathName.length() - 6).replace('/', '.'); - return className; - } - - interface UpdateHandler { - - void handle(Map changed); - - } - public RuntimeUpdatesProcessor setConfigFilePaths(Set configFilePaths) { this.configFilePaths = configFilePaths; configFileTimestamps.clear(); - Path root = resourcesDir; - if (root == null) { - root = classesDir; - } - for (String i : configFilePaths) { - Path config = root.resolve(i); - if (Files.exists(config)) { - try { - configFileTimestamps.put(i, Files.getLastModifiedTime(config).toMillis()); - } catch (IOException e) { - throw new RuntimeException(e); + + for (DevModeContext.ModuleInfo module : context.getModules()) { + String rootPath = module.getResourcePath(); + + if (rootPath == null) { + rootPath = module.getClassesPath(); + } + if (rootPath == null) { + continue; + } + Path root = Paths.get(rootPath); + for (String i : configFilePaths) { + Path config = root.resolve(i); + if (Files.exists(config)) { + try { + configFileTimestamps.put(config, Files.getLastModifiedTime(config).toMillis()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } else { + configFileTimestamps.put(config, 0L); } - } else { - configFileTimestamps.put(i, 0L); } } - return this; } diff --git a/integration-tests/infinispan-cache-jpa-stress/src/main/resources/META-INF/beans.xml b/core/runtime/build-include-jdk-misc similarity index 100% rename from integration-tests/infinispan-cache-jpa-stress/src/main/resources/META-INF/beans.xml rename to core/runtime/build-include-jdk-misc diff --git a/core/runtime/pom.xml b/core/runtime/pom.xml index 5fe9e6143b680..52401e7cc11bb 100644 --- a/core/runtime/pom.xml +++ b/core/runtime/pom.xml @@ -27,7 +27,7 @@ ../ - quarkus-core-runtime + quarkus-core Quarkus - Core - Runtime @@ -103,11 +103,16 @@ junit junit + test + + io.quarkus + quarkus-bootstrap-maven-plugin + org.apache.maven.plugins maven-compiler-plugin @@ -130,9 +135,6 @@ - - maven-dependency-plugin - 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 877ccade2678f..1a945d47e99ac 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/Application.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/Application.java @@ -21,8 +21,8 @@ import java.util.concurrent.locks.LockSupport; import org.graalvm.nativeimage.ImageInfo; -import org.jboss.threads.Locks; import org.wildfly.common.Assert; +import org.wildfly.common.lock.Locks; import io.quarkus.runtime.graal.DiagnosticPrinter; import sun.misc.Signal; @@ -34,6 +34,11 @@ */ @SuppressWarnings("restriction") public abstract class Application { + + // WARNING: do not inject a logger here, it's too early: the log manager has not been properly set up yet + + private static final String DISABLE_SIGNAL_HANDLERS = "DISABLE_SIGNAL_HANDLERS"; + private static final int ST_INITIAL = 0; private static final int ST_STARTING = 1; private static final int ST_STARTED = 2; @@ -46,6 +51,7 @@ public abstract class Application { private int state = ST_INITIAL; private volatile boolean shutdownRequested; + private static volatile Application currentApplication; /** * Construct a new instance. @@ -62,7 +68,8 @@ protected Application() { * @implNote The command line args are not yet used, but at some point we'll want a facility for overriding config and/or * letting the user hook into it. */ - public final void start(@SuppressWarnings("unused") String[] args) { + public final void start(String[] args) { + currentApplication = this; final Lock stateLock = this.stateLock; stateLock.lock(); try { @@ -159,6 +166,7 @@ public final void stop() { try { doStop(); } finally { + currentApplication = null; stateLock.lock(); try { state = ST_STOPPED; @@ -170,6 +178,10 @@ public final void stop() { } } + public static Application currentApplication() { + return currentApplication; + } + protected abstract void doStop(); /** @@ -177,15 +189,16 @@ public final void stop() { */ public final void run(String[] args) { try { - if (ImageInfo.inImageRuntimeCode()) { + if (ImageInfo.inImageRuntimeCode() && System.getenv(DISABLE_SIGNAL_HANDLERS) == null) { final SignalHandler handler = new SignalHandler() { @Override public void handle(final Signal signal) { - System.exit(0); + System.exit(signal.getNumber() + 0x80); } }; Signal.handle(new Signal("INT"), handler); Signal.handle(new Signal("TERM"), handler); + Signal.handle(new Signal("HUP"), handler); Signal.handle(new Signal("QUIT"), new SignalHandler() { @Override public void handle(final Signal signal) { @@ -193,6 +206,7 @@ public void handle(final Signal signal) { } }); } + final ShutdownHookThread shutdownHookThread = new ShutdownHookThread(Thread.currentThread()); Runtime.getRuntime().addShutdownHook(shutdownHookThread); start(args); diff --git a/core/runtime/src/main/java/io/quarkus/runtime/ExecutorTemplate.java b/core/runtime/src/main/java/io/quarkus/runtime/ExecutorTemplate.java index d430aa8875cc0..8694e6d095df9 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/ExecutorTemplate.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/ExecutorTemplate.java @@ -16,12 +16,13 @@ package io.quarkus.runtime; +import java.time.Duration; +import java.util.List; import java.util.Optional; -import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; -import org.eclipse.microprofile.config.Config; -import org.eclipse.microprofile.config.ConfigProvider; +import org.jboss.logging.Logger; import org.jboss.threads.EnhancedQueueExecutor; import org.jboss.threads.JBossExecutors; import org.jboss.threads.JBossThreadFactory; @@ -34,18 +35,23 @@ */ @Template public class ExecutorTemplate { - static final Config config = ConfigProvider.getConfig(); - public static final String CORE_POOL_SIZE = "executor.core-pool-size"; - public static final String MAX_POOL_SIZE = "executor.max-pool-size"; - public static final String QUEUE_SIZE = "executor.queue-size"; - public static final String GROWTH_RESISTANCE = "executor.growth-resistance"; - public static final String KEEP_ALIVE_MILLIS = "executor.keep-alive-millis"; + + private static final Logger log = Logger.getLogger("io.quarkus.thread-pool"); public ExecutorTemplate() { } - public Executor setupRunTime(ShutdownContext shutdownContext, int defaultCoreSize, int defaultMaxSize, int defaultQueueSize, - float defaultGrowthResistance, int defaultKeepAliveMillis) { + /** + * In dev mode for now we need the executor to last for the life of the app, as it is used by Undertow. This will likely + * change + */ + static ExecutorService devModeExecutor; + + public ExecutorService setupRunTime(ShutdownContext shutdownContext, ThreadPoolConfig threadPoolConfig, + LaunchMode launchMode) { + if (devModeExecutor != null) { + return devModeExecutor; + } final JBossThreadFactory threadFactory = new JBossThreadFactory(new ThreadGroup("executor"), Boolean.TRUE, null, "executor-thread-%t", JBossExecutors.loggingExceptionHandler("org.jboss.executor.uncaught"), null); final EnhancedQueueExecutor.Builder builder = new EnhancedQueueExecutor.Builder() @@ -54,34 +60,91 @@ public Executor setupRunTime(ShutdownContext shutdownContext, int defaultCoreSiz .setThreadFactory(JBossExecutors.resettingThreadFactory(threadFactory)); final int cpus = ProcessorInfo.availableProcessors(); // run time config variables - builder.setCorePoolSize(getIntConfigVal(CORE_POOL_SIZE, defaultCoreSize == -1 ? 4 * cpus : defaultCoreSize)); - builder.setMaximumPoolSize(getIntConfigVal(MAX_POOL_SIZE, defaultMaxSize == -1 ? 10 * cpus : defaultMaxSize)); - builder.setMaximumQueueSize(getIntConfigVal(QUEUE_SIZE, defaultQueueSize)); - builder.setGrowthResistance(getFloatConfigVal(GROWTH_RESISTANCE, defaultGrowthResistance)); - builder.setKeepAliveTime(getIntConfigVal("executor.keep-alive-millis", defaultKeepAliveMillis), TimeUnit.MILLISECONDS); + builder.setCorePoolSize(threadPoolConfig.coreThreads); + builder.setMaximumPoolSize(threadPoolConfig.maxThreads <= 0 ? 8 * cpus : threadPoolConfig.maxThreads); + builder.setMaximumQueueSize(threadPoolConfig.queueSize); + builder.setGrowthResistance(threadPoolConfig.growthResistance); + builder.setKeepAliveTime(threadPoolConfig.keepAliveTime); final EnhancedQueueExecutor executor = builder.build(); - shutdownContext.addShutdownTask(new Runnable() { + + Runnable shutdownTask = new Runnable() { @Override public void run() { executor.shutdown(); + final Duration shutdownTimeout = threadPoolConfig.shutdownTimeout; + final Optional optionalInterval = threadPoolConfig.shutdownCheckInterval; + long remaining = shutdownTimeout.toNanos(); + final long interval = optionalInterval.orElse(Duration.ofNanos(Long.MAX_VALUE)).toNanos(); + long intervalRemaining = interval; + long interruptRemaining = threadPoolConfig.shutdownInterrupt.toNanos(); + + long start = System.nanoTime(); for (;;) try { - executor.awaitTermination(Long.MAX_VALUE, TimeUnit.HOURS); + if (!executor.awaitTermination(Math.min(remaining, intervalRemaining), TimeUnit.MILLISECONDS)) { + long elapsed = System.nanoTime() - start; + intervalRemaining -= elapsed; + remaining -= elapsed; + interruptRemaining -= elapsed; + if (interruptRemaining <= 0) { + executor.shutdown(true); + } + if (remaining <= 0) { + // done waiting + final List runnables = executor.shutdownNow(); + if (!runnables.isEmpty()) { + log.warnf("Thread pool shutdown failed: discarding %d tasks, %d threads still running", + Integer.valueOf(runnables.size()), Integer.valueOf(executor.getActiveCount())); + } else { + log.warnf("Thread pool shutdown failed: %d threads still running", + Integer.valueOf(executor.getActiveCount())); + } + break; + } + if (intervalRemaining <= 0) { + intervalRemaining = interval; + // do some probing + final int queueSize = executor.getQueueSize(); + final Thread[] runningThreads = executor.getRunningThreads(); + log.infof("Awaiting thread pool shutdown; %d thread(s) running with %d task(s) waiting", + Integer.valueOf(runningThreads.length), Integer.valueOf(queueSize)); + // make sure no threads are stuck in {@code exit()} + int realWaiting = runningThreads.length; + for (Thread thr : runningThreads) { + final StackTraceElement[] stackTrace = thr.getStackTrace(); + for (int i = 0; i < stackTrace.length && i < 8; i++) { + if (stackTrace[i].getClassName().equals("java.lang.System") + && stackTrace[i].getMethodName().equals("exit")) { + final Throwable t = new Throwable(); + t.setStackTrace(stackTrace); + log.errorf(t, "Thread %s is blocked in System.exit(); pooled (Executor) threads " + + "should never call this method because it never returns, thus preventing " + + "the thread pool from shutting down in a timely manner. This is the " + + "stack trace of the call", thr.getName()); + // don't bother waiting for exit() to return + realWaiting--; + break; + } + } + } + if (realWaiting == 0 && queueSize == 0) { + // just exit + executor.shutdownNow(); + break; + } + } + } return; } catch (InterruptedException ignored) { } } - }); + }; + if (launchMode == LaunchMode.DEVELOPMENT) { + devModeExecutor = executor; + Runtime.getRuntime().addShutdownHook(new Thread(shutdownTask, "Executor shutdown thread")); + } else { + shutdownContext.addShutdownTask(shutdownTask); + } return executor; } - - public static float getFloatConfigVal(final String key, final float defVal) { - final Optional val = config.getOptionalValue(key, Float.class); - return val.isPresent() ? val.get().floatValue() : defVal; - } - - public static int getIntConfigVal(String key, int defVal) { - final Optional val = config.getOptionalValue(key, Integer.class); - return val.isPresent() ? val.get().intValue() : defVal; - } } diff --git a/core/runtime/src/main/java/io/quarkus/runtime/ThreadPoolConfig.java b/core/runtime/src/main/java/io/quarkus/runtime/ThreadPoolConfig.java new file mode 100644 index 0000000000000..2e889c928e208 --- /dev/null +++ b/core/runtime/src/main/java/io/quarkus/runtime/ThreadPoolConfig.java @@ -0,0 +1,77 @@ +package io.quarkus.runtime; + +import java.time.Duration; +import java.util.Optional; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; + +/** + * The core thread pool config. This thread pool is responsible for running + * all blocking tasks. + */ +@ConfigRoot(phase = ConfigPhase.RUN_TIME) +public class ThreadPoolConfig { + + /** + * The core thread pool size. This number of threads will always be kept alive. + */ + @ConfigItem(defaultValue = "1") + public int coreThreads; + + /** + * The maximum number of threads. If this is not specified or <= to zero then + * it will be automatically sized to 4 * the number of available processors + */ + @ConfigItem(defaultValue = "0") + public int maxThreads; + + /** + * The queue size. For most applications this should be unbounded + */ + @ConfigItem(defaultValue = "0") + public int queueSize; + + /** + * The executor growth resistance. + * + * A resistance factor applied after the core pool is full; values applied here will cause that fraction + * of submissions to create new threads when no idle thread is available. A value of {@code 0.0f} implies that + * threads beyond the core size should be created as aggressively as threads within it; a value of {@code 1.0f} + * implies that threads beyond the core size should never be created. + */ + @ConfigItem(defaultValue = "0") + public float growthResistance; + + /** + * The shutdown timeout. If all pending work has not been completed by this time + * then additional threads will be spawned to attempt to finish any pending tasks, and the shutdown process will + * continue + */ + @ConfigItem(defaultValue = "PT60S") + public Duration shutdownTimeout; + + /** + * The amount of time to wait for thread pool shutdown before tasks should be interrupted. If this value is + * greater than or equal to the value for {@link #shutdownTimeout}, then tasks will not be interrupted before + * the shutdown timeout occurs. + */ + @ConfigItem(defaultValue = "PT10S") + public Duration shutdownInterrupt; + + /** + * The frequency at which the status of the thread pool should be checked during shutdown. Information about + * waiting tasks and threads will be checked and possibly logged at this interval. Setting this key to an empty + * value disables the shutdown check interval. + */ + @ConfigItem(defaultValue = "PT5S") + public Optional shutdownCheckInterval; + + /** + * The amount of time in milliseconds a thread will stay alive with no work. Defaults to 1 second + */ + @ConfigItem(defaultValue = "PT1S") + public Duration keepAliveTime; + +} diff --git a/core/runtime/src/main/java/io/quarkus/runtime/Timing.java b/core/runtime/src/main/java/io/quarkus/runtime/Timing.java index 441d1bcb90acb..cd56d555d891c 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/Timing.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/Timing.java @@ -73,6 +73,7 @@ public static void printStartupTime(String version, String features) { final BigDecimal secondsRepresentation = convertToBigDecimalSeconds(bootTimeNanoSeconds); logger.infof("Quarkus %s started in %ss. %s", version, secondsRepresentation, httpServerInfo); logger.infof("Installed features: [%s]", features); + bootStartTime = -1; } public static void printStopTime() { @@ -80,12 +81,13 @@ public static void printStopTime() { final Logger logger = Logger.getLogger("io.quarkus"); final BigDecimal secondsRepresentation = convertToBigDecimalSeconds(stopTimeNanoSeconds); logger.infof("Quarkus stopped in %ss", secondsRepresentation); + bootStopTime = -1; } public static BigDecimal convertToBigDecimalSeconds(final long timeNanoSeconds) { - final BigDecimal secondsRepresentation=BigDecimal.valueOf(timeNanoSeconds) // As nanoseconds - .divide(BigDecimal.valueOf(1_000_000),BigDecimal.ROUND_HALF_UP) // Convert to milliseconds, discard remaining digits while rounding - .divide(BigDecimal.valueOf(1_000),3,BigDecimal.ROUND_HALF_UP); // Convert to seconds, while preserving 3 digits + final BigDecimal secondsRepresentation = BigDecimal.valueOf(timeNanoSeconds) // As nanoseconds + .divide(BigDecimal.valueOf(1_000_000), BigDecimal.ROUND_HALF_UP) // Convert to milliseconds, discard remaining digits while rounding + .divide(BigDecimal.valueOf(1_000), 3, BigDecimal.ROUND_HALF_UP); // Convert to seconds, while preserving 3 digits return secondsRepresentation; } diff --git a/core/runtime/src/main/java/io/quarkus/runtime/annotations/ConfigPhase.java b/core/runtime/src/main/java/io/quarkus/runtime/annotations/ConfigPhase.java index ce75658840988..27278258e0d0c 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/annotations/ConfigPhase.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/annotations/ConfigPhase.java @@ -4,35 +4,24 @@ public enum ConfigPhase { /** * Values are read and available for usage at build time. */ - BUILD_TIME(true, false, false, false), + BUILD_TIME(true, false, false), /** * Values are read and available for usage at build time, and available on a read-only basis at run time. */ - BUILD_AND_RUN_TIME_FIXED(true, true, false, false), - /** - * Values are read and available for usage at run time during static initialization. In a JVM image, they will - * be read on every execution; in a native image, they will only be read during the building of the image. - * - * @deprecated We are removing static init time configuration processing. - */ - @Deprecated - RUN_TIME_STATIC(false, true, true, false), + BUILD_AND_RUN_TIME_FIXED(true, true, false), /** * Values are read and available for usage at run time and are re-read on each program execution. */ - RUN_TIME(false, true, true, true), + RUN_TIME(false, true, true), ; private final boolean availableAtBuild; private final boolean availableAtRun; - private final boolean readAtStaticInit; private final boolean readAtMain; - ConfigPhase(final boolean availableAtBuild, final boolean availableAtRun, final boolean readAtStaticInit, - final boolean readAtMain) { + ConfigPhase(final boolean availableAtBuild, final boolean availableAtRun, final boolean readAtMain) { this.availableAtBuild = availableAtBuild; this.availableAtRun = availableAtRun; - this.readAtStaticInit = readAtStaticInit; this.readAtMain = readAtMain; } @@ -45,7 +34,7 @@ public boolean isAvailableAtRun() { } public boolean isReadAtStaticInit() { - return readAtStaticInit; + return isAvailableAtBuild() && isAvailableAtRun(); } public boolean isReadAtMain() { 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 new file mode 100644 index 0000000000000..3e2f4915cd5ff --- /dev/null +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/AbstractRawDefaultConfigSource.java @@ -0,0 +1,32 @@ +package io.quarkus.runtime.configuration; + +import java.util.Collections; +import java.util.Map; + +import org.eclipse.microprofile.config.spi.ConfigSource; + +/** + * The base class for the config source that yields the 'raw' default values. + */ +public abstract class AbstractRawDefaultConfigSource implements ConfigSource { + protected AbstractRawDefaultConfigSource() { + } + + public Map getProperties() { + return Collections.emptyMap(); + } + + public String getValue(final String propertyName) { + return getValue(new NameIterator(propertyName)); + } + + protected abstract String getValue(final NameIterator nameIterator); + + public String getName() { + return "default values"; + } + + public int getOrdinal() { + return Integer.MIN_VALUE; + } +} 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 new file mode 100644 index 0000000000000..6f862d1c18fd9 --- /dev/null +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/BuildTimeConfigFactory.java @@ -0,0 +1,46 @@ +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/ConverterFactory.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConverterFactory.java index 92ac832e8188a..5b7b63f01a149 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConverterFactory.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConverterFactory.java @@ -5,8 +5,6 @@ import org.eclipse.microprofile.config.spi.Converter; -import io.smallrye.config.SmallRyeConfig; - /** * A factory to acquire a converter for a given type. * @@ -14,21 +12,22 @@ */ @Deprecated public final class ConverterFactory { - static final Method getConverter; + static final Method getImplicitConverter; static { try { - getConverter = SmallRyeConfig.class.getDeclaredMethod("getConverter", Class.class); - getConverter.setAccessible(true); - } catch (NoSuchMethodException e) { + getImplicitConverter = Class.forName("io.smallrye.config.ImplicitConverters").getDeclaredMethod("getConverter", + Class.class); + getImplicitConverter.setAccessible(true); + } catch (NoSuchMethodException | ClassNotFoundException e) { throw reflectionFailure(e); } } @SuppressWarnings("unchecked") - public static Converter getConverter(final SmallRyeConfig smallRyeConfig, final Class itemClass) { + public static Converter getImplicitConverter(final Class itemClass) { try { - return (Converter) getConverter.invoke(smallRyeConfig, itemClass); + return (Converter) getImplicitConverter.invoke(null, itemClass); } catch (IllegalAccessException | InvocationTargetException e) { throw reflectionFailure(e); } 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 index 5eb597b6678f2..40cbe8f7d02da 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/DefaultConfigSource.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/DefaultConfigSource.java @@ -3,8 +3,9 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.net.URL; import java.nio.charset.StandardCharsets; -import java.util.Collections; +import java.util.Enumeration; import java.util.Map; import java.util.Properties; @@ -28,17 +29,23 @@ public DefaultConfigSource() { @SuppressWarnings("unchecked") private static Map getMap() { ClassLoader cl = Thread.currentThread().getContextClassLoader(); - if (cl == null) + if (cl == null) { cl = DefaultConfigSource.class.getClassLoader(); - try (InputStream is = cl.getResourceAsStream(DEFAULT_CONFIG_PROPERTIES_NAME)) { - if (is == null) { - return Collections.emptyMap(); - } - try (InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8)) { - final Properties p = new Properties(); - p.load(isr); - return (Map) p; + } + 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/ExpandingConfigSource.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ExpandingConfigSource.java index 95778574ca19f..b588d32854f49 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 @@ -5,48 +5,89 @@ import java.util.function.UnaryOperator; import org.eclipse.microprofile.config.spi.ConfigSource; +import org.wildfly.common.Assert; import org.wildfly.common.expression.Expression; -import io.smallrye.config.SmallRyeConfigBuilder; - /** * A value-expanding configuration source, which allows (limited) recursive expansion. */ public class ExpandingConfigSource extends AbstractDelegatingConfigSource { - // this is a cache of compiled expressions, NOT a cache of expanded values - private final ConcurrentHashMap exprCache = new ConcurrentHashMap<>(); - /** - * A wrapper suitable for passing in to {@link SmallRyeConfigBuilder#withWrapper(UnaryOperator)}. - */ - public static final UnaryOperator WRAPPER = ExpandingConfigSource::new; + private static final ThreadLocal NO_EXPAND = new ThreadLocal<>(); + + public static UnaryOperator wrapper(Cache cache) { + return configSource -> new ExpandingConfigSource(configSource, cache); + } + + private final Cache cache; /** * Construct a new instance. * * @param delegate the delegate config source (must not be {@code null}) + * @param cache the cache instance to use (must not be {@code null}) */ - public ExpandingConfigSource(final ConfigSource delegate) { + public ExpandingConfigSource(final ConfigSource delegate, final Cache cache) { super(delegate); + Assert.checkNotNullParam("cache", cache); + this.cache = cache; } + @Override public Set getPropertyNames() { return delegate.getPropertyNames(); } + @Override public String getValue(final String propertyName) { - return expand(delegate.getValue(propertyName)); + final String delegateValue = delegate.getValue(propertyName); + return isExpanding() ? expand(delegateValue) : delegateValue; } String expand(final String value) { + return expandValue(value, cache); + } + + public void flush() { + cache.flush(); + } + + private static boolean isExpanding() { + return NO_EXPAND.get() != Boolean.TRUE; + } + + public static boolean setExpanding(boolean newValue) { + try { + return NO_EXPAND.get() != Boolean.TRUE; + } finally { + if (newValue) { + NO_EXPAND.remove(); + } else { + NO_EXPAND.set(Boolean.TRUE); + } + } + } + + public static String expandValue(String value, Cache cache) { if (value == null) return null; - final Expression compiled = exprCache.computeIfAbsent(value, - str -> Expression.compile(str, Expression.Flag.ESCAPES, Expression.Flag.LENIENT_SYNTAX)); + final Expression compiled = cache.exprCache.computeIfAbsent(value, + str -> Expression.compile(str, Expression.Flag.LENIENT_SYNTAX)); return compiled.evaluate(ConfigExpander.INSTANCE); } - public void flush() { - exprCache.clear(); + /** + * An expression cache to use with {@link ExpandingConfigSource}. + */ + public static final class Cache { + // this is a cache of compiled expressions, NOT a cache of expanded values + final ConcurrentHashMap exprCache = new ConcurrentHashMap<>(); + + public Cache() { + } + + public void flush() { + exprCache.clear(); + } } } 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 index 701d571c4ddc7..c41510ad7bb88 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/SimpleConfigurationProviderResolver.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/SimpleConfigurationProviderResolver.java @@ -10,11 +10,9 @@ * A simple configuration provider. */ public class SimpleConfigurationProviderResolver extends ConfigProviderResolver { - private final Config config; - public SimpleConfigurationProviderResolver(final Config config) { - this.config = config; - } + // We use a shared config + private static volatile Config config; public Config getConfig() { return config; @@ -29,10 +27,10 @@ public ConfigBuilder getBuilder() { } public void registerConfig(final Config config, final ClassLoader classLoader) { - // ignore + SimpleConfigurationProviderResolver.config = config; } public void releaseConfig(final Config config) { - // ignore + 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 45ad7fded760b..fbf76fd31b8e2 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,6 +1,9 @@ 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; import com.oracle.svm.core.annotate.Alias; import com.oracle.svm.core.annotate.Delete; @@ -48,4 +51,37 @@ static final class Target_ConfigurationProviderResolver { private static volatile ConfigProviderResolver instance; } + @TargetClass(ExpandingConfigSource.class) + static final class Target_ExpandingConfigSource { + @Delete + private static ThreadLocal NO_EXPAND; + + @Substitute + private static boolean isExpanding() { + return true; + } + + @Substitute + public static boolean setExpanding(boolean newValue) { + if (!newValue) + throw Assert.unsupported(); + 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/extensions/resteasy-common/runtime/src/main/java/io/quarkus/resteasy/common/runtime/graal/ImageIOSubstitutions.java b/core/runtime/src/main/java/io/quarkus/runtime/graal/ImageIOSubstitutions.java similarity index 57% rename from extensions/resteasy-common/runtime/src/main/java/io/quarkus/resteasy/common/runtime/graal/ImageIOSubstitutions.java rename to core/runtime/src/main/java/io/quarkus/runtime/graal/ImageIOSubstitutions.java index 06cc81008563e..41a89d8b78f0f 100644 --- a/extensions/resteasy-common/runtime/src/main/java/io/quarkus/resteasy/common/runtime/graal/ImageIOSubstitutions.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/graal/ImageIOSubstitutions.java @@ -1,4 +1,4 @@ -package io.quarkus.resteasy.common.runtime.graal; +package io.quarkus.runtime.graal; import java.awt.image.BufferedImage; import java.awt.image.RenderedImage; @@ -29,155 +29,139 @@ public static void scanForPlugins() { @Substitute public static ImageInputStream createImageInputStream(Object input) throws IOException { - throw new IOException("Not Implemented yet on substrate"); + throw new UnsupportedOperationException("Not implemented yet for GraalVM native images"); } @Substitute public static ImageOutputStream createImageOutputStream(Object output) throws IOException { - throw new IOException("Not Implemented yet on substrate"); + throw new UnsupportedOperationException("Not implemented yet for GraalVM native images"); } @Substitute public static String[] getReaderFormatNames() { - throw new RuntimeException("Not Implemented yet on substrate"); + throw new UnsupportedOperationException("Not implemented yet for GraalVM native images"); } @Substitute public static String[] getReaderMIMETypes() { - throw new RuntimeException("Not Implemented yet on substrate"); + throw new UnsupportedOperationException("Not implemented yet for GraalVM native images"); } @Substitute public static String[] getReaderFileSuffixes() { - throw new RuntimeException("Not Implemented yet on substrate"); + throw new UnsupportedOperationException("Not implemented yet for GraalVM native images"); } @Substitute public static Iterator getImageReaders(Object input) { - throw new RuntimeException("Not Implemented yet on substrate"); + throw new UnsupportedOperationException("Not implemented yet for GraalVM native images"); } @Substitute public static Iterator getImageReadersByFormatName(String formatName) { - throw new RuntimeException("Not Implemented yet on substrate"); + throw new UnsupportedOperationException("Not implemented yet for GraalVM native images"); } @Substitute public static Iterator getImageReadersBySuffix(String fileSuffix) { - throw new RuntimeException("Not Implemented yet on substrate"); + throw new UnsupportedOperationException("Not implemented yet for GraalVM native images"); } @Substitute public static Iterator getImageReadersByMIMEType(String MIMEType) { - throw new RuntimeException("Not Implemented yet on substrate"); + throw new UnsupportedOperationException("Not implemented yet for GraalVM native images"); } @Substitute public static String[] getWriterFormatNames() { - - throw new RuntimeException("Not Implemented yet on substrate"); + throw new UnsupportedOperationException("Not implemented yet for GraalVM native images"); } - /** - * Returns an array of {@code String}s listing all of the - * MIME types understood by the current set of registered - * writers. - * - * @return an array of {@code String}s. - */ @Substitute public static String[] getWriterMIMETypes() { - throw new RuntimeException("Not Implemented yet on substrate"); + throw new UnsupportedOperationException("Not implemented yet for GraalVM native images"); } - /** - * Returns an array of {@code String}s listing all of the - * file suffixes associated with the formats understood - * by the current set of registered writers. - * - * @return an array of {@code String}s. - * @since 1.6 - */ @Substitute public static String[] getWriterFileSuffixes() { - throw new RuntimeException("Not Implemented yet on substrate"); + throw new UnsupportedOperationException("Not implemented yet for GraalVM native images"); } @Substitute public static Iterator getImageWritersByFormatName(String formatName) { - throw new RuntimeException("Not Implemented yet on substrate"); + throw new UnsupportedOperationException("Not implemented yet for GraalVM native images"); } @Substitute public static Iterator getImageWritersBySuffix(String fileSuffix) { - throw new RuntimeException("Not Implemented yet on substrate"); + throw new UnsupportedOperationException("Not implemented yet for GraalVM native images"); } @Substitute public static Iterator getImageWritersByMIMEType(String MIMEType) { - throw new RuntimeException("Not Implemented yet on substrate"); + throw new UnsupportedOperationException("Not implemented yet for GraalVM native images"); } @Substitute public static ImageWriter getImageWriter(ImageReader reader) { - throw new RuntimeException("Not Implemented yet on substrate"); + throw new UnsupportedOperationException("Not implemented yet for GraalVM native images"); } @Substitute public static ImageReader getImageReader(ImageWriter writer) { - throw new RuntimeException("Not Implemented yet on substrate"); + throw new UnsupportedOperationException("Not implemented yet for GraalVM native images"); } @Substitute public static Iterator getImageWriters(ImageTypeSpecifier type, String formatName) { - throw new RuntimeException("Not Implemented yet on substrate"); + throw new UnsupportedOperationException("Not implemented yet for GraalVM native images"); } @Substitute public static Iterator getImageTranscoders(ImageReader reader, ImageWriter writer) { - throw new RuntimeException("Not Implemented yet on substrate"); + throw new UnsupportedOperationException("Not implemented yet for GraalVM native images"); } @Substitute public static BufferedImage read(File input) throws IOException { - throw new IOException("Not Implemented yet on substrate"); + throw new UnsupportedOperationException("Not implemented yet for GraalVM native images"); } @Substitute public static BufferedImage read(InputStream input) throws IOException { - throw new IOException("Not Implemented yet on substrate"); + throw new UnsupportedOperationException("Not implemented yet for GraalVM native images"); } @Substitute public static BufferedImage read(URL input) throws IOException { - throw new IOException("Not Implemented yet on substrate"); + throw new UnsupportedOperationException("Not implemented yet for GraalVM native images"); } @Substitute public static BufferedImage read(ImageInputStream stream) throws IOException { - throw new IOException("Not Implemented yet on substrate"); + throw new UnsupportedOperationException("Not implemented yet for GraalVM native images"); } @Substitute public static boolean write(RenderedImage im, String formatName, ImageOutputStream output) throws IOException { - throw new IOException("Not Implemented yet on substrate"); + throw new UnsupportedOperationException("Not implemented yet for GraalVM native images"); } @Substitute public static boolean write(RenderedImage im, String formatName, File output) throws IOException { - throw new IOException("Not Implemented yet on substrate"); + throw new UnsupportedOperationException("Not implemented yet for GraalVM native images"); } @Substitute public static boolean write(RenderedImage im, String formatName, OutputStream output) throws IOException { - throw new IOException("Not Implemented yet on substrate"); + throw new UnsupportedOperationException("Not implemented yet for GraalVM native images"); } } diff --git a/extensions/resteasy-common/runtime/src/main/java/io/quarkus/resteasy/common/runtime/graal/LCMSSubstitutions.java b/core/runtime/src/main/java/io/quarkus/runtime/graal/LCMSSubstitutions.java similarity index 63% rename from extensions/resteasy-common/runtime/src/main/java/io/quarkus/resteasy/common/runtime/graal/LCMSSubstitutions.java rename to core/runtime/src/main/java/io/quarkus/runtime/graal/LCMSSubstitutions.java index 93ef2fe91a4f1..322349e23613a 100644 --- a/extensions/resteasy-common/runtime/src/main/java/io/quarkus/resteasy/common/runtime/graal/LCMSSubstitutions.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/graal/LCMSSubstitutions.java @@ -1,4 +1,4 @@ -package io.quarkus.resteasy.common.runtime.graal; +package io.quarkus.runtime.graal; import java.awt.color.ICC_Profile; @@ -10,38 +10,28 @@ final class Target_sun_java2d_cmm_lcms_LCMS { @Substitute private long loadProfileNative(byte[] data, Object ref) { - throw new RuntimeException("Not Implemented"); + throw new UnsupportedOperationException("Not implemented yet for GraalVM native images"); } @Substitute private int getProfileSizeNative(long ptr) { - throw new RuntimeException("Not Implemented"); + throw new UnsupportedOperationException("Not implemented yet for GraalVM native images"); } @Substitute private void getProfileDataNative(long ptr, byte[] data) { - throw new RuntimeException("Not Implemented"); + throw new UnsupportedOperationException("Not implemented yet for GraalVM native images"); } @Substitute static byte[] getTagNative(long profileID, int signature) { - throw new RuntimeException("Not Implemented"); + throw new UnsupportedOperationException("Not implemented yet for GraalVM native images"); } - /** - * Writes supplied data as a tag into the profile. - * Destroys old profile, if new one was successfully - * created. - *

- * Returns valid pointer to new profile. - *

- * Throws CMMException if operation fails, preserve old profile from - * destruction. - */ @Substitute private void setTagDataNative(long ptr, int tagSignature, byte[] data) { - throw new RuntimeException("Not Implemented"); + throw new UnsupportedOperationException("Not implemented yet for GraalVM native images"); } @Substitute @@ -50,29 +40,28 @@ private static long createNativeTransform( int inFormatter, boolean isInIntPacked, int outFormatter, boolean isOutIntPacked, Object disposerRef) { - throw new RuntimeException("Not Implemented"); + throw new UnsupportedOperationException("Not implemented yet for GraalVM native images"); } @Substitute public static void initLCMS(Class Trans, Class IL, Class Pf) { - throw new RuntimeException("Not Implemented"); + throw new UnsupportedOperationException("Not implemented yet for GraalVM native images"); } @Substitute public static synchronized LCMSProfile getProfileID(ICC_Profile profile) { - throw new RuntimeException("Not Implemented"); + throw new UnsupportedOperationException("Not implemented yet for GraalVM native images"); } @Substitute public static void colorConvert(LCMSTransform trans, LCMSImageLayout src, LCMSImageLayout dest) { - throw new RuntimeException("Not Implemented"); + throw new UnsupportedOperationException("Not implemented yet for GraalVM native images"); } @TargetClass(className = "sun.java2d.cmm.lcms.LCMSProfile") static final class LCMSProfile { - } @TargetClass(className = "sun.java2d.cmm.lcms.LCMSImageLayout") @@ -81,7 +70,6 @@ static final class LCMSImageLayout { @TargetClass(className = "sun.java2d.cmm.lcms.LCMSTransform") static final class LCMSTransform { - } } diff --git a/core/runtime/src/main/java/io/quarkus/runtime/graal/Target_javax_net_ssl_SSLContext.java b/core/runtime/src/main/java/io/quarkus/runtime/graal/Target_javax_net_ssl_SSLContext.java new file mode 100644 index 0000000000000..7fe2c336b5413 --- /dev/null +++ b/core/runtime/src/main/java/io/quarkus/runtime/graal/Target_javax_net_ssl_SSLContext.java @@ -0,0 +1,182 @@ +package io.quarkus.runtime.graal; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.net.UnknownHostException; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.Provider; +import java.security.SecureRandom; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLContextSpi; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLServerSocketFactory; +import javax.net.ssl.SSLSessionContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; + +import com.oracle.svm.core.annotate.Alias; +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; + +import io.quarkus.runtime.ssl.SslContextConfiguration; + +@TargetClass(className = "javax.net.ssl.SSLContext") +public final class Target_javax_net_ssl_SSLContext { + + @Alias + private static SSLContext defaultContext; + + @Alias + protected Target_javax_net_ssl_SSLContext(SSLContextSpi contextSpi, Provider provider, String protocol) { + } + + @Substitute + public static synchronized SSLContext getDefault() + throws NoSuchAlgorithmException { + if (defaultContext == null) { + if (SslContextConfiguration.isSslNativeEnabled()) { + defaultContext = SSLContext.getInstance("Default"); + } else { + defaultContext = new DisabledSSLContext(); + } + } + return defaultContext; + } + + // TODO sun.security.jca.GetInstance is not accessible in JDK 11. We cannot add an export + // as we still compile with a JDK 8 target. So for now, we will have to leave with this + // and only override getDefault(). + // @Substitute + // public static Target_javax_net_ssl_SSLContext getInstance(String protocol) + // throws NoSuchAlgorithmException { + // Objects.requireNonNull(protocol, "null protocol name"); + // + // if (!SslContextConfiguration.isSslNativeEnabled()) { + // return (Target_javax_net_ssl_SSLContext) (Object) getDefault(); + // } + // + // GetInstance.Instance instance = GetInstance.getInstance("SSLContext", SSLContextSpi.class, protocol); + // return new Target_javax_net_ssl_SSLContext((SSLContextSpi) instance.impl, instance.provider, + // protocol); + // } + // + // @Substitute + // public static Target_javax_net_ssl_SSLContext getInstance(String protocol, String provider) + // throws NoSuchAlgorithmException, NoSuchProviderException { + // Objects.requireNonNull(protocol, "null protocol name"); + // + // if (!SslContextConfiguration.isSslNativeEnabled()) { + // return (Target_javax_net_ssl_SSLContext) (Object) getDefault(); + // } + // + // GetInstance.Instance instance = GetInstance.getInstance("SSLContext", SSLContextSpi.class, protocol, provider); + // return new Target_javax_net_ssl_SSLContext((SSLContextSpi) instance.impl, instance.provider, + // protocol); + // } + // + // @Substitute + // public static Target_javax_net_ssl_SSLContext getInstance(String protocol, Provider provider) + // throws NoSuchAlgorithmException { + // Objects.requireNonNull(protocol, "null protocol name"); + // + // if (!SslContextConfiguration.isSslNativeEnabled()) { + // return (Target_javax_net_ssl_SSLContext) (Object) getDefault(); + // } + // + // GetInstance.Instance instance = GetInstance.getInstance("SSLContext", SSLContextSpi.class, protocol, provider); + // return new Target_javax_net_ssl_SSLContext((SSLContextSpi) instance.impl, instance.provider, + // protocol); + // } + + private static class DisabledSSLContext extends SSLContext { + + protected DisabledSSLContext() { + super(new DisabledSSLContextSpi(), new Provider("DISABLED", 1, "DISABLED") { + }, "DISABLED"); + } + } + + private static class DisabledSSLContextSpi extends SSLContextSpi { + + @Override + protected void engineInit(KeyManager[] keyManagers, TrustManager[] trustManagers, SecureRandom secureRandom) + throws KeyManagementException { + } + + @Override + protected SSLSocketFactory engineGetSocketFactory() { + return new SSLSocketFactory() { + @Override + public String[] getDefaultCipherSuites() { + return new String[0]; + } + + @Override + public String[] getSupportedCipherSuites() { + return new String[0]; + } + + @Override + public Socket createSocket(Socket socket, String s, int i, boolean b) throws IOException { + throw sslSupportDisabledException(); + } + + @Override + public Socket createSocket(String s, int i) throws IOException, UnknownHostException { + throw sslSupportDisabledException(); + } + + @Override + public Socket createSocket(String s, int i, InetAddress inetAddress, int i1) + throws IOException, UnknownHostException { + throw sslSupportDisabledException(); + } + + @Override + public Socket createSocket(InetAddress inetAddress, int i) throws IOException { + throw sslSupportDisabledException(); + } + + @Override + public Socket createSocket(InetAddress inetAddress, int i, InetAddress inetAddress1, int i1) + throws IOException { + throw sslSupportDisabledException(); + } + }; + } + + @Override + protected SSLServerSocketFactory engineGetServerSocketFactory() { + throw sslSupportDisabledException(); + } + + @Override + protected SSLEngine engineCreateSSLEngine() { + throw sslSupportDisabledException(); + } + + @Override + protected SSLEngine engineCreateSSLEngine(String s, int i) { + throw sslSupportDisabledException(); + } + + @Override + protected SSLSessionContext engineGetServerSessionContext() { + throw sslSupportDisabledException(); + } + + @Override + protected SSLSessionContext engineGetClientSessionContext() { + throw sslSupportDisabledException(); + } + + private RuntimeException sslSupportDisabledException() { + return new IllegalStateException( + "Native SSL support is disabled: you have set quarkus.ssl.native to false in your configuration."); + } + } +} diff --git a/core/runtime/src/main/java/io/quarkus/runtime/graal/Target_org_wildfly_security_x500_util_X500PrincipalUtil.java b/core/runtime/src/main/java/io/quarkus/runtime/graal/Target_org_wildfly_security_x500_util_X500PrincipalUtil.java index eb5a6af766744..13ee1282a140e 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/graal/Target_org_wildfly_security_x500_util_X500PrincipalUtil.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/graal/Target_org_wildfly_security_x500_util_X500PrincipalUtil.java @@ -28,7 +28,7 @@ final class Target_org_wildfly_security_x500_util_X500PrincipalUtil { /** * Only handle the case of converting a {@linkplain Principal#getName()} to {@linkplain X500Principal} - * + * * @param principal - Principal with name that maps to valid DN * @param convert - whether one should convert to X500Principal * @return X500Principal if convert is true and valid DN is seen, null otherwise diff --git a/core/runtime/src/main/java/io/quarkus/runtime/logging/CleanupFilterConfig.java b/core/runtime/src/main/java/io/quarkus/runtime/logging/CleanupFilterConfig.java index 308947705f678..42bb56a85a99a 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/logging/CleanupFilterConfig.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/logging/CleanupFilterConfig.java @@ -16,14 +16,16 @@ package io.quarkus.runtime.logging; +import java.util.List; + import io.quarkus.runtime.annotations.ConfigGroup; import io.quarkus.runtime.annotations.ConfigItem; @ConfigGroup public class CleanupFilterConfig { /** - * The start message to match + * The message starts to match */ @ConfigItem(defaultValue = "inherit") - String ifStartsWith; + List ifStartsWith; } 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 7bbc67f27b213..553259b70d96e 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 @@ -48,4 +48,9 @@ public class ConsoleConfig { @ConfigItem(defaultValue = "true") boolean color; + /** + * Specify how much the colors should be darkened + */ + @ConfigItem(defaultValue = "0") + int darken; } diff --git a/core/runtime/src/main/java/io/quarkus/runtime/logging/FileConfig.java b/core/runtime/src/main/java/io/quarkus/runtime/logging/FileConfig.java index 276433a231ae7..7842ec255abff 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/logging/FileConfig.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/logging/FileConfig.java @@ -17,6 +17,8 @@ package io.quarkus.runtime.logging; import java.io.File; +import java.util.Optional; +import java.util.OptionalLong; import java.util.logging.Level; import io.quarkus.runtime.annotations.ConfigGroup; @@ -28,7 +30,7 @@ public class FileConfig { /** * If file logging should be enabled */ - @ConfigItem(defaultValue = "true") + @ConfigItem(defaultValue = "false") boolean enable; /** @@ -49,4 +51,35 @@ public class FileConfig { @ConfigItem(defaultValue = "quarkus.log") File path; + /** + * File rotation config + */ + RotationConfig rotation; + + @ConfigGroup + public static class RotationConfig { + /** + * The maximum file size of the log file after which a rotation is executed. + */ + @ConfigItem + OptionalLong maxFileSize; + + /** + * The maximum number of backups to keep. + */ + @ConfigItem(defaultValue = "1") + int maxBackupIndex; + + /** + * File handler rotation file suffix. + */ + @ConfigItem + Optional fileSuffix; + + /** + * Indicates whether to rotate log files on server initialization. + */ + @ConfigItem(defaultValue = "true") + boolean rotateOnBoot; + } } diff --git a/core/runtime/src/main/java/io/quarkus/runtime/logging/InitialConfigurator.java b/core/runtime/src/main/java/io/quarkus/runtime/logging/InitialConfigurator.java index db97300c2fe58..5a80b6a2c08c6 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/logging/InitialConfigurator.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/logging/InitialConfigurator.java @@ -16,23 +16,23 @@ public final class InitialConfigurator implements EmbeddedConfigurator { public static final DelayedHandler DELAYED_HANDLER = new DelayedHandler(); + @Override public Level getMinimumLevelOf(final String loggerName) { return Level.ALL; } + @Override public Level getLevelOf(final String loggerName) { return loggerName.isEmpty() ? Level.ALL : null; } + @Override public Handler[] getHandlersOf(final String loggerName) { if (loggerName.isEmpty()) { - if (ImageInfo.inImageBuildtimeCode() || System.getProperty("quarkus.devMode") != null) { - final ConsoleHandler handler = new ConsoleHandler(new PatternFormatter( - "%d{HH:mm:ss,SSS} %-5p [%c{3.}] %s%e%n")); - handler.setLevel(Level.INFO); + if (ImageInfo.inImageBuildtimeCode()) { // we can't set a cleanup filter without the build items ready return new Handler[] { - handler + createDefaultHandler() }; } else { return new Handler[] { DELAYED_HANDLER }; @@ -41,4 +41,10 @@ public Handler[] getHandlersOf(final String loggerName) { return NO_HANDLERS; } } + + public static ConsoleHandler createDefaultHandler() { + ConsoleHandler handler = new ConsoleHandler(new PatternFormatter("%d{HH:mm:ss,SSS} %-5p [%c{3.}] %s%e%n")); + handler.setLevel(Level.INFO); + return handler; + } } diff --git a/core/runtime/src/main/java/io/quarkus/runtime/logging/LogCleanupFilter.java b/core/runtime/src/main/java/io/quarkus/runtime/logging/LogCleanupFilter.java index 98176a570d8d7..908273351465a 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/logging/LogCleanupFilter.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/logging/LogCleanupFilter.java @@ -21,17 +21,19 @@ public LogCleanupFilter(List filterElements) { @Override public boolean isLoggable(LogRecord record) { - if (record.getLevel().intValue() != Level.INFO.intValue()) + // Only allow filtering messages of warning level and lower + if (record.getLevel().intValue() > Level.WARNING.intValue()) { return true; + } LogCleanupFilterElement filterElement = filterElements.get(record.getLoggerName()); if (filterElement != null) { - if (record.getMessage().startsWith(filterElement.getMessageStart())) { - record.setLevel(org.jboss.logmanager.Level.DEBUG); - return Logger.getLogger(record.getLoggerName()).isDebugEnabled(); + for (String messageStart : filterElement.getMessageStarts()) { + if (record.getMessage().startsWith(messageStart)) { + record.setLevel(org.jboss.logmanager.Level.DEBUG); + return Logger.getLogger(record.getLoggerName()).isDebugEnabled(); + } } } - // System.err.println("isLoggable: "+record.getLoggerName()); - // System.err.println("isLoggable: "+record.getMessage()); return true; } diff --git a/core/runtime/src/main/java/io/quarkus/runtime/logging/LogCleanupFilterElement.java b/core/runtime/src/main/java/io/quarkus/runtime/logging/LogCleanupFilterElement.java index 109c01a4f3b3f..b45085d6ecba9 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/logging/LogCleanupFilterElement.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/logging/LogCleanupFilterElement.java @@ -1,16 +1,18 @@ package io.quarkus.runtime.logging; +import java.util.List; + public class LogCleanupFilterElement { private String loggerName; - private String messageStart; + private List messageStarts; // Required by template public LogCleanupFilterElement() { } - public LogCleanupFilterElement(String loggerName, String messageStart) { + public LogCleanupFilterElement(String loggerName, List messageStarts) { this.loggerName = loggerName; - this.messageStart = messageStart; + this.messageStarts = messageStarts; } public String getLoggerName() { @@ -21,11 +23,11 @@ public void setLoggerName(String loggerName) { this.loggerName = loggerName; } - public String getMessageStart() { - return messageStart; + public List getMessageStarts() { + return messageStarts; } - public void setMessageStart(String messageStart) { - this.messageStart = messageStart; + public void setMessageStart(List messageStarts) { + this.messageStarts = messageStarts; } } \ No newline at end of file diff --git a/core/runtime/src/main/java/io/quarkus/runtime/logging/LoggingSetupTemplate.java b/core/runtime/src/main/java/io/quarkus/runtime/logging/LoggingSetupTemplate.java index 3a706adc93fc4..93ac157401da5 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/logging/LoggingSetupTemplate.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/logging/LoggingSetupTemplate.java @@ -18,8 +18,9 @@ import org.jboss.logmanager.formatters.PatternFormatter; import org.jboss.logmanager.handlers.ConsoleHandler; import org.jboss.logmanager.handlers.FileHandler; - -import com.oracle.svm.core.annotate.RecomputeFieldValue; +import org.jboss.logmanager.handlers.PeriodicRotatingFileHandler; +import org.jboss.logmanager.handlers.PeriodicSizeRotatingFileHandler; +import org.jboss.logmanager.handlers.SizeRotatingFileHandler; import io.quarkus.runtime.annotations.Template; @@ -31,15 +32,7 @@ public class LoggingSetupTemplate { public LoggingSetupTemplate() { } - @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset) - private static volatile boolean initialized; - public void initializeLogging(LogConfig config) { - if (initialized || ImageInfo.inImageBuildtimeCode()) { - // JVM mode, already initialized in static init - return; - } - initialized = true; final Map categories = config.categories; final LogContext logContext = LogContext.getLogContext(); final Logger rootLogger = logContext.getLogger(""); @@ -62,7 +55,7 @@ public void initializeLogging(LogConfig config) { if (config.console.enable) { final PatternFormatter formatter; if (config.console.color && System.console() != null) { - formatter = new ColorPatternFormatter(config.console.format); + formatter = new ColorPatternFormatter(config.console.darken, config.console.format); } else { formatter = new PatternFormatter(config.console.format); } @@ -73,9 +66,30 @@ public void initializeLogging(LogConfig config) { handlers.add(handler); errorManager = handler.getLocalErrorManager(); } + if (config.file.enable) { + FileHandler handler = new FileHandler(); + FileConfig.RotationConfig rotationConfig = config.file.rotation; + if (rotationConfig.maxFileSize.isPresent() && rotationConfig.fileSuffix.isPresent()) { + PeriodicSizeRotatingFileHandler periodicSizeRotatingFileHandler = new PeriodicSizeRotatingFileHandler(); + periodicSizeRotatingFileHandler.setSuffix(rotationConfig.fileSuffix.get()); + periodicSizeRotatingFileHandler.setRotateSize(rotationConfig.maxFileSize.getAsLong()); + periodicSizeRotatingFileHandler.setRotateOnBoot(rotationConfig.rotateOnBoot); + periodicSizeRotatingFileHandler.setMaxBackupIndex(rotationConfig.maxBackupIndex); + handler = periodicSizeRotatingFileHandler; + } else if (rotationConfig.maxFileSize.isPresent()) { + SizeRotatingFileHandler sizeRotatingFileHandler = new SizeRotatingFileHandler( + rotationConfig.maxFileSize.getAsLong(), rotationConfig.maxBackupIndex); + sizeRotatingFileHandler.setRotateOnBoot(rotationConfig.rotateOnBoot); + handler = sizeRotatingFileHandler; + } else if (rotationConfig.fileSuffix.isPresent()) { + PeriodicRotatingFileHandler periodicRotatingFileHandler = new PeriodicRotatingFileHandler(); + periodicRotatingFileHandler.setSuffix(rotationConfig.fileSuffix.get()); + handler = periodicRotatingFileHandler; + } + final PatternFormatter formatter = new PatternFormatter(config.file.format); - final FileHandler handler = new FileHandler(formatter); + handler.setFormatter(formatter); handler.setAppend(true); try { handler.setFile(config.file.path); @@ -87,6 +101,16 @@ public void initializeLogging(LogConfig config) { handler.setFilter(new LogCleanupFilter(filterElements)); handlers.add(handler); } + InitialConfigurator.DELAYED_HANDLER.setHandlers(handlers.toArray(EmbeddedConfigurator.NO_HANDLERS)); } + + 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 }); + } + } } diff --git a/core/runtime/src/main/java/io/quarkus/runtime/ssl/SslContextConfiguration.java b/core/runtime/src/main/java/io/quarkus/runtime/ssl/SslContextConfiguration.java new file mode 100644 index 0000000000000..16f68a0ed0bd8 --- /dev/null +++ b/core/runtime/src/main/java/io/quarkus/runtime/ssl/SslContextConfiguration.java @@ -0,0 +1,14 @@ +package io.quarkus.runtime.ssl; + +public class SslContextConfiguration { + + private static boolean sslNativeEnabled; + + public static void setSslNativeEnabled(boolean sslNativeEnabled) { + SslContextConfiguration.sslNativeEnabled = sslNativeEnabled; + } + + public static boolean isSslNativeEnabled() { + return sslNativeEnabled; + } +} diff --git a/core/runtime/src/main/java/io/quarkus/runtime/ssl/SslContextConfigurationTemplate.java b/core/runtime/src/main/java/io/quarkus/runtime/ssl/SslContextConfigurationTemplate.java new file mode 100644 index 0000000000000..0085c578681ec --- /dev/null +++ b/core/runtime/src/main/java/io/quarkus/runtime/ssl/SslContextConfigurationTemplate.java @@ -0,0 +1,11 @@ +package io.quarkus.runtime.ssl; + +import io.quarkus.runtime.annotations.Template; + +@Template +public class SslContextConfigurationTemplate { + + public void setSslNativeEnabled(boolean sslNativeEnabled) { + SslContextConfiguration.setSslNativeEnabled(sslNativeEnabled); + } +} 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 898034825bfcd..5d78bf5816852 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 @@ -39,7 +39,7 @@ public void doAfter() { private SmallRyeConfig buildConfig(Map configMap) { final SmallRyeConfigBuilder builder = new SmallRyeConfigBuilder(); - builder.withWrapper(ExpandingConfigSource.WRAPPER); + builder.withWrapper(ExpandingConfigSource.wrapper(new ExpandingConfigSource.Cache())); builder.withSources(new PropertiesConfigSource(configMap, "test input", 500)); final SmallRyeConfig config = (SmallRyeConfig) builder.build(); cpr.registerConfig(config, classLoader); diff --git a/core/test-extension/deployment/pom.xml b/core/test-extension/deployment/pom.xml index 6e9055913d444..a996b9d5fee46 100644 --- a/core/test-extension/deployment/pom.xml +++ b/core/test-extension/deployment/pom.xml @@ -25,7 +25,7 @@ 4.0.0 - quarkus-test-extension + quarkus-test-extension-deployment Quarkus - Core - Test Extension - Deployment @@ -35,20 +35,24 @@ io.quarkus - quarkus-core + quarkus-undertow-deployment io.quarkus - quarkus-arc + quarkus-core-deployment io.quarkus - quarkus-undertow-runtime + quarkus-arc-deployment io.quarkus - quarkus-test-extension-runtime - 999-SNAPSHOT + quarkus-undertow + + + io.quarkus + quarkus-test-extension + ${project.version} @@ -57,10 +61,31 @@ quarkus-junit5-internal test + + io.quarkus + quarkus-junit5 + test + + + org.assertj + assertj-core + test + io.rest-assured rest-assured test + + + org.apache.commons + commons-lang3 + + + + + org.assertj + assertj-core + test @@ -80,5 +105,5 @@ - + diff --git a/core/test-extension/deployment/src/main/java/io/quarkus/extest/deployment/PublicKeyBuildItem.java b/core/test-extension/deployment/src/main/java/io/quarkus/extest/deployment/PublicKeyBuildItem.java new file mode 100644 index 0000000000000..d5db0ca99400e --- /dev/null +++ b/core/test-extension/deployment/src/main/java/io/quarkus/extest/deployment/PublicKeyBuildItem.java @@ -0,0 +1,17 @@ +package io.quarkus.extest.deployment; + +import java.security.interfaces.DSAPublicKey; + +import io.quarkus.builder.item.SimpleBuildItem; + +final public class PublicKeyBuildItem extends SimpleBuildItem { + private DSAPublicKey publicKey; + + public PublicKeyBuildItem(DSAPublicKey publicKey) { + this.publicKey = publicKey; + } + + public DSAPublicKey getPublicKey() { + return publicKey; + } +} diff --git a/core/test-extension/deployment/src/main/java/io/quarkus/extest/deployment/RuntimeServiceBuildItem.java b/core/test-extension/deployment/src/main/java/io/quarkus/extest/deployment/RuntimeServiceBuildItem.java new file mode 100644 index 0000000000000..211f1b6e70e12 --- /dev/null +++ b/core/test-extension/deployment/src/main/java/io/quarkus/extest/deployment/RuntimeServiceBuildItem.java @@ -0,0 +1,17 @@ +package io.quarkus.extest.deployment; + +import io.quarkus.builder.item.SimpleBuildItem; +import io.quarkus.extest.runtime.RuntimeXmlConfigService; +import io.quarkus.runtime.RuntimeValue; + +final public class RuntimeServiceBuildItem extends SimpleBuildItem { + private RuntimeValue service; + + public RuntimeServiceBuildItem(RuntimeValue service) { + this.service = service; + } + + public RuntimeValue getService() { + return service; + } +} diff --git a/core/test-extension/deployment/src/main/java/io/quarkus/extest/deployment/TestBeanBuildItem.java b/core/test-extension/deployment/src/main/java/io/quarkus/extest/deployment/TestBeanBuildItem.java index 9a1552703f14a..6fd48d69b059e 100644 --- a/core/test-extension/deployment/src/main/java/io/quarkus/extest/deployment/TestBeanBuildItem.java +++ b/core/test-extension/deployment/src/main/java/io/quarkus/extest/deployment/TestBeanBuildItem.java @@ -1,9 +1,11 @@ package io.quarkus.extest.deployment; -import org.jboss.builder.item.MultiBuildItem; - +import io.quarkus.builder.item.MultiBuildItem; import io.quarkus.extest.runtime.IConfigConsumer; +/** + * Represents beans annotated with @TestAnnotation that also implement IConfigConsumer + */ public final class TestBeanBuildItem extends MultiBuildItem { private Class configConsumer; 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 16efb0e0b4781..a7b5957588203 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 @@ -1,9 +1,32 @@ package io.quarkus.extest.deployment; import static io.quarkus.deployment.annotations.ExecutionTime.RUNTIME_INIT; +import static io.quarkus.deployment.annotations.ExecutionTime.STATIC_INIT; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.Provider; +import java.security.Security; +import java.security.interfaces.DSAPublicKey; +import java.security.spec.X509EncodedKeySpec; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Base64; import java.util.Collection; +import java.util.HashSet; import java.util.List; +import java.util.Set; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Unmarshaller; import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.ClassInfo; @@ -11,28 +34,35 @@ import org.jboss.jandex.IndexView; import org.jboss.logging.Logger; +import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.deployment.BeanArchiveIndexBuildItem; import io.quarkus.arc.deployment.BeanContainerBuildItem; -import io.quarkus.arc.deployment.BeanContainerListenerBuildItem; import io.quarkus.arc.deployment.BeanDefiningAnnotationBuildItem; -import io.quarkus.arc.runtime.BeanContainerListener; 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.LaunchModeBuildItem; +import io.quarkus.deployment.builditem.ObjectSubstitutionBuildItem; import io.quarkus.deployment.builditem.ServiceStartBuildItem; -import io.quarkus.deployment.recording.RecorderContext; -import io.quarkus.extest.runtime.IConfigConsumer; -import io.quarkus.extest.runtime.ObjectOfValue; -import io.quarkus.extest.runtime.ObjectValueOf; -import io.quarkus.extest.runtime.TestAnnotation; -import io.quarkus.extest.runtime.TestBuildAndRunTimeConfig; -import io.quarkus.extest.runtime.TestBuildTimeConfig; -import io.quarkus.extest.runtime.TestRunTimeConfig; -import io.quarkus.extest.runtime.TestTemplate; +import io.quarkus.deployment.builditem.ShutdownContextBuildItem; +import io.quarkus.deployment.builditem.substrate.ReflectiveClassBuildItem; +import io.quarkus.deployment.builditem.substrate.SubstrateResourceBuildItem; +import io.quarkus.deployment.builditem.substrate.SubstrateResourceBundleBuildItem; +import io.quarkus.extest.runtime.*; +import io.quarkus.extest.runtime.beans.CommandServlet; +import io.quarkus.extest.runtime.beans.PublicKeyProducer; +import io.quarkus.extest.runtime.config.ObjectOfValue; +import io.quarkus.extest.runtime.config.ObjectValueOf; +import io.quarkus.extest.runtime.config.TestBuildAndRunTimeConfig; +import io.quarkus.extest.runtime.config.TestBuildTimeConfig; +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.subst.DSAPublicKeyObjectSubstitution; +import io.quarkus.extest.runtime.subst.KeyProxy; import io.quarkus.runtime.RuntimeValue; +import io.quarkus.undertow.deployment.ServletBuildItem; /** * A test extension deployment processor @@ -40,10 +70,16 @@ public final class TestProcessor { static final Logger log = Logger.getLogger(TestProcessor.class); static DotName TEST_ANNOTATION = DotName.createSimple(TestAnnotation.class.getName()); + static DotName TEST_ANNOTATION_SCOPE = DotName.createSimple(ApplicationScoped.class.getName()); + + @Inject + BuildProducer resource; + @Inject + BuildProducer resourceBundle; + TestConfigRoot configRoot; TestBuildTimeConfig buildTimeConfig; TestBuildAndRunTimeConfig buildAndRunTimeConfig; - TestRunTimeConfig runTimeConfig; /** * Register a extension capability and feature @@ -61,16 +97,145 @@ FeatureBuildItem featureBuildItem() { * @return */ @BuildStep - BeanDefiningAnnotationBuildItem registerX() { - return new BeanDefiningAnnotationBuildItem(TEST_ANNOTATION); + BeanDefiningAnnotationBuildItem registerBeanDefinningAnnotations() { + return new BeanDefiningAnnotationBuildItem(TEST_ANNOTATION, TEST_ANNOTATION_SCOPE); + } + + @BuildStep + void registerNativeImageReources() { + resource.produce(new SubstrateResourceBuildItem("/DSAPublicKey.encoded")); + } + + /** + * Register the CDI beans that are needed by the test extension + * + * @param additionalBeans - producer for additional bean items + */ + @BuildStep + void registerAdditionalBeans(BuildProducer additionalBeans) { + AdditionalBeanBuildItem additionalBeansItem = AdditionalBeanBuildItem.builder() + .addBeanClass(PublicKeyProducer.class) + .addBeanClass(CommandServlet.class) + .setRemovable() + .build(); + additionalBeans.produce(additionalBeansItem); + } + + /** + * Parse an XML configuration using JAXB into an XmlConfig instance graph + * + * @param template - runtime template + * @return RuntimeServiceBuildItem + * @throws JAXBException + */ + @BuildStep + @Record(STATIC_INIT) + RuntimeServiceBuildItem parseServiceXmlConfig(TestTemplate template) throws JAXBException { + RuntimeServiceBuildItem serviceBuildItem = null; + JAXBContext context = JAXBContext.newInstance(XmlConfig.class); + Unmarshaller unmarshaller = context.createUnmarshaller(); + InputStream is = getClass().getResourceAsStream("/config.xml"); + if (is != null) { + log.infof("Have XmlConfig, loading"); + XmlConfig config = (XmlConfig) unmarshaller.unmarshal(is); + log.infof("Loaded XmlConfig, creating service"); + RuntimeValue service = template.initRuntimeService(config); + serviceBuildItem = new RuntimeServiceBuildItem(service); + } + return serviceBuildItem; + } + + /** + * Have the runtime template start the service and install a shutdown hook + * + * @param template - runtime template + * @param shutdownContextBuildItem - ShutdownContext information + * @param serviceBuildItem - previously created RuntimeXmlConfigService container + * @return ServiceStartBuildItem - build item indicating the RuntimeXmlConfigService startuup + * @throws IOException - on failure + */ + @BuildStep + @Record(RUNTIME_INIT) + ServiceStartBuildItem startRuntimeService(TestTemplate template, ShutdownContextBuildItem shutdownContextBuildItem, + RuntimeServiceBuildItem serviceBuildItem) throws IOException { + if (serviceBuildItem != null) { + log.info("Registering service start"); + template.startRuntimeService(shutdownContextBuildItem, serviceBuildItem.getService()); + } else { + log.info("No RuntimeServiceBuildItem seen, check config.xml"); + } + return new ServiceStartBuildItem("RuntimeXmlConfigService"); + } + + /** + * Load a DSAPublicKey from a resource and create an instance of it + * + * @param template - runtime template + * @return PublicKeyBuildItem for the DSAPublicKey + * @throws IOException - on resource load failure + * @throws GeneralSecurityException - on key creation failure + */ + @BuildStep + @Record(STATIC_INIT) + PublicKeyBuildItem loadDSAPublicKey(TestTemplate template, + BuildProducer substitutions) throws IOException, GeneralSecurityException { + String path = configRoot.dsaKeyLocation; + InputStream is = getClass().getResourceAsStream(path); + if (is == null) { + throw new IOException("Failed to load resource: " + path); + } + BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8)); + String base64 = reader.readLine(); + reader.close(); + byte[] encoded = Base64.getDecoder().decode(base64); + KeyFactory keyFactory = KeyFactory.getInstance("DSA"); + X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(encoded); + DSAPublicKey publicKey = (DSAPublicKey) keyFactory.generatePublic(publicKeySpec); + // Register how to serialize DSAPublicKey + ObjectSubstitutionBuildItem.Holder holder = new ObjectSubstitutionBuildItem.Holder( + DSAPublicKey.class, KeyProxy.class, DSAPublicKeyObjectSubstitution.class); + ObjectSubstitutionBuildItem keysub = new ObjectSubstitutionBuildItem(holder); + substitutions.produce(keysub); + log.infof("loadDSAPublicKey run"); + return new PublicKeyBuildItem(publicKey); + } + + /** + * Have the runtime register the public key with the public key producer bean + * + * @param template - runtime template + * @param publicKey - previously loaded public key + * @param beanContainer - BeanContainer build item + */ + @BuildStep + @Record(RUNTIME_INIT) + void loadDSAPublicKeyProducer(TestTemplate template, PublicKeyBuildItem publicKey, BeanContainerBuildItem beanContainer) { + template.loadDSAPublicKeyProducer(publicKey.getPublicKey(), beanContainer.getValue()); + } + + /** + * Register a servlet used for interacting with native image for testing + * + * @return ServletBuildItem + */ + @BuildStep + ServletBuildItem createServlet() { + ServletBuildItem servletBuildItem = ServletBuildItem.builder("commands", CommandServlet.class.getName()) + .addMapping("/commands/*") + .build(); + return servletBuildItem; } /** * Validate the expected BUILD_TIME configuration */ @BuildStep - @Record(ExecutionTime.STATIC_INIT) + @Record(STATIC_INIT) void checkConfig() { + if (!configRoot.validateBuildConfig) { + return; + } + // Deployment time configuration if (!buildTimeConfig.btSBV.getValue().equals("StringBasedValue")) { throw new IllegalStateException("buildTimeConfig.btSBV != StringBasedValue; " + buildTimeConfig.btSBV.getValue()); @@ -166,7 +331,7 @@ void checkConfig() { * @param testBeanProducer - producer for located Class bean types */ @BuildStep - @Record(ExecutionTime.STATIC_INIT) + @Record(STATIC_INIT) void scanForBeans(TestTemplate template, BeanArchiveIndexBuildItem beanArchiveIndex, BuildProducer testBeanProducer) { IndexView indexView = beanArchiveIndex.getIndex(); @@ -174,11 +339,16 @@ void scanForBeans(TestTemplate template, BeanArchiveIndexBuildItem beanArchiveIn for (AnnotationInstance ann : testBeans) { ClassInfo beanClassInfo = ann.target().asClass(); try { - Class beanClass = (Class) Class.forName(beanClassInfo.name().toString()); - testBeanProducer.produce(new TestBeanBuildItem(beanClass)); - System.out.printf("Configured bean: %s\n", beanClass); + boolean isConfigConsumer = beanClassInfo.interfaceNames() + .stream() + .anyMatch(dotName -> dotName.equals(DotName.createSimple(IConfigConsumer.class.getName()))); + if (isConfigConsumer) { + Class beanClass = (Class) Class.forName(beanClassInfo.name().toString()); + testBeanProducer.produce(new TestBeanBuildItem(beanClass)); + log.infof("Configured bean: %s", beanClass); + } } catch (ClassNotFoundException e) { - e.printStackTrace(); + log.warn("Failed to load bean class", e); } } } @@ -189,20 +359,83 @@ void scanForBeans(TestTemplate template, BeanArchiveIndexBuildItem beanArchiveIn * @param template - runtime template * @param testBeans - types of IConfigConsumer found * @param beanContainer - bean container to create test bean in + * @param runTimeConfig - The RUN_TIME config phase root config */ @BuildStep @Record(RUNTIME_INIT) - void configureBeans(TestTemplate template, List testBeans, BeanContainerBuildItem beanContainer) { + void configureBeans(TestTemplate template, List testBeans, + BeanContainerBuildItem beanContainer, + TestRunTimeConfig runTimeConfig) { for (TestBeanBuildItem testBeanBuildItem : testBeans) { Class beanClass = testBeanBuildItem.getConfigConsumer(); template.configureBeans(beanContainer.getValue(), beanClass, buildAndRunTimeConfig, runTimeConfig); } } + /** + * Test for https://github.com/quarkusio/quarkus/issues/1633 + * + * @param template - runtime template + */ + @BuildStep + @Record(RUNTIME_INIT) + void referencePrimitiveTypeClasses(TestTemplate template) { + HashSet> allPrimitiveTypes = new HashSet<>(); + allPrimitiveTypes.add(byte.class); + allPrimitiveTypes.add(char.class); + allPrimitiveTypes.add(short.class); + allPrimitiveTypes.add(int.class); + allPrimitiveTypes.add(long.class); + allPrimitiveTypes.add(float.class); + allPrimitiveTypes.add(double.class); + allPrimitiveTypes.add(byte[].class); + allPrimitiveTypes.add(char[].class); + allPrimitiveTypes.add(short[].class); + allPrimitiveTypes.add(int[].class); + allPrimitiveTypes.add(long[].class); + allPrimitiveTypes.add(float[].class); + allPrimitiveTypes.add(double[].class); + template.validateTypes(allPrimitiveTypes); + } + @BuildStep @Record(RUNTIME_INIT) ServiceStartBuildItem boot(LaunchModeBuildItem launchMode) { log.infof("boot, launchMode=%s", launchMode.getLaunchMode()); return new ServiceStartBuildItem("test-service"); } + + @BuildStep + @Record(STATIC_INIT) + void registerSUNProvider(BuildProducer classes) { + Provider provider = Security.getProvider("SUN"); + ArrayList providerClasses = new ArrayList<>(); + providerClasses.add(provider.getClass().getName()); + Set services = provider.getServices(); + for (Provider.Service service : services) { + String serviceClass = service.getClassName(); + providerClasses.add(serviceClass); + // Need to pull in the key classes + String supportedKeyClasses = service.getAttribute("SupportedKeyClasses"); + if (supportedKeyClasses != null) { + String[] keyClasses = supportedKeyClasses.split("\\|"); + providerClasses.addAll(Arrays.asList(keyClasses)); + } + } + for (String className : providerClasses) { + classes.produce(new ReflectiveClassBuildItem(true, true, className)); + log.debugf("Register SUN.provider class: %s", className); + } + } + + @BuildStep + void registerFinalFieldReflectionObject(BuildProducer classes) { + ReflectiveClassBuildItem finalField = ReflectiveClassBuildItem + .builder(FinalFieldReflectionObject.class.getName()) + .methods(true) + .fields(true) + .finalFieldsWritable(true) + .build(); + classes.produce(finalField); + } } diff --git a/core/test-extension/deployment/src/main/resources/DSAPublicKey.encoded b/core/test-extension/deployment/src/main/resources/DSAPublicKey.encoded new file mode 100644 index 0000000000000..f3d3e7a80c453 --- /dev/null +++ b/core/test-extension/deployment/src/main/resources/DSAPublicKey.encoded @@ -0,0 +1 @@ +MIIDQjCCAjUGByqGSM44BAEwggIoAoIBAQCPeTXZuarpv6vtiHrPSVG28y7FnjuvNxjo6sSWHz79NgbnQ1GpxBgzObgJ58KuHFObp0dbhdARrbi0eYd1SYRpXKwOjxSzNggooi/6JxEKPWKpk0U0CaD+aWxGWPhL3SCBnDcJoBBXsZWtzQAjPbpUhLYpH51kjviDRIZ3l5zsBLQ0pqwudemYXeI9sCkvwRGMn/qdgYHnM423krcw17njSVkvaAmYchU5Feo9a4tGU8YzRY+AOzKkwuDycpAlbk4/ijsIOKHEUOThjBopo33fXqFD3ktm/wSQPtXPFiPhWNSHxgjpfyEc2B3KI8tuOAdl+CLjQr5ITAV2OTlgHNZnAh0AuvaWpoV499/e5/pnyXfHhe8ysjO65YDAvNVpXQKCAQAWplxYIEhQcE51AqOXVwQNNNo6NHjBVNTkpcAtJC7gT5bmHkvQkEq9rI837rHgnzGC0jyQQ8tkL4gAQWDt+coJsyB2p5wypifyRz6Rh5uixOdEvSCBVEy1W4AsNo0fqD7UielOD6BojjJCilx4xHjGjQUntxyaOrsLC+EsRGiWOefTznTbEBplqiuH9kxoJts+xy9LVZmDS7TtsC98kOmkltOlXVNb6/xF1PYZ9j897buHOSXC8iTgdzEpbaiH7B5HSPh++1/et1SEMWsiMt7lU92vAhErDR8C2jCXMiT+J67ai51LKSLZuovjntnhA6Y8UoELxoi34u1DFuHvF9veA4IBBQACggEAK6IeZShhydDUM5XsOJ/VAYPOgrnLr30AfKWLR39+FJBunVMWNPpvO5D9dU7B6nmSiLATpwhBDNEhyJ0ltmBGuFDBAkKkqE4l6l2iVh+C1TyYliv1P2LCJFNgrAJxyr+5Q5zM9hUgfbT66xnwCf/4aiO7nBlj4wOL3l9ABVllYifMZyKVYFGluXmo+jyyeAcCtzHi5SABbTOQJN0WXTlGtzxLFQ0QErDGhP1/A6z5lw5VHJn2aWMeTCaH+rJZpQfM8b2VWr7UEljqFgpSIHbrImuXcf2nP6uZLKFiDdAjDUyj0h2jXwwcdhwWXuhOEv8XIilkc9nMcPLqbdcQ4M5agg== \ No newline at end of file diff --git a/core/test-extension/deployment/src/main/resources/application-console-output.properties b/core/test-extension/deployment/src/main/resources/application-console-output.properties new file mode 100644 index 0000000000000..8b90b157e5be9 --- /dev/null +++ b/core/test-extension/deployment/src/main/resources/application-console-output.properties @@ -0,0 +1,6 @@ +quarkus.log.level=ALL +quarkus.log.console.enable=true +quarkus.log.console.level=ALL +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/main/resources/application-file-output-log.properties b/core/test-extension/deployment/src/main/resources/application-file-output-log.properties new file mode 100644 index 0000000000000..a53c0ac4dc9a1 --- /dev/null +++ b/core/test-extension/deployment/src/main/resources/application-file-output-log.properties @@ -0,0 +1,5 @@ +quarkus.log.level=ALL +quarkus.log.file.enable=true +quarkus.log.file.level=INFO +quarkus.log.file.format=%d{HH:mm:ss} %-5p [%c{2.}]] (%t) %s%e%n +quarkus.root.dsa-key-location=/DSAPublicKey.encoded diff --git a/core/test-extension/deployment/src/main/resources/application-periodic-file-log-rotating.properties b/core/test-extension/deployment/src/main/resources/application-periodic-file-log-rotating.properties new file mode 100644 index 0000000000000..eb3a6823fb977 --- /dev/null +++ b/core/test-extension/deployment/src/main/resources/application-periodic-file-log-rotating.properties @@ -0,0 +1,6 @@ +quarkus.log.level=ALL +quarkus.log.file.enable=true +quarkus.log.file.level=INFO +quarkus.log.file.format=%d{HH:mm:ss} %-5p [%c{2.}]] (%t) %s%e%n +quarkus.log.file.rotation.file-suffix=yyyy-MM-dd +quarkus.root.dsa-key-location=/DSAPublicKey.encoded diff --git a/core/test-extension/deployment/src/main/resources/application-periodic-size-file-log-rotating.properties b/core/test-extension/deployment/src/main/resources/application-periodic-size-file-log-rotating.properties new file mode 100644 index 0000000000000..b11e66c852da4 --- /dev/null +++ b/core/test-extension/deployment/src/main/resources/application-periodic-size-file-log-rotating.properties @@ -0,0 +1,8 @@ +quarkus.log.level=ALL +quarkus.log.file.enable=true +quarkus.log.file.level=INFO +quarkus.log.file.format=%d{HH:mm:ss} %-5p [%c{2.}]] (%t) %s%e%n +quarkus.log.file.rotation.max-file-size=100 +quarkus.log.file.rotation.file-suffix=yyyy-MM-dd +quarkus.log.file.rotation.rotate-on-boot=false +quarkus.root.dsa-key-location=/DSAPublicKey.encoded diff --git a/core/test-extension/deployment/src/main/resources/application-size-file-log-rotating.properties b/core/test-extension/deployment/src/main/resources/application-size-file-log-rotating.properties new file mode 100644 index 0000000000000..1aff413e13bd4 --- /dev/null +++ b/core/test-extension/deployment/src/main/resources/application-size-file-log-rotating.properties @@ -0,0 +1,8 @@ +quarkus.log.level=ALL +quarkus.log.file.enable=true +quarkus.log.file.level=INFO +quarkus.log.file.format=%d{HH:mm:ss} %-5p [%c{2.}]] (%t) %s%e%n +quarkus.log.file.rotation.max-file-size=100 +quarkus.log.file.rotation.max-backup-index=100 +quarkus.log.file.rotation.rotate-on-boot=true +quarkus.root.dsa-key-location=/DSAPublicKey.encoded diff --git a/core/test-extension/deployment/src/test/resources/application.properties b/core/test-extension/deployment/src/main/resources/application.properties similarity index 90% rename from core/test-extension/deployment/src/test/resources/application.properties rename to core/test-extension/deployment/src/main/resources/application.properties index 0da38b798bb21..4ae9422f68f4e 100644 --- a/core/test-extension/deployment/src/test/resources/application.properties +++ b/core/test-extension/deployment/src/main/resources/application.properties @@ -1,3 +1,14 @@ +# Log settings +quarkus.log.level=TRACE +quarkus.log.file.enable=true +quarkus.log.file.level=TRACE +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 + +# Have the TestProcessor validate the build time configuration below +quarkus.root.validate-build-config=true ### Configuration settings for the TestBuildTimeConfig config root quarkus.bt.bt-string-opt=btStringOptValue diff --git a/core/test-extension/deployment/src/main/resources/config.xml b/core/test-extension/deployment/src/main/resources/config.xml new file mode 100644 index 0000000000000..6d0057ee81257 --- /dev/null +++ b/core/test-extension/deployment/src/main/resources/config.xml @@ -0,0 +1,17 @@ + + +

localhost
+ 12345 + + + name1 + model1 + 2019-03-21T02:40:38Z + + + name2 + model2 + 2019-03-23T11:44:00Z + + + \ No newline at end of file diff --git a/core/test-extension/deployment/src/test/java/io/quarkus/extest/ConfiguredBean.java b/core/test-extension/deployment/src/test/java/io/quarkus/extest/ConfiguredBean.java index 549fd2fea4410..fd18a68fbee1c 100644 --- a/core/test-extension/deployment/src/test/java/io/quarkus/extest/ConfiguredBean.java +++ b/core/test-extension/deployment/src/test/java/io/quarkus/extest/ConfiguredBean.java @@ -1,26 +1,24 @@ package io.quarkus.extest; -import javax.enterprise.context.ApplicationScoped; import javax.enterprise.event.Observes; -import javax.inject.Inject; import io.quarkus.extest.runtime.IConfigConsumer; import io.quarkus.extest.runtime.TestAnnotation; -import io.quarkus.extest.runtime.TestBuildAndRunTimeConfig; -import io.quarkus.extest.runtime.TestRunTimeConfig; +import io.quarkus.extest.runtime.config.TestBuildAndRunTimeConfig; +import io.quarkus.extest.runtime.config.TestRunTimeConfig; +import io.quarkus.runtime.ShutdownEvent; import io.quarkus.runtime.StartupEvent; /** * A sample bean */ @TestAnnotation -@ApplicationScoped public class ConfiguredBean implements IConfigConsumer { TestRunTimeConfig runTimeConfig; TestBuildAndRunTimeConfig buildTimeConfig; public ConfiguredBean() { - System.out.printf("ConfiguredBean.ctor, %s\n", super.toString()); + System.out.printf("ConfiguredBean.ctor, %s%n", super.toString()); } /** @@ -30,7 +28,7 @@ public ConfiguredBean() { */ @Override public void loadConfig(TestBuildAndRunTimeConfig buildTimeConfig, TestRunTimeConfig runTimeConfig) { - System.out.printf("loadConfig, buildTimeConfig=%s, runTimeConfig=%s\n", buildTimeConfig, runTimeConfig); + System.out.printf("loadConfig, buildTimeConfig=%s, runTimeConfig=%s%n", buildTimeConfig, runTimeConfig); this.buildTimeConfig = buildTimeConfig; this.runTimeConfig = runTimeConfig; } @@ -41,7 +39,11 @@ public void loadConfig(TestBuildAndRunTimeConfig buildTimeConfig, TestRunTimeCon * @param event */ void onStart(@Observes StartupEvent event) { - System.out.printf("onStart, event=%s\n", event); + System.out.printf("onStart, event=%s%n", event); + } + + void onStop(@Observes ShutdownEvent event) { + System.out.printf("onStop, event=%s%n", event); } public TestRunTimeConfig getRunTimeConfig() { @@ -54,8 +56,6 @@ public TestBuildAndRunTimeConfig getBuildTimeConfig() { @Override public String toString() { - return "ConfiguredBean{" + - "runTimeConfig=" + runTimeConfig + - '}'; + return "ConfiguredBean{runTimeConfig=" + runTimeConfig + '}'; } } 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 f3d6935bce38a..a73a943744483 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 @@ -1,5 +1,12 @@ package io.quarkus.extest; +import static org.hamcrest.Matchers.is; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.Socket; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -14,12 +21,13 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -import io.quarkus.extest.runtime.NestedConfig; -import io.quarkus.extest.runtime.ObjectOfValue; -import io.quarkus.extest.runtime.ObjectValueOf; -import io.quarkus.extest.runtime.TestBuildAndRunTimeConfig; -import io.quarkus.extest.runtime.TestRunTimeConfig; +import io.quarkus.extest.runtime.config.NestedConfig; +import io.quarkus.extest.runtime.config.ObjectOfValue; +import io.quarkus.extest.runtime.config.ObjectValueOf; +import io.quarkus.extest.runtime.config.TestBuildAndRunTimeConfig; +import io.quarkus.extest.runtime.config.TestRunTimeConfig; import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; /** * Test driver for the test-extension @@ -39,7 +47,7 @@ public class ConfiguredBeanTest { */ @Test public void validateConfiguredBean() { - System.out.printf("validateConfiguredBean, %s\n", configuredBean); + System.out.printf("validateConfiguredBean, %s%n", configuredBean); Assertions.assertNotNull(configuredBean); Assertions.assertNotNull(configuredBean.getBuildTimeConfig()); Assertions.assertNotNull(configuredBean.getRunTimeConfig()); @@ -105,6 +113,8 @@ public void validateBuildTimeConfig() { throw new IllegalStateException( "buildTimeConfig.allValues.simpleMap.size != 2; " + buildTimeConfig.allValues.nestedConfigMap.size()); } + Assertions.assertNotEquals("${java.vm.version}", buildTimeConfig.allValues.expandedDefault); + Assertions.assertFalse(buildTimeConfig.allValues.expandedDefault.isEmpty()); } /** @@ -149,6 +159,8 @@ public void validateRuntimeConfig() { Assertions.assertEquals(1, runTimeConfig.allValues.longList.get(0).longValue()); Assertions.assertEquals(2, runTimeConfig.allValues.longList.get(1).longValue()); Assertions.assertEquals(3, runTimeConfig.allValues.longList.get(2).longValue()); + Assertions.assertNotEquals("${java.vm.version}", runTimeConfig.allValues.expandedDefault); + Assertions.assertFalse(runTimeConfig.allValues.expandedDefault.isEmpty()); } /** @@ -185,4 +197,29 @@ public void validateRuntimeConfigMap() { Assertions.assertEquals(Arrays.asList("value4", "value5"), stringListMap.get("key2")); Assertions.assertEquals(Collections.singletonList("value6"), stringListMap.get("key3")); } + + /** + * Test the RuntimeXmlConfigService using old school sockets + */ + @Test + public void testRuntimeXmlConfigService() throws Exception { + // From config.xml + Socket socket = new Socket("localhost", 12345); + OutputStream os = socket.getOutputStream(); + os.write("testRuntimeXmlConfigService\n".getBytes("UTF-8")); + os.flush(); + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8))) { + String reply = reader.readLine(); + Assertions.assertEquals("testRuntimeXmlConfigService-ack", reply); + } + socket.close(); + } + + @Test + public void verifyCommandServlet() { + RestAssured.when().get("/commands/ping").then() + .body(is("/ping-ack")); + } + } diff --git a/core/test-extension/deployment/src/test/java/io/quarkus/extest/DSAKeyUtil.java b/core/test-extension/deployment/src/test/java/io/quarkus/extest/DSAKeyUtil.java new file mode 100644 index 0000000000000..34a4c032463ff --- /dev/null +++ b/core/test-extension/deployment/src/test/java/io/quarkus/extest/DSAKeyUtil.java @@ -0,0 +1,41 @@ +package io.quarkus.extest; + +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.Provider; +import java.security.Security; +import java.security.interfaces.DSAPublicKey; +import java.security.spec.X509EncodedKeySpec; +import java.util.Base64; + +/** + * Utility main to validate that the DSA key provider is the default SUN provider enabled by Graal + * and to generate a DSA public key encoded string + */ +public class DSAKeyUtil { + public static void main(String[] args) throws Exception { + Provider provider = Security.getProvider("SUN"); + System.out.println(provider.getInfo()); + for (Provider.Service service : provider.getServices()) { + System.out.println(service); + } + + KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA"); + kpg.initialize(2048); + System.out.println("DSA.provider: " + kpg.getProvider()); + KeyFactory keyFactory = KeyFactory.getInstance("DSA"); + System.out.println("DSA.provider: " + keyFactory.getProvider()); + + KeyPair pair = kpg.genKeyPair(); + DSAPublicKey publicKey = (DSAPublicKey) pair.getPublic(); + byte[] encoded = publicKey.getEncoded(); + X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(encoded); + DSAPublicKey publicKey1 = (DSAPublicKey) keyFactory.generatePublic(publicKeySpec); + System.out.printf("keys are equal: %s%n", publicKey1.equals(publicKey)); + + String base64 = Base64.getEncoder().encodeToString(encoded); + System.out.println(base64); + } + +} diff --git a/core/test-extension/deployment/src/test/java/logging/ConsoleHandlerTest.java b/core/test-extension/deployment/src/test/java/logging/ConsoleHandlerTest.java new file mode 100644 index 0000000000000..423d2e7e3bbf7 --- /dev/null +++ b/core/test-extension/deployment/src/test/java/logging/ConsoleHandlerTest.java @@ -0,0 +1,48 @@ +package logging; + +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 { + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addAsResource("application-console-output.properties", "application.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.asList(delayedHandler.getHandlers()).stream().filter(h -> (h instanceof ConsoleHandler)) + .findFirst().get(); + assertThat(handler).isNotNull(); + assertThat(handler.getLevel()).isEqualTo(Level.ALL); + + 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/core/test-extension/deployment/src/test/java/logging/FileHandlerTest.java b/core/test-extension/deployment/src/test/java/logging/FileHandlerTest.java new file mode 100644 index 0000000000000..ea65c56dd03ca --- /dev/null +++ b/core/test-extension/deployment/src/test/java/logging/FileHandlerTest.java @@ -0,0 +1,49 @@ +package logging; + +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 { + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addAsResource("application-file-output-log.properties", "application.properties")); + + @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.asList(delayedHandler.getHandlers()).stream().filter(h -> (h instanceof FileHandler)) + .findFirst().get(); + assertThat(handler).isNotNull(); + assertThat(handler.getLevel()).isEqualTo(Level.INFO); + + Formatter formatter = handler.getFormatter(); + assertThat(formatter).isInstanceOf(PatternFormatter.class); + + PatternFormatter patternFormatter = (PatternFormatter) formatter; + assertThat(patternFormatter.getPattern()).isEqualTo("%d{HH:mm:ss} %-5p [%c{2.}]] (%t) %s%e%n"); + } + +} diff --git a/core/test-extension/deployment/src/test/java/logging/PeriodicRotatingLoggingTest.java b/core/test-extension/deployment/src/test/java/logging/PeriodicRotatingLoggingTest.java new file mode 100644 index 0000000000000..e0e905947ee64 --- /dev/null +++ b/core/test-extension/deployment/src/test/java/logging/PeriodicRotatingLoggingTest.java @@ -0,0 +1,48 @@ +package logging; + +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 { + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addAsResource("application-periodic-file-log-rotating.properties", "application.properties")); + + @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.asList(delayedHandler.getHandlers()).stream() + .filter(h -> (h instanceof PeriodicRotatingFileHandler)) + .findFirst().get(); + assertThat(handler).isNotNull(); + assertThat(handler.getLevel()).isEqualTo(Level.INFO); + + Formatter formatter = handler.getFormatter(); + assertThat(formatter).isInstanceOf(PatternFormatter.class); + PatternFormatter patternFormatter = (PatternFormatter) formatter; + assertThat(patternFormatter.getPattern()).isEqualTo("%d{HH:mm:ss} %-5p [%c{2.}]] (%t) %s%e%n"); + } +} diff --git a/core/test-extension/deployment/src/test/java/logging/PeriodicSizeRotatingLoggingTest.java b/core/test-extension/deployment/src/test/java/logging/PeriodicSizeRotatingLoggingTest.java new file mode 100644 index 0000000000000..35299c5257e25 --- /dev/null +++ b/core/test-extension/deployment/src/test/java/logging/PeriodicSizeRotatingLoggingTest.java @@ -0,0 +1,51 @@ +package logging; + +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 { + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addAsResource("application-periodic-size-file-log-rotating.properties", "application.properties")); + + @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.asList(delayedHandler.getHandlers()).stream() + .filter(h -> (h instanceof PeriodicSizeRotatingFileHandler)) + .findFirst().get(); + assertThat(handler).isNotNull(); + assertThat(handler.getLevel()).isEqualTo(Level.INFO); + + Formatter formatter = handler.getFormatter(); + assertThat(formatter).isInstanceOf(PatternFormatter.class); + PatternFormatter patternFormatter = (PatternFormatter) formatter; + assertThat(patternFormatter.getPattern()).isEqualTo("%d{HH:mm:ss} %-5p [%c{2.}]] (%t) %s%e%n"); + + PeriodicSizeRotatingFileHandler periodicSizeRotatingFileHandler = (PeriodicSizeRotatingFileHandler) handler; + assertThat(periodicSizeRotatingFileHandler.isRotateOnBoot()).isFalse(); + } +} diff --git a/core/test-extension/deployment/src/test/java/logging/SizeRotatingLoggingTest.java b/core/test-extension/deployment/src/test/java/logging/SizeRotatingLoggingTest.java new file mode 100644 index 0000000000000..81895f379035e --- /dev/null +++ b/core/test-extension/deployment/src/test/java/logging/SizeRotatingLoggingTest.java @@ -0,0 +1,50 @@ +package logging; + +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 { + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addAsResource("application-size-file-log-rotating.properties", "application.properties")); + + @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.asList(delayedHandler.getHandlers()).stream() + .filter(h -> (h instanceof SizeRotatingFileHandler)) + .findFirst().get(); + assertThat(handler).isNotNull(); + assertThat(handler.getLevel()).isEqualTo(Level.INFO); + + Formatter formatter = handler.getFormatter(); + assertThat(formatter).isInstanceOf(PatternFormatter.class); + PatternFormatter patternFormatter = (PatternFormatter) formatter; + assertThat(patternFormatter.getPattern()).isEqualTo("%d{HH:mm:ss} %-5p [%c{2.}]] (%t) %s%e%n"); + SizeRotatingFileHandler sizeRotatingFileHandler = (SizeRotatingFileHandler) handler; + assertThat(sizeRotatingFileHandler.isRotateOnBoot()).isTrue(); + } +} diff --git a/core/test-extension/pom.xml b/core/test-extension/pom.xml index 2763492d23dae..076d3e4f1254e 100644 --- a/core/test-extension/pom.xml +++ b/core/test-extension/pom.xml @@ -47,6 +47,16 @@ true
+ + org.sonatype.plugins + nexus-staging-maven-plugin + ${nexus-staging-maven-plugin.version} + + true + +
+ + diff --git a/core/test-extension/runtime/pom.xml b/core/test-extension/runtime/pom.xml index 441fae4a13c3e..172bed93524ea 100644 --- a/core/test-extension/runtime/pom.xml +++ b/core/test-extension/runtime/pom.xml @@ -25,13 +25,26 @@ 4.0.0 - quarkus-test-extension-runtime + quarkus-test-extension Quarkus - Core - Test Extension - Runtime + + + + + io.quarkus + quarkus-bom + ${project.version} + pom + import + + + + io.quarkus - quarkus-arc-runtime + quarkus-arc com.oracle.substratevm @@ -51,14 +64,28 @@ io.quarkus - quarkus-core-runtime + quarkus-core + + + + javax.xml.bind + jaxb-api + + + org.glassfish.jaxb + jaxb-runtime + + + com.sun.activation + jakarta.activation - + - maven-dependency-plugin + io.quarkus + quarkus-bootstrap-maven-plugin maven-compiler-plugin diff --git a/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/FinalFieldReflectionObject.java b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/FinalFieldReflectionObject.java new file mode 100644 index 0000000000000..6fb72aa8cfbd4 --- /dev/null +++ b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/FinalFieldReflectionObject.java @@ -0,0 +1,17 @@ +package io.quarkus.extest.runtime; + +public class FinalFieldReflectionObject { + private final String value; + + public FinalFieldReflectionObject() { + this.value = null; + } + + public FinalFieldReflectionObject(String value) { + this.value = value; + } + + public String getValue() { + return value; + } +} diff --git a/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/IConfigConsumer.java b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/IConfigConsumer.java index 8c253baa73115..1e6b9e3c6d13e 100644 --- a/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/IConfigConsumer.java +++ b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/IConfigConsumer.java @@ -1,5 +1,8 @@ package io.quarkus.extest.runtime; +import io.quarkus.extest.runtime.config.TestBuildAndRunTimeConfig; +import io.quarkus.extest.runtime.config.TestRunTimeConfig; + /** * Interface used to pass the runtime configuration to an application bean for validation */ diff --git a/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/RuntimeXmlConfigService.java b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/RuntimeXmlConfigService.java new file mode 100644 index 0000000000000..006b3030c7a5c --- /dev/null +++ b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/RuntimeXmlConfigService.java @@ -0,0 +1,97 @@ +package io.quarkus.extest.runtime; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.jboss.logging.Logger; + +import io.quarkus.extest.runtime.config.XmlConfig; + +/** + * Hypothetical legacy service that uses the XmlConfig configuration to run a socket based service + */ +public class RuntimeXmlConfigService { + private static final Logger log = Logger.getLogger("RuntimeXmlConfigService"); + private XmlConfig config; + private ServerSocket serverSocket; + private volatile AtomicBoolean running = new AtomicBoolean(false); + private Thread clientHandler; + + public RuntimeXmlConfigService(XmlConfig config) { + this.config = config; + log.infof("ctor, config: %s", config); + } + + public void startService() throws IOException { + log.infof("startService, config: %s", config); + try { + log.infof("Class.forName(XmlRootElement): %s", Class.forName("javax.xml.bind.annotation.XmlRootElement")); + } catch (Exception e) { + log.info("Failed to load XmlRootElement", e); + } + InetAddress address = InetAddress.getByName(config.getAddress()); + serverSocket = new ServerSocket(config.getPort(), -1, address); + clientHandler = new Thread(this::run, "RuntimeXmlConfigServiceThread"); + clientHandler.setDaemon(false); + running.set(true); + clientHandler.start(); + log.info("startService, " + clientHandler); + } + + public void stopService() { + log.info("stopService, stopping..."); + running.set(false); + try { + serverSocket.close(); + } catch (IOException e) { + log.warn("ServerSocket.close", e); + } + clientHandler.interrupt(); + log.info("stopService, complete."); + } + + private void run() { + try { + log.info("Starting accept loop"); + while (running.get()) { + Socket client = serverSocket.accept(); + log.infof("Accepted client: %s", client); + CommandHandler handler = new CommandHandler(client.getInputStream(), client.getOutputStream()); + handler.run(); + client.close(); + log.infof("Closed client: %s", client); + } + } catch (Exception e) { + if (!serverSocket.isClosed()) { + log.warn("Error handling client request", e); + } + } + log.info("Exiting accept loop"); + } + + private static class CommandHandler { + private InputStream is; + private OutputStream os; + + CommandHandler(InputStream inputStream, OutputStream outputStream) { + this.is = inputStream; + this.os = outputStream; + } + + void run() throws IOException { + BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8)); + String command = reader.readLine(); + log.infof("Received command: %s", command); + String reply = command + "-ack"; + os.write(reply.getBytes("UTF-8")); + } + } +} diff --git a/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/TestConfigValidator.java b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/TestConfigValidator.java deleted file mode 100644 index 6c6dc2b0d21f3..0000000000000 --- a/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/TestConfigValidator.java +++ /dev/null @@ -1,4 +0,0 @@ -package io.quarkus.extest.runtime; - -public interface TestConfigValidator { -} diff --git a/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/TestTemplate.java b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/TestTemplate.java index 6b2da7a1e2f39..46859898b44d7 100644 --- a/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/TestTemplate.java +++ b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/TestTemplate.java @@ -1,8 +1,18 @@ package io.quarkus.extest.runtime; +import java.io.IOException; +import java.security.interfaces.DSAPublicKey; +import java.util.Set; + import org.jboss.logging.Logger; import io.quarkus.arc.runtime.BeanContainer; +import io.quarkus.extest.runtime.beans.PublicKeyProducer; +import io.quarkus.extest.runtime.config.TestBuildAndRunTimeConfig; +import io.quarkus.extest.runtime.config.TestRunTimeConfig; +import io.quarkus.extest.runtime.config.XmlConfig; +import io.quarkus.runtime.RuntimeValue; +import io.quarkus.runtime.ShutdownContext; import io.quarkus.runtime.annotations.Template; /** @@ -15,12 +25,12 @@ public class TestTemplate { /** * Instantiate the given class in the given BeanContainer and passes the TestBuildAndRunTimeConfig and TestRunTimeConfig to * it - * - * @see IConfigConsumer#loadConfig(TestBuildAndRunTimeConfig, TestRunTimeConfig) + * * @param beanContainer - CDI container * @param beanClass - IConfigConsumer * @param buildTimeConfig - the extension TestBuildAndRunTimeConfig * @param runTimeConfig - the extension TestRunTimeConfig + * @see IConfigConsumer#loadConfig(TestBuildAndRunTimeConfig, TestRunTimeConfig) */ public void configureBeans(BeanContainer beanContainer, Class beanClass, TestBuildAndRunTimeConfig buildTimeConfig, @@ -30,4 +40,51 @@ public void configureBeans(BeanContainer beanContainer, Class b instance.loadConfig(buildTimeConfig, runTimeConfig); log.infof("configureBeans, instance=%s\n", instance); } + + /** + * Create a non-CDI based RuntimeXmlConfigService from the XmlConfig + * + * @param config - parse XML configuration + * @return RuntimeValue + */ + public RuntimeValue initRuntimeService(XmlConfig config) { + RuntimeXmlConfigService service = new RuntimeXmlConfigService(config); + return new RuntimeValue<>(service); + } + + /** + * Invoke the RuntimeXmlConfigService#startService method and register a stopService call with the shutdown context. + * + * @param shutdownContext - context for adding shutdown hooks + * @param runtimeValue - service value + * @throws IOException - on startup failure + */ + public void startRuntimeService(ShutdownContext shutdownContext, RuntimeValue runtimeValue) + throws IOException { + RuntimeXmlConfigService service = runtimeValue.getValue(); + service.startService(); + shutdownContext.addShutdownTask(service::stopService); + } + + /** + * Passes the public ket to the PublicKeyProducer for injection into CDI beans at runtime + * + * @param publicKey - public key + * @param beanContainer - CDI bean container + */ + public void loadDSAPublicKeyProducer(DSAPublicKey publicKey, BeanContainer beanContainer) { + PublicKeyProducer keyProducer = beanContainer.instance(PublicKeyProducer.class); + keyProducer.setPublicKey(publicKey); + } + + /** + * Access the primitive class types at runtime to validate the build step generated deploy method + * + * @param typesSet - primitive classes set + */ + public void validateTypes(Set> typesSet) { + for (Class type : typesSet) { + log.debugf("Checking type: %s", type.getName()); + } + } } diff --git a/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/beans/CommandServlet.java b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/beans/CommandServlet.java new file mode 100644 index 0000000000000..7e396d224182e --- /dev/null +++ b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/beans/CommandServlet.java @@ -0,0 +1,33 @@ +package io.quarkus.extest.runtime.beans; + +import java.io.IOException; +import java.security.interfaces.DSAPublicKey; + +import javax.inject.Inject; +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * Simple command dispatch servlet used for testing the state of the native image + */ +@WebServlet +public class CommandServlet extends HttpServlet { + @Inject + DSAPublicKey publicKey; + + @Override + public void init() throws ServletException { + super.init(); + log("init, publicKey=" + publicKey); + } + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + String pathInfo = req.getPathInfo(); + log("doGet, " + pathInfo); + resp.getWriter().write(pathInfo + "-ack"); + } +} diff --git a/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/beans/PublicKeyProducer.java b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/beans/PublicKeyProducer.java new file mode 100644 index 0000000000000..5205b926dca7b --- /dev/null +++ b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/beans/PublicKeyProducer.java @@ -0,0 +1,30 @@ +package io.quarkus.extest.runtime.beans; + +import java.security.interfaces.DSAPublicKey; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Produces; + +import org.jboss.logging.Logger; + +/** + * Producer of DSAPublicKey + */ +@ApplicationScoped +public class PublicKeyProducer { + private static final Logger log = Logger.getLogger("PublicKeyProducer"); + private DSAPublicKey publicKey; + + public PublicKeyProducer() { + } + + @Produces + public DSAPublicKey getPublicKey() { + return publicKey; + } + + public void setPublicKey(DSAPublicKey publicKey) { + log.infof("setPublicKey, key=%s", publicKey); + this.publicKey = publicKey; + } +} diff --git a/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/AllValuesConfig.java b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/AllValuesConfig.java similarity index 92% rename from core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/AllValuesConfig.java rename to core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/AllValuesConfig.java index d9bf4e56406dc..3a281e12aad5b 100644 --- a/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/AllValuesConfig.java +++ b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/AllValuesConfig.java @@ -1,4 +1,4 @@ -package io.quarkus.extest.runtime; +package io.quarkus.extest.runtime.config; import java.util.List; import java.util.Map; @@ -56,6 +56,9 @@ public class AllValuesConfig { /** A List of long values */ @ConfigItem public List longList; + /** A configuration item that has a default value that is an expression */ + @ConfigItem(defaultValue = "${java.vm.version}") + public String expandedDefault; @Override public String toString() { diff --git a/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/NestedConfig.java b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/NestedConfig.java similarity index 88% rename from core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/NestedConfig.java rename to core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/NestedConfig.java index de5c2a5321de9..190737336da71 100644 --- a/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/NestedConfig.java +++ b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/NestedConfig.java @@ -1,4 +1,4 @@ -package io.quarkus.extest.runtime; +package io.quarkus.extest.runtime.config; import io.quarkus.runtime.annotations.ConfigGroup; import io.quarkus.runtime.annotations.ConfigItem; diff --git a/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/ObjectOfValue.java b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/ObjectOfValue.java similarity index 96% rename from core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/ObjectOfValue.java rename to core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/ObjectOfValue.java index aab3fbca4ce2f..391d4624edc09 100644 --- a/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/ObjectOfValue.java +++ b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/ObjectOfValue.java @@ -1,4 +1,4 @@ -package io.quarkus.extest.runtime; +package io.quarkus.extest.runtime.config; import java.util.Objects; diff --git a/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/ObjectValueOf.java b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/ObjectValueOf.java similarity index 96% rename from core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/ObjectValueOf.java rename to core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/ObjectValueOf.java index 6cba23e432777..86201e139c67a 100644 --- a/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/ObjectValueOf.java +++ b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/ObjectValueOf.java @@ -1,4 +1,4 @@ -package io.quarkus.extest.runtime; +package io.quarkus.extest.runtime.config; /** * A configuration type that has a static {@linkplain ObjectValueOf#valueOf(String)} conversion method diff --git a/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/StringBasedValue.java b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/StringBasedValue.java similarity index 86% rename from core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/StringBasedValue.java rename to core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/StringBasedValue.java index 38b27fd52f866..8bbcf45db4c75 100644 --- a/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/StringBasedValue.java +++ b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/StringBasedValue.java @@ -1,4 +1,4 @@ -package io.quarkus.extest.runtime; +package io.quarkus.extest.runtime.config; public class StringBasedValue { private String value; diff --git a/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/TestBuildAndRunTimeConfig.java b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/TestBuildAndRunTimeConfig.java similarity index 96% rename from core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/TestBuildAndRunTimeConfig.java rename to core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/TestBuildAndRunTimeConfig.java index cc42093ecd95c..9a0c18034d783 100644 --- a/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/TestBuildAndRunTimeConfig.java +++ b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/TestBuildAndRunTimeConfig.java @@ -1,4 +1,4 @@ -package io.quarkus.extest.runtime; +package io.quarkus.extest.runtime.config; import io.quarkus.runtime.annotations.ConfigItem; import io.quarkus.runtime.annotations.ConfigPhase; diff --git a/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/TestBuildTimeConfig.java b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/TestBuildTimeConfig.java similarity index 85% rename from core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/TestBuildTimeConfig.java rename to core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/TestBuildTimeConfig.java index 1cb9bcaeb3482..6694901c7afc1 100644 --- a/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/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; +package io.quarkus.extest.runtime.config; +import io.quarkus.extest.runtime.config.AllValuesConfig; +import io.quarkus.extest.runtime.config.StringBasedValue; import io.quarkus.runtime.annotations.ConfigItem; import io.quarkus.runtime.annotations.ConfigPhase; import io.quarkus.runtime.annotations.ConfigRoot; diff --git a/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/TestConfigRoot.java b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/TestConfigRoot.java new file mode 100644 index 0000000000000..f184f34abe72c --- /dev/null +++ b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/TestConfigRoot.java @@ -0,0 +1,21 @@ +package io.quarkus.extest.runtime.config; + +import org.eclipse.microprofile.config.inject.ConfigProperty; + +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; + +@ConfigRoot(name = "root", phase = ConfigPhase.BUILD_AND_RUN_TIME_FIXED) +public class TestConfigRoot { + /** + * resource location of the DSAPublicKey encoded bytes + */ + @ConfigProperty + public String dsaKeyLocation; + + /** + * Should the TestProcessor#checkConfig method validate the buildTimeConfig + */ + @ConfigProperty(defaultValue = "false") + public boolean validateBuildConfig; +} diff --git a/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/TestRunTimeConfig.java b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/TestRunTimeConfig.java similarity index 92% rename from core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/TestRunTimeConfig.java rename to core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/TestRunTimeConfig.java index 4048f232ea2d5..ebeac383e0a14 100644 --- a/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/TestRunTimeConfig.java +++ b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/TestRunTimeConfig.java @@ -1,8 +1,9 @@ -package io.quarkus.extest.runtime; +package io.quarkus.extest.runtime.config; import java.util.List; import java.util.Map; +import io.quarkus.extest.runtime.config.AllValuesConfig; import io.quarkus.runtime.annotations.ConfigItem; import io.quarkus.runtime.annotations.ConfigPhase; import io.quarkus.runtime.annotations.ConfigRoot; diff --git a/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/XConfig.java b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/XConfig.java new file mode 100644 index 0000000000000..49df566c6f2d1 --- /dev/null +++ b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/XConfig.java @@ -0,0 +1,45 @@ +package io.quarkus.extest.runtime.config; + +import java.util.ArrayList; + +/** + * An alternate to XlmConfig that has no JAXB annotations + */ +public class XConfig { + private ArrayList dataList; + private String address; + private int port; + + public ArrayList getDataList() { + return dataList; + } + + public void setDataList(ArrayList dataList) { + this.dataList = dataList; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + @Override + public String toString() { + return "XConfig{" + + "dataList=" + dataList + + ", address='" + address + '\'' + + ", port=" + port + + '}'; + } +} diff --git a/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/XData.java b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/XData.java new file mode 100644 index 0000000000000..3a97c1589434b --- /dev/null +++ b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/XData.java @@ -0,0 +1,24 @@ +package io.quarkus.extest.runtime.config; + +import java.util.Date; + +/** + * An alternate to XlmData that has no JAXB annotations + */ +public class XData { + private String name; + private String model; + private Date date; + + public String getName() { + return name; + } + + public String getModel() { + return model; + } + + public Date getDate() { + return date; + } +} diff --git a/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/XmlConfig.java b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/XmlConfig.java new file mode 100644 index 0000000000000..d79a892621080 --- /dev/null +++ b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/XmlConfig.java @@ -0,0 +1,51 @@ +package io.quarkus.extest.runtime.config; + +import java.util.ArrayList; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +/** + * A hypothetical configuration that needs xml parsing at build time + */ +@XmlRootElement(namespace = "https://quarkus.io") +public class XmlConfig { + private ArrayList dataList; + private String address; + private int port; + + // XmlElement sets the name of the entities + @XmlElement(name = "data") + public ArrayList getDataList() { + return dataList; + } + + public void setDataList(ArrayList dataList) { + this.dataList = dataList; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + @Override + public String toString() { + return "XmlConfig{" + + "dataList=" + dataList + + ", address='" + address + '\'' + + ", port=" + port + + '}'; + } +} diff --git a/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/XmlData.java b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/XmlData.java new file mode 100644 index 0000000000000..b5b432b98c139 --- /dev/null +++ b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/config/XmlData.java @@ -0,0 +1,45 @@ +package io.quarkus.extest.runtime.config; + +import java.util.Date; + +import javax.xml.bind.annotation.XmlRootElement; + +@XmlRootElement(name = "data") +public class XmlData { + private String name; + private String model; + private Date date; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getModel() { + return model; + } + + public void setModel(String model) { + this.model = model; + } + + public Date getDate() { + return date; + } + + public void setDate(Date date) { + this.date = date; + } + + @Override + public String toString() { + return "XmlData{" + + "name='" + name + '\'' + + ", model='" + model + '\'' + + ", date='" + date + '\'' + + '}'; + } +} diff --git a/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/graal/Target_XmlConfig.java b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/graal/Target_XmlConfig.java new file mode 100644 index 0000000000000..c967bd0daaaac --- /dev/null +++ b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/graal/Target_XmlConfig.java @@ -0,0 +1,45 @@ +package io.quarkus.extest.runtime.graal; + +import java.util.ArrayList; + +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; + +import io.quarkus.extest.runtime.config.XData; +import io.quarkus.extest.runtime.config.XmlConfig; + +@TargetClass(XmlConfig.class) +@Substitute +final public class Target_XmlConfig { + @Substitute + private String address; + @Substitute + private int port; + @Substitute + private ArrayList dataList; + + @Substitute + public String getAddress() { + return address; + } + + @Substitute + public int getPort() { + return port; + } + + @Substitute + public ArrayList getDataList() { + return dataList; + } + + @Substitute + @Override + public String toString() { + return "Target_XmlConfig{" + + "address='" + address + '\'' + + ", port=" + port + + ", dataList=" + dataList + + '}'; + } +} diff --git a/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/graal/Target_XmlData.java b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/graal/Target_XmlData.java new file mode 100644 index 0000000000000..9a19596a19e5e --- /dev/null +++ b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/graal/Target_XmlData.java @@ -0,0 +1,45 @@ +package io.quarkus.extest.runtime.graal; + +import java.util.Date; + +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; + +import io.quarkus.extest.runtime.config.XmlData; + +@TargetClass(XmlData.class) +@Substitute +final public class Target_XmlData { + @Substitute + private String name; + @Substitute + private String model; + @Substitute + private Date date; + + @Substitute + public String getName() { + return name; + } + + @Substitute + public String getModel() { + return model; + } + + @Substitute + public Date getDate() { + return date; + } + + @Substitute + @Override + public String toString() { + return "Target_XmlData{" + + "name='" + name + '\'' + + ", model='" + model + '\'' + + ", date='" + date + '\'' + + '}'; + } + +} diff --git a/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/subst/DSAPublicKeyObjectSubstitution.java b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/subst/DSAPublicKeyObjectSubstitution.java new file mode 100644 index 0000000000000..ca346c611deb7 --- /dev/null +++ b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/subst/DSAPublicKeyObjectSubstitution.java @@ -0,0 +1,43 @@ +package io.quarkus.extest.runtime.subst; + +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.interfaces.DSAPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; +import java.util.logging.Logger; + +import io.quarkus.runtime.ObjectSubstitution; + +/** + * Example ObjectSubstitution for DSA public key substitution + * The DSA key provider is the SUN provider enabled by default in Graal + */ +public class DSAPublicKeyObjectSubstitution implements ObjectSubstitution { + private static final Logger log = Logger.getLogger("DSAPublicKeyObjectSubstitution"); + + @Override + public KeyProxy serialize(DSAPublicKey obj) { + log.info("DSAPublicKeyObjectSubstitution.serialize"); + byte[] encoded = obj.getEncoded(); + KeyProxy proxy = new KeyProxy(); + proxy.setContent(encoded); + return proxy; + } + + @Override + public DSAPublicKey deserialize(KeyProxy obj) { + log.info("DSAPublicKeyObjectSubstitution.deserialize"); + byte[] encoded = obj.getContent(); + X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(encoded); + DSAPublicKey dsaPublicKey = null; + try { + KeyFactory kf = KeyFactory.getInstance("DSA"); + dsaPublicKey = (DSAPublicKey) kf.generatePublic(publicKeySpec); + + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + e.printStackTrace(); + } + return dsaPublicKey; + } +} diff --git a/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/subst/KeyProxy.java b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/subst/KeyProxy.java new file mode 100644 index 0000000000000..8626e3b93c91e --- /dev/null +++ b/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/subst/KeyProxy.java @@ -0,0 +1,17 @@ +package io.quarkus.extest.runtime.subst; + +/** + * A simple proxy for the public key encoded bytes used to serialize/deserialize a public key + */ +public class KeyProxy { + private byte[] content; + + public byte[] getContent() { + return content; + } + + public void setContent(byte[] content) { + this.content = content; + } + +} diff --git a/devtools/aesh/pom.xml b/devtools/aesh/pom.xml index 8ff800cf3b8ab..9eec5c1d00e0d 100644 --- a/devtools/aesh/pom.xml +++ b/devtools/aesh/pom.xml @@ -49,6 +49,7 @@ junit junit + test 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 0f4edc2c898d8..67735d3839270 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 @@ -13,6 +13,7 @@ import org.aesh.command.option.Option; import org.aesh.io.Resource; +import io.quarkus.cli.commands.writer.FileProjectWriter; import io.quarkus.dependencies.Extension; import io.quarkus.maven.utilities.MojoUtils; @@ -42,7 +43,9 @@ public CommandResult execute(CommandInvocation commandInvocation) throws Command return CommandResult.SUCCESS; } else if (pom.isLeaf()) { try { - AddExtensions project = new AddExtensions(new File(pom.getAbsolutePath())); + File pomFile = new File(pom.getAbsolutePath()); + AddExtensions project = new AddExtensions(new FileProjectWriter(pomFile.getParentFile()), + pomFile.getName()); project.addExtensions(Collections.singleton(extension)); } catch (IOException e) { e.printStackTrace(); diff --git a/devtools/aesh/src/main/java/io/quarkus/cli/commands/CreateProjectCommand.java b/devtools/aesh/src/main/java/io/quarkus/cli/commands/CreateProjectCommand.java index 7fd58da83d179..7b6adefdef248 100644 --- a/devtools/aesh/src/main/java/io/quarkus/cli/commands/CreateProjectCommand.java +++ b/devtools/aesh/src/main/java/io/quarkus/cli/commands/CreateProjectCommand.java @@ -12,6 +12,8 @@ import org.aesh.command.option.Option; import org.aesh.io.Resource; +import io.quarkus.cli.commands.writer.FileProjectWriter; + /** * @author Ståle Pedersen */ @@ -21,10 +23,10 @@ public class CreateProjectCommand implements Command { @Option(shortName = 'h', hasValue = false) private boolean help; - @Option(shortName = 'g', defaultValue = "com.acme") + @Option(shortName = 'g', defaultValue = "org.acme") private String groupid; - @Option(shortName = 'a', defaultValue = "quarkuss") + @Option(shortName = 'a', defaultValue = "quarkus") private String artifactid; @Option(shortName = 'v', defaultValue = "1.0.0-SNAPSHOT") @@ -41,7 +43,7 @@ public CommandResult execute(CommandInvocation commandInvocation) { if (path != null) { try { - boolean status = new CreateProject(new File(path.getAbsolutePath())) + boolean status = new CreateProject(new FileProjectWriter(new File(path.getAbsolutePath()))) .groupId(groupid) .artifactId(artifactid) .version(this.version) diff --git a/devtools/common-core/src/main/java/io/quarkus/dependencies/Extension.java b/devtools/common-core/src/main/java/io/quarkus/dependencies/Extension.java index 9b319c3929b06..10f5932935a0e 100644 --- a/devtools/common-core/src/main/java/io/quarkus/dependencies/Extension.java +++ b/devtools/common-core/src/main/java/io/quarkus/dependencies/Extension.java @@ -18,6 +18,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -41,6 +42,10 @@ public class Extension { private String description; private boolean internal = false; private String[] labels; + private String guide; + + private String simplifiedArtifactId; + private static final Pattern QUARKUS_PREFIX = Pattern.compile("^quarkus-"); public Extension() { // Use by mapper. @@ -48,7 +53,7 @@ public Extension() { public Extension(String groupId, String artifactId, String version) { this.groupId = groupId; - this.artifactId = artifactId; + this.setArtifactId(artifactId); this.version = version; } @@ -58,6 +63,7 @@ public String getArtifactId() { public Extension setArtifactId(String artifactId) { this.artifactId = artifactId; + this.simplifiedArtifactId = QUARKUS_PREFIX.matcher(artifactId).replaceFirst(""); return this; } @@ -175,6 +181,10 @@ public String gav() { return managementKey() + ":" + version; } + public String getSimplifiedArtifactId() { + return simplifiedArtifactId; + } + @Override public String toString() { return gav(); @@ -232,4 +242,8 @@ public boolean equals(Object obj) { return true; } + + public String getGuide() { + return guide; + } } diff --git a/devtools/common/pom.xml b/devtools/common/pom.xml index f39f337dbe007..a97f509042358 100644 --- a/devtools/common/pom.xml +++ b/devtools/common/pom.xml @@ -53,6 +53,12 @@ io.quarkus quarkus-devtools-common-core
+ org.apache.maven maven-plugin-api @@ -64,19 +70,27 @@ org.junit.jupiter junit-jupiter-api + test org.junit.jupiter junit-jupiter-params + test org.junit.jupiter junit-jupiter-engine + test + + + io.undertow + undertow-websockets-jsr org.assertj assertj-core + test diff --git a/devtools/common/src/main/filtered/extensions.json b/devtools/common/src/main/filtered/extensions.json index 6f49a33a10885..33d795b159f49 100644 --- a/devtools/common/src/main/filtered/extensions.json +++ b/devtools/common/src/main/filtered/extensions.json @@ -17,7 +17,8 @@ "di" ], "groupId": "io.quarkus", - "artifactId": "quarkus-arc" + "artifactId": "quarkus-arc", + "guide": "https://quarkus.io/guides/cdi-reference" }, { "name": "Camel Core", @@ -55,6 +56,17 @@ "groupId": "io.quarkus", "artifactId": "quarkus-camel-salesforce" }, + { + "name": "Flyway", + "labels": [ + "flyway", + "database", + "data" + ], + "groupId": "io.quarkus", + "artifactId": "quarkus-flyway", + "guide": "https://quarkus.io/guides/flyway-guide" + }, { "name": "Hibernate ORM", "labels": [ @@ -63,7 +75,8 @@ "hibernate" ], "groupId": "io.quarkus", - "artifactId": "quarkus-hibernate-orm" + "artifactId": "quarkus-hibernate-orm", + "guide": "https://quarkus.io/guides/hibernate-orm-guide" }, { "name": "Hibernate ORM with Panache", @@ -74,7 +87,8 @@ "jpa" ], "groupId": "io.quarkus", - "artifactId": "quarkus-hibernate-orm-panache" + "artifactId": "quarkus-hibernate-orm-panache", + "guide": "https://quarkus.io/guides/hibernate-orm-panache-guide" }, { "name": "Hibernate Validator", @@ -84,7 +98,8 @@ "validation" ], "groupId": "io.quarkus", - "artifactId": "quarkus-hibernate-validator" + "artifactId": "quarkus-hibernate-validator", + "guide": "https://quarkus.io/guides/validation-guide" }, { "name": "Infinispan Client", @@ -94,7 +109,8 @@ "infinispan" ], "groupId": "io.quarkus", - "artifactId": "quarkus-infinispan-client" + "artifactId": "quarkus-infinispan-client", + "guide": "https://quarkus.io/guides/infinispan-client-guide" }, { "name": "JDBC Driver - H2", @@ -126,13 +142,55 @@ "groupId": "io.quarkus", "artifactId": "quarkus-jdbc-postgresql" }, + { + "name": "JSON-B", + "labels": [ + "jsonb", + "json-b", + "json" + ], + "groupId": "io.quarkus", + "artifactId": "quarkus-jsonb", + "guide": "https://quarkus.io/guides/rest-json-guide" + }, + { + "name": "JSON-P", + "labels": [ + "jsonp", + "json-p", + "json" + ], + "groupId": "io.quarkus", + "artifactId": "quarkus-jsonp" + }, + { + "name": "Keycloak", + "labels": [ + "keycloak", + "oauth2", + "openid-connect" + ], + "groupId": "io.quarkus", + "artifactId": "quarkus-keycloak", + "guide": "https://quarkus.io/guides/keycloak-guide" + }, { "name": "Kotlin", "labels": [ "kotlin" ], "groupId": "io.quarkus", - "artifactId": "quarkus-kotlin" + "artifactId": "quarkus-kotlin", + "guide": "https://quarkus.io/guides/kotlin" + }, + { + "name": "Kubernetes", + "labels": [ + "kubernetes" + ], + "groupId": "io.quarkus", + "artifactId": "quarkus-kubernetes", + "guide": "https://quarkus.io/guides/kubernetes-guide" }, { "name": "AWS Lambda", @@ -155,7 +213,8 @@ "txs" ], "groupId": "io.quarkus", - "artifactId": "quarkus-narayana-jta" + "artifactId": "quarkus-narayana-jta", + "guide": "https://quarkus.io/guides/transaction-guide" }, { "name": "RESTEasy", @@ -166,7 +225,8 @@ "rest" ], "groupId": "io.quarkus", - "artifactId": "quarkus-resteasy" + "artifactId": "quarkus-resteasy", + "guide": "https://quarkus.io/guides/rest-json-guide" }, { "name": "RESTEasy - JSON-B", @@ -180,7 +240,8 @@ "jsonb" ], "groupId": "io.quarkus", - "artifactId": "quarkus-resteasy-jsonb" + "artifactId": "quarkus-resteasy-jsonb", + "guide": "https://quarkus.io/guides/rest-json-guide" }, { "name": "Scheduler", @@ -190,7 +251,8 @@ "periodic-tasks" ], "groupId": "io.quarkus", - "artifactId": "quarkus-scheduler" + "artifactId": "quarkus-scheduler", + "guide": "https://quarkus.io/guides/scheduled-guide" }, { "name": "Security", @@ -198,7 +260,8 @@ "security" ], "groupId": "io.quarkus", - "artifactId": "quarkus-elytron-security" + "artifactId": "quarkus-elytron-security", + "guide": "https://quarkus.io/guides/security-guide" }, { "name": "SmallRye Fault Tolerance", @@ -222,7 +285,8 @@ "microprofile-health-check" ], "groupId": "io.quarkus", - "artifactId": "quarkus-smallrye-health" + "artifactId": "quarkus-smallrye-health", + "guide": "https://quarkus.io/guides/health-guide" }, { "name": "SmallRye JWT", @@ -233,7 +297,8 @@ "rbac" ], "groupId": "io.quarkus", - "artifactId": "quarkus-smallrye-jwt" + "artifactId": "quarkus-smallrye-jwt", + "guide": "https://quarkus.io/guides/jwt-guide" }, { "name": "SmallRye Metrics", @@ -245,7 +310,8 @@ "monitoring" ], "groupId": "io.quarkus", - "artifactId": "quarkus-smallrye-metrics" + "artifactId": "quarkus-smallrye-metrics", + "guide": "https://quarkus.io/guides/metrics-guide" }, { "name": "SmallRye OpenAPI", @@ -255,7 +321,8 @@ "open-api" ], "groupId": "io.quarkus", - "artifactId": "quarkus-smallrye-openapi" + "artifactId": "quarkus-smallrye-openapi", + "guide": "https://quarkus.io/guides/openapi-swaggerui-guide" }, { "name": "SmallRye OpenTracing", @@ -267,7 +334,8 @@ "jaeger" ], "groupId": "io.quarkus", - "artifactId": "quarkus-smallrye-opentracing" + "artifactId": "quarkus-smallrye-opentracing", + "guide": "https://quarkus.io/guides/opentracing-guide" }, { "name": "SmallRye Reactive Streams Operators", @@ -303,7 +371,8 @@ "reactive" ], "groupId": "io.quarkus", - "artifactId": "quarkus-smallrye-reactive-messaging" + "artifactId": "quarkus-smallrye-reactive-messaging", + "guide": "https://quarkus.io/guides/async-message-passing" }, { "name": "SmallRye Reactive Messaging - Kafka Connector", @@ -312,7 +381,8 @@ "reactive-kafka" ], "groupId": "io.quarkus", - "artifactId": "quarkus-smallrye-reactive-messaging-kafka" + "artifactId": "quarkus-smallrye-reactive-messaging-kafka", + "guide": "https://quarkus.io/guides/kafka-guide" }, { "name": "SmallRye REST Client", @@ -323,7 +393,8 @@ "microprofile-rest-client" ], "groupId": "io.quarkus", - "artifactId": "quarkus-smallrye-rest-client" + "artifactId": "quarkus-smallrye-rest-client", + "guide": "https://quarkus.io/guides/rest-client-guide" }, { "name": "Spring DI compatibility layer", @@ -331,7 +402,17 @@ "spring-di" ], "groupId": "io.quarkus", - "artifactId": "quarkus-spring-di" + "artifactId": "quarkus-spring-di", + "guide": "https://quarkus.io/guides/spring-di-guide" + }, + { + "name": "Swagger UI", + "labels": [ + "swagger-ui" + ], + "groupId": "io.quarkus", + "artifactId": "quarkus-swagger-ui", + "guide": "https://quarkus.io/guides/openapi-swaggerui-guide" }, { "name": "Undertow", @@ -353,7 +434,8 @@ "web-sockets" ], "groupId": "io.quarkus", - "artifactId": "quarkus-undertow-websockets" + "artifactId": "quarkus-undertow-websockets", + "guide": "https://quarkus.io/guides/websocket-guide" }, { "name": "Eclipse Vert.x", @@ -364,6 +446,21 @@ "reactive" ], "groupId": "io.quarkus", - "artifactId": "quarkus-vertx" + "artifactId": "quarkus-vertx", + "guide": "https://quarkus.io/guides/using-vertx" + }, + { + "name": "Reactive Postgres Client", + "labels": [ + "eclipse-vert.x", + "vertx", + "vert.x", + "reactive", + "database", + "data", + "postgresql" + ], + "groupId": "io.quarkus", + "artifactId": "quarkus-reactive-pg-client" } ] diff --git a/devtools/common/src/main/filtered/quarkus.properties b/devtools/common/src/main/filtered/quarkus.properties index 2c5730ad1c86e..c3dc008a42069 100644 --- a/devtools/common/src/main/filtered/quarkus.properties +++ b/devtools/common/src/main/filtered/quarkus.properties @@ -29,3 +29,6 @@ plugin-artifactId=quarkus-maven-plugin plugin-version=${project.version} supported-maven-versions=${supported-maven-versions} +# the proposed version must be in the range of the supported versions +proposed-maven-version=${proposed-maven-version} +maven-wrapper-version=${maven-wrapper.version} diff --git a/devtools/common/src/main/java/io/quarkus/BasicRest.java b/devtools/common/src/main/java/io/quarkus/BasicRest.java deleted file mode 100644 index 7a73a0bcb5e31..0000000000000 --- a/devtools/common/src/main/java/io/quarkus/BasicRest.java +++ /dev/null @@ -1,199 +0,0 @@ -package io.quarkus; - -import static java.lang.String.format; - -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.File; -import java.io.IOException; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.StandardOpenOption; -import java.util.Arrays; -import java.util.Map; -import java.util.Map.Entry; -import java.util.stream.Collectors; - -import org.apache.maven.model.Model; - -import io.quarkus.maven.utilities.MojoUtils; - -public class BasicRest extends QuarkusTemplate { - private Map context; - private String className; - private String path = "/hello"; - private File projectRoot; - private File srcMain; - private File testMain; - - @Override - public String getName() { - return "basic-rest"; - } - - @Override - public void generate(final File projectRoot, Map parameters) throws IOException { - this.projectRoot = projectRoot; - this.context = parameters; - - initProject(); - setupContext(); - - if (className != null) { - createClasses(); - } - createIndexPage(); - createDockerFile(); - createDockerIgnore(); - createApplicationConfig(); - } - - private void setupContext() { - MojoUtils.getAllProperties().forEach((k, v) -> context.put(k.replace("-", "_"), v)); - - if (className != null) { - String packageName = (String) context.get(PACKAGE_NAME); - - if (className.endsWith(MojoUtils.JAVA_EXTENSION)) { - className = className.substring(0, className.length() - MojoUtils.JAVA_EXTENSION.length()); - } else if (className.endsWith(MojoUtils.KOTLIN_EXTENSION)) { - className = className.substring(0, className.length() - MojoUtils.KOTLIN_EXTENSION.length()); - } - - if (className.contains(".")) { - int idx = className.lastIndexOf('.'); - packageName = className.substring(0, idx); - className = className.substring(idx + 1); - } - - if (packageName != null) { - File packageDir = new File(srcMain, packageName.replace('.', '/')); - File testPackageDir = new File(testMain, packageName.replace('.', '/')); - srcMain = mkdirs(packageDir); - testMain = mkdirs(testPackageDir); - } - - context.put(CLASS_NAME, className); - context.put(RESOURCE_PATH, path); - - if (packageName != null) { - context.put(PACKAGE_NAME, packageName); - } - } - } - - private void createClasses() throws IOException { - File classFile = new File(srcMain, className + getProperSourceExtension()); - File testClassFile = new File(testMain, className + "Test" + getProperSourceExtension()); - File itTestClassFile = new File(testMain, "Native" + className + "IT" + getProperSourceExtension()); - generate( - getSourceType() == SourceType.JAVA - ? "templates/resource-template.ftl" - : "templates/resource-template-kotlin.ftl", - context, classFile, "resource code"); - generate( - getSourceType() == SourceType.JAVA - ? "templates/test-resource-template.ftl" - : "templates/test-resource-template-kotlin.ftl", - context, testClassFile, "test code"); - generate( - getSourceType() == SourceType.JAVA - ? "templates/native-test-resource-template.ftl" - : "templates/native-test-resource-template-kotlin.ftl", - context, itTestClassFile, "IT code"); - } - - @SuppressWarnings("unchecked") - private T get(final String key, final String defaultValue) { - return (T) context.getOrDefault(key, defaultValue); - } - - private boolean initProject() throws IOException { - final File pomFile = new File(projectRoot, "pom.xml"); - boolean newProject = !pomFile.exists(); - if (newProject) { - final String templateName = getSourceType() == SourceType.JAVA ? "templates/pom-template.ftl" - : "templates/pom-template-kotlin.ftl"; - generate(templateName, context, pomFile, "Unable to generate pom.xml"); - } else { - final Model model = MojoUtils.readPom(pomFile); - context.put(PROJECT_GROUP_ID, model.getGroupId()); - context.put(PROJECT_ARTIFACT_ID, model.getArtifactId()); - } - - // If className is null we disable the generation of the Jax-RS resource. - className = get("className", null); - path = get(RESOURCE_PATH, path); - - srcMain = mkdirs(new File(projectRoot, getSourceType() == SourceType.JAVA ? "src/main/java" : "src/main/kotlin")); - testMain = mkdirs(new File(projectRoot, getSourceType() == SourceType.JAVA ? "src/test/java" : "src/test/kotlin")); - - return newProject; - } - - private void generate(final String templateName, final Map context, final File outputFile, - final String resourceType) - throws IOException { - if (!outputFile.exists()) { - String path = templateName.startsWith("/") ? templateName : "/" + templateName; - try (BufferedWriter out = Files.newBufferedWriter(outputFile.toPath()); - final BufferedReader stream = new BufferedReader( - new InputStreamReader(getClass().getResourceAsStream(path), StandardCharsets.UTF_8))) { - String template = stream.lines().collect(Collectors.joining("\n")); - for (Entry e : context.entrySet()) { - if (e.getValue() != null) { // Exclude null values (classname and path can be null) - template = template.replace(format("${%s}", e.getKey()), e.getValue().toString()); - } - } - out.write(template); - } - } - } - - private void createIndexPage() throws IOException { - // Generate index page - File resources = new File(projectRoot, "src/main/resources/META-INF/resources"); - File index = new File(mkdirs(resources), "index.html"); - if (!index.exists()) { - generate("templates/index.ftl", context, index, "welcome page"); - } - - } - - private void createDockerFile() throws IOException { - File dockerRoot = new File(projectRoot, "src/main/docker"); - File docker = new File(mkdirs(dockerRoot), "Dockerfile"); - generate("templates/dockerfile.ftl", context, docker, "docker file"); - } - - private void createDockerIgnore() throws IOException { - File dockerRoot = new File(projectRoot, ""); - File docker = new File(mkdirs(dockerRoot), ".dockerignore"); - generate("templates/dockerignore.ftl", context, docker, "docker ignore"); - } - - private void createApplicationConfig() throws IOException { - File meta = new File(projectRoot, "src/main/resources"); - File file = new File(mkdirs(meta), "application.properties"); - if (!file.exists()) { - Files.write(file.toPath(), Arrays.asList("# Configuration file", "# key = value"), StandardOpenOption.CREATE_NEW); - System.out.println("Configuration file created in src/main/resources/META-INF/" + file.getName()); - } - } - - private File mkdirs(File dir) { - if (!dir.exists()) { - dir.mkdirs(); - } - return dir; - } - - private SourceType getSourceType() { - return (SourceType) context.get(SOURCE_TYPE); - } - - private String getProperSourceExtension() { - return getSourceType() == SourceType.JAVA ? MojoUtils.JAVA_EXTENSION : MojoUtils.KOTLIN_EXTENSION; - } -} diff --git a/devtools/common/src/main/java/io/quarkus/QuarkusTemplate.java b/devtools/common/src/main/java/io/quarkus/QuarkusTemplate.java deleted file mode 100644 index a24981a5d9735..0000000000000 --- a/devtools/common/src/main/java/io/quarkus/QuarkusTemplate.java +++ /dev/null @@ -1,20 +0,0 @@ -package io.quarkus; - -import java.io.File; -import java.io.IOException; -import java.util.Map; - -public abstract class QuarkusTemplate { - public static final String PROJECT_GROUP_ID = "project_groupId"; - public static final String PROJECT_ARTIFACT_ID = "project_artifactId"; - public static final String PROJECT_VERSION = "project_version"; - public static final String QUARKUS_VERSION = "quarkus_version"; - public static final String PACKAGE_NAME = "package_name"; - public static final String SOURCE_TYPE = "source_type"; - public static final String CLASS_NAME = "class_name"; - public static final String RESOURCE_PATH = "path"; - - public abstract String getName(); - - public abstract void generate(final File projectRoot, Map parameters) throws IOException; -} diff --git a/devtools/common/src/main/java/io/quarkus/SourceType.java b/devtools/common/src/main/java/io/quarkus/SourceType.java deleted file mode 100644 index 1282b32bc5cd9..0000000000000 --- a/devtools/common/src/main/java/io/quarkus/SourceType.java +++ /dev/null @@ -1,5 +0,0 @@ -package io.quarkus; - -public enum SourceType { - JAVA, KOTLIN -} diff --git a/devtools/common/src/main/java/io/quarkus/cli/commands/AddExtensions.java b/devtools/common/src/main/java/io/quarkus/cli/commands/AddExtensions.java index 0c02bd9323de3..c93af6f0593bd 100644 --- a/devtools/common/src/main/java/io/quarkus/cli/commands/AddExtensions.java +++ b/devtools/common/src/main/java/io/quarkus/cli/commands/AddExtensions.java @@ -3,7 +3,8 @@ import static io.quarkus.maven.utilities.MojoUtils.getBomArtifactId; import static io.quarkus.maven.utilities.MojoUtils.readPom; -import java.io.File; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.List; import java.util.Optional; @@ -12,15 +13,18 @@ import org.apache.maven.model.Dependency; import org.apache.maven.model.Model; +import io.quarkus.cli.commands.writer.ProjectWriter; import io.quarkus.dependencies.Extension; import io.quarkus.maven.utilities.MojoUtils; public class AddExtensions { private Model model; - private File pom; + private String pom; + private ProjectWriter writer; - public AddExtensions(final File pom) throws IOException { - this.model = MojoUtils.readPom(pom); + public AddExtensions(final ProjectWriter writer, final String pom) throws IOException { + this.model = MojoUtils.readPom(new ByteArrayInputStream(writer.getContent(pom))); + this.writer = writer; this.pom = pom; } @@ -66,7 +70,9 @@ public boolean addExtensions(final Set extensions) throws IOException { } if (updated) { - MojoUtils.write(model, pom); + ByteArrayOutputStream pomOutputStream = new ByteArrayOutputStream(); + MojoUtils.write(model, pomOutputStream); + writer.write(pom, pomOutputStream.toString()); } return updated; diff --git a/devtools/common/src/main/java/io/quarkus/cli/commands/CreateProject.java b/devtools/common/src/main/java/io/quarkus/cli/commands/CreateProject.java index 2303442d90a62..e8ff89d76d7bd 100644 --- a/devtools/common/src/main/java/io/quarkus/cli/commands/CreateProject.java +++ b/devtools/common/src/main/java/io/quarkus/cli/commands/CreateProject.java @@ -1,16 +1,29 @@ package io.quarkus.cli.commands; -import static io.quarkus.QuarkusTemplate.*; -import static io.quarkus.maven.utilities.MojoUtils.*; +import static io.quarkus.maven.utilities.MojoUtils.QUARKUS_VERSION_PROPERTY; import static io.quarkus.maven.utilities.MojoUtils.configuration; +import static io.quarkus.maven.utilities.MojoUtils.getBomArtifactId; +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.File; +import static io.quarkus.templates.QuarkusTemplate.ADDITIONAL_GITIGNORE_ENTRIES; +import static io.quarkus.templates.QuarkusTemplate.CLASS_NAME; +import static io.quarkus.templates.QuarkusTemplate.PACKAGE_NAME; +import static io.quarkus.templates.QuarkusTemplate.PROJECT_ARTIFACT_ID; +import static io.quarkus.templates.QuarkusTemplate.PROJECT_GROUP_ID; +import static io.quarkus.templates.QuarkusTemplate.PROJECT_VERSION; +import static io.quarkus.templates.QuarkusTemplate.QUARKUS_VERSION; +import static io.quarkus.templates.QuarkusTemplate.SOURCE_TYPE; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Properties; +import java.util.Set; import org.apache.maven.model.Activation; import org.apache.maven.model.ActivationProperty; @@ -24,25 +37,32 @@ import org.apache.maven.model.PluginManagement; import org.apache.maven.model.Profile; -import io.quarkus.BasicRest; -import io.quarkus.SourceType; +import io.quarkus.cli.commands.writer.ProjectWriter; import io.quarkus.maven.utilities.MojoUtils; +import io.quarkus.maven.utilities.MojoUtils.Element; +import io.quarkus.templates.BuildTool; +import io.quarkus.templates.SourceType; +import io.quarkus.templates.TemplateRegistry; +import io.quarkus.templates.rest.BasicRest; /** * @author Ståle Pedersen */ public class CreateProject { - private File root; + private static final String POM_PATH = "pom.xml"; + private ProjectWriter writer; private String groupId; private String artifactId; private String version = getPluginVersion(); private SourceType sourceType = SourceType.JAVA; + private BuildTool buildTool = BuildTool.MAVEN; + private String className; private Model model; - public CreateProject(final File file) { - root = file; + public CreateProject(final ProjectWriter writer) { + this.writer = writer; } public CreateProject groupId(String groupId) { @@ -65,24 +85,25 @@ public CreateProject sourceType(SourceType sourceType) { return this; } + public CreateProject className(String className) { + this.className = className; + return this; + } + + public CreateProject buildTool(BuildTool buildTool) { + this.buildTool = buildTool; + return this; + } + public Model getModel() { return model; } public boolean doCreateProject(final Map context) throws IOException { - if (root.exists() && !root.isDirectory()) { - System.out.println("Project root needs to either not exist or be a directory"); + if (!writer.init()) { return false; - } else if (!root.exists()) { - boolean mkdirStatus = root.mkdirs(); - if (!mkdirStatus) { - System.out.println("Failed to create root directory"); - return false; - } } - System.out.println("Creating a new project in " + root.getAbsolutePath()); - MojoUtils.getAllProperties().forEach((k, v) -> context.put(k.replace("-", "_"), v)); context.put(PROJECT_GROUP_ID, groupId); @@ -90,17 +111,30 @@ public boolean doCreateProject(final Map context) throws IOExcep context.put(PROJECT_VERSION, version); context.put(QUARKUS_VERSION, getPluginVersion()); context.put(SOURCE_TYPE, sourceType); + context.put(ADDITIONAL_GITIGNORE_ENTRIES, buildTool.getGitIgnoreEntries()); + + 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); + } + context.put(CLASS_NAME, className); + } - new BasicRest() - .generate(root, context); + TemplateRegistry.createTemplateWith(BasicRest.TEMPLATE_NAME).generate(writer, context); - final File pom = new File(root + "/pom.xml"); - model = MojoUtils.readPom(pom); + final byte[] pom = writer.getContent(POM_PATH); + model = MojoUtils.readPom(new ByteArrayInputStream(pom)); addVersionProperty(model); addBom(model); addMainPluginConfig(model); addNativeProfile(model); - MojoUtils.write(model, pom); + ByteArrayOutputStream pomOutputStream = new ByteArrayOutputStream(); + MojoUtils.write(model, pomOutputStream); + writer.write(POM_PATH, pomOutputStream.toString()); return true; } @@ -224,4 +258,10 @@ private void addVersionProperty(Model model) { private boolean isParentPom(Model model) { return "pom".equals(model.getPackaging()); } + + public static SourceType determineSourceType(Set extensions) { + return extensions.stream().anyMatch(e -> e.toLowerCase().contains("kotlin")) + ? SourceType.KOTLIN + : SourceType.JAVA; + } } diff --git a/devtools/common/src/main/java/io/quarkus/cli/commands/ListExtensions.java b/devtools/common/src/main/java/io/quarkus/cli/commands/ListExtensions.java index d86e35948431c..5cdef0b7ccc0b 100644 --- a/devtools/common/src/main/java/io/quarkus/cli/commands/ListExtensions.java +++ b/devtools/common/src/main/java/io/quarkus/cli/commands/ListExtensions.java @@ -9,7 +9,9 @@ import java.util.List; import java.util.Map; import java.util.TreeMap; +import java.util.function.Consumer; +import org.apache.commons.lang3.StringUtils; import org.apache.maven.model.Dependency; import org.apache.maven.model.DependencyManagement; import org.apache.maven.model.Model; @@ -19,23 +21,44 @@ import io.quarkus.maven.utilities.QuarkusDependencyPredicate; public class ListExtensions { - private static final String FORMAT = "%-8s %-20s %-50s %s"; + private static final String FULL_FORMAT = "%-8s %-50s %-50s %-25s %s"; + private static final String SIMPLE_FORMAT = "%-50s %-50s %s"; private Model model; public ListExtensions(final Model model) { this.model = model; } - public void listExtensions() { - System.out.println("\nCurrent Quarkus extensions available: "); - System.out.println(String.format(FORMAT, "Status", "Extension", "ArtifactId", "Updated Version")); + public void listExtensions(boolean all, String format) { + Consumer currentFormatter = "simple".equalsIgnoreCase(format) ? this::simpleFormatter : this::fullFormatter; + String extensionStatus = all ? "available" : "installable"; + System.out.println(String.format("\nCurrent Quarkus extensions %s: ", extensionStatus)); + if (!"simple".equalsIgnoreCase(format)) { + currentFormatter.accept(new String[] { "Status", "Extension", "ArtifactId", "Updated Version", "Guide" }); + } final Map installed = findInstalled(); - loadExtensions().forEach(extension -> display(extension, installed)); + loadExtensions().forEach(extension -> display(extension, installed, all, currentFormatter)); + + System.out.println("\nAdd an extension to your project by adding the dependency to your " + + "project or use `mvn quarkus:add-extension -Dextensions=\"artifactId\"`"); + } + + private void simpleFormatter(String[] cols) { + System.out.println(String.format(SIMPLE_FORMAT, cols[1], cols[2], cols[4])); } - private void display(Extension extension, final Map installed) { + private void fullFormatter(String[] cols) { + System.out.println(String.format(FULL_FORMAT, cols[0], cols[1], cols[2], cols[3], cols[4])); + } + + private void display(Extension extension, final Map installed, boolean all, + Consumer formatter) { + + if (!all && installed.containsKey(String.format("%s:%s", extension.getGroupId(), extension.getArtifactId()))) { + return; + } final Dependency dependency = installed.get(String.format("%s:%s", extension.getGroupId(), extension.getArtifactId())); String label = ""; @@ -43,15 +66,17 @@ private void display(Extension extension, final Map installe final String extracted = extractVersion(dependency); if (extracted != null) { - if (MojoUtils.getPluginVersion().equalsIgnoreCase(extracted)) { + if (getPluginVersion().equalsIgnoreCase(extracted)) { label = "current"; + version = String.format("%s", extracted); } else { label = "update"; version = String.format("%s <> %s", extracted, getPluginVersion()); } } - System.out.println(String.format(FORMAT, label, extension.getName(), extension.getArtifactId(), version)); + String guide = StringUtils.defaultString(extension.getGuide(), ""); + formatter.accept(new String[] { label, extension.getName(), extension.getArtifactId(), version, guide }); } private String extractVersion(final Dependency dependency) { diff --git a/devtools/common/src/main/java/io/quarkus/cli/commands/writer/FileProjectWriter.java b/devtools/common/src/main/java/io/quarkus/cli/commands/writer/FileProjectWriter.java new file mode 100644 index 0000000000000..d0a6f5e55cefa --- /dev/null +++ b/devtools/common/src/main/java/io/quarkus/cli/commands/writer/FileProjectWriter.java @@ -0,0 +1,76 @@ +/** + * + */ +package io.quarkus.cli.commands.writer; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * ProjectWriter implementation to create direct files in the file system. + */ +public class FileProjectWriter implements ProjectWriter { + private final File root; + + public FileProjectWriter(final File file) { + root = file; + } + + @Override + public boolean init() { + if (root.exists() && !root.isDirectory()) { + System.out.println("Project root needs to either not exist or be a directory"); + return false; + } else if (!root.exists()) { + boolean mkdirStatus = root.mkdirs(); + if (!mkdirStatus) { + System.out.println("Failed to create root directory"); + return false; + } + } + + System.out.println("Creating a new project in " + root.getAbsolutePath()); + + return true; + } + + @Override + public String mkdirs(String path) { + File dirToCreate = new File(root, path); + if (!dirToCreate.exists()) { + dirToCreate.mkdirs(); + } + if (path.isEmpty()) { + if (root.getPath().isEmpty()) { + return ""; + } + return "/"; + } + return dirToCreate.getPath().substring(root.getPath().length() + 1) + "/"; + } + + @Override + public void write(String path, String content) throws IOException { + final Path outputPath = Paths.get(root + "/" + path); + Files.write(outputPath, content.getBytes()); + } + + @Override + public byte[] getContent(String path) throws IOException { + return Files.readAllBytes(Paths.get(root + "/" + path)); + } + + @Override + public boolean exists(String path) { + return new File(root, path).exists(); + } + + @Override + public void close() throws IOException { + //do nothing + } + +} diff --git a/devtools/common/src/main/java/io/quarkus/cli/commands/writer/ProjectWriter.java b/devtools/common/src/main/java/io/quarkus/cli/commands/writer/ProjectWriter.java new file mode 100644 index 0000000000000..87410ecf5f582 --- /dev/null +++ b/devtools/common/src/main/java/io/quarkus/cli/commands/writer/ProjectWriter.java @@ -0,0 +1,25 @@ +/** + * + */ +package io.quarkus.cli.commands.writer; + +import java.io.Closeable; +import java.io.IOException; + +/** + * This writer provide a way to write direct a file or inside a zip for project creation. + */ +public interface ProjectWriter extends Closeable { + + default boolean init() { + return true; + } + + void write(String path, String content) throws IOException; + + byte[] getContent(String path) throws IOException; + + String mkdirs(String path) throws IOException; + + boolean exists(String path); +} diff --git a/devtools/common/src/main/java/io/quarkus/cli/commands/writer/ZipProjectWriter.java b/devtools/common/src/main/java/io/quarkus/cli/commands/writer/ZipProjectWriter.java new file mode 100644 index 0000000000000..4eab9b484998c --- /dev/null +++ b/devtools/common/src/main/java/io/quarkus/cli/commands/writer/ZipProjectWriter.java @@ -0,0 +1,73 @@ +/** + * + */ +package io.quarkus.cli.commands.writer; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +/** + * ProjectWriter implementation to create a zip. + */ +public class ZipProjectWriter implements ProjectWriter { + private final ZipOutputStream zos; + private final Map contentByPath = new LinkedHashMap<>(); + private final List dirs = new ArrayList<>(); + + public ZipProjectWriter(final ZipOutputStream zip) { + zos = zip; + } + + @Override + public String mkdirs(String path) throws IOException { + if (path.length() == 0) { + return ""; + } + String dirPath = path; + if (!dirPath.endsWith("/")) { + dirPath = dirPath + "/"; + } + if (dirs.contains(dirPath)) { + return dirPath; + } + ZipEntry ze = new ZipEntry(dirPath); + zos.putNextEntry(ze); + zos.closeEntry(); + dirs.add(dirPath); + return dirPath; + } + + @Override + public void write(String path, String content) throws IOException { + byte[] contentBytes = content.getBytes(); + contentByPath.put(path, contentBytes); + } + + @Override + public byte[] getContent(String path) { + return contentByPath.get(path); + } + + @Override + public boolean exists(String path) { + return contentByPath.containsKey(path); + } + + @Override + public void close() throws IOException { + for (Entry entry : contentByPath.entrySet()) { + ZipEntry ze = new ZipEntry(entry.getKey()); + zos.putNextEntry(ze); + zos.write(entry.getValue()); + zos.closeEntry(); + } + + } + +} diff --git a/devtools/common/src/main/java/io/quarkus/maven/utilities/MojoUtils.java b/devtools/common/src/main/java/io/quarkus/maven/utilities/MojoUtils.java index 0d49d2aac065c..b009a0d376d89 100644 --- a/devtools/common/src/main/java/io/quarkus/maven/utilities/MojoUtils.java +++ b/devtools/common/src/main/java/io/quarkus/maven/utilities/MojoUtils.java @@ -29,6 +29,7 @@ import org.apache.maven.model.Dependency; import org.apache.maven.model.Model; import org.apache.maven.model.Plugin; +import org.apache.maven.model.PluginExecution; import org.apache.maven.model.io.xpp3.MavenXpp3Reader; import org.apache.maven.model.io.xpp3.MavenXpp3Writer; import org.apache.maven.project.MavenProject; @@ -84,6 +85,14 @@ public static String getBomArtifactId() { return get("bom-artifactId"); } + public static String getProposedMavenVersion() { + return get("proposed-maven-version"); + } + + public static String getMavenWrapperVersion() { + return get("maven-wrapper-version"); + } + private static void loadProperties() { URL url = MojoUtils.class.getClassLoader().getResource("quarkus.properties"); Objects.requireNonNull(url); @@ -219,7 +228,12 @@ public static Model readPom(final InputStream resourceAsStream) throws IOExcepti } public static void write(Model model, File outputFile) throws IOException { - try (OutputStream stream = new FileOutputStream(outputFile)) { + FileOutputStream fileOutputStream = new FileOutputStream(outputFile); + write(model, fileOutputStream); + } + + public static void write(Model model, OutputStream fileOutputStream) throws IOException { + try (OutputStream stream = fileOutputStream) { new MavenXpp3Writer().write(stream, model); } } @@ -246,6 +260,21 @@ public static String credentials(final Dependency d) { return String.format("%s:%s", d.getGroupId(), d.getArtifactId()); } + public static boolean checkProjectForMavenBuildPlugin(MavenProject project) { + for (Plugin plugin : project.getBuildPlugins()) { + if (plugin.getGroupId().equals(MojoUtils.getPluginGroupId()) + && plugin.getArtifactId().equals(MojoUtils.getPluginArtifactId())) { + for (PluginExecution pluginExecution : plugin.getExecutions()) { + if (pluginExecution.getGoals().contains("build")) { + return true; + } + } + } + } + + return false; + } + /** * Element wrapper class for configuration elements */ diff --git a/devtools/common/src/main/java/io/quarkus/remotedev/AgentRunner.java b/devtools/common/src/main/java/io/quarkus/remotedev/AgentRunner.java new file mode 100644 index 0000000000000..a33874174a4c5 --- /dev/null +++ b/devtools/common/src/main/java/io/quarkus/remotedev/AgentRunner.java @@ -0,0 +1,197 @@ +/* + * Copyright 2016, Stuart Douglas, and individual contributors as indicated + * by the @authors tag. + * + * Licensed 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.remotedev; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.lang.management.ManagementFactory; +import java.net.URI; +import java.nio.ByteBuffer; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.ConcurrentHashMap; + +import javax.websocket.ClientEndpointConfig; +import javax.websocket.ContainerProvider; +import javax.websocket.Session; + +public class AgentRunner extends QuarkusWebsocketProtocol { + + private static final String REMOTE_PASSWORD = "quarkus-security-key"; + + private final Map classChangeTimes = new ConcurrentHashMap<>(); + private final Map resourceChangeTimes = new ConcurrentHashMap<>(); + //we only care about classes changed after the agent started + private final long agentStart; + + private final String web; + private final String srcs; + private final String classes; + private final String uri; + private final String password; + + public AgentRunner(String web, String srcs, String classes, String uri, String password) { + this.web = web; + this.srcs = srcs; + this.classes = classes; + this.uri = uri; + this.password = password; + // + this.agentStart = ManagementFactory.getRuntimeMXBean().getStartTime(); + } + + public void run() { + + try { + Session session = ContainerProvider.getWebSocketContainer().connectToServer(this, + ClientEndpointConfig.Builder.create().configurator(new ClientEndpointConfig.Configurator() { + @Override + public void beforeRequest(Map> headers) { + headers.put(REMOTE_PASSWORD, Collections.singletonList(password)); + } + }).build(), new URI(uri)); + Timer timer = new Timer("Websocket ping timer"); + timer.schedule(new TimerTask() { + @Override + public void run() { + try { + session.getAsyncRemote().sendPing(ByteBuffer.allocate(0)); + } catch (IOException e) { + e.printStackTrace(); + } + } + }, 10000, 10000); + } catch (Exception e) { + e.printStackTrace(); + System.exit(1); + } + } + + protected Map changedSrcs() { + Map found = new HashMap<>(); + if (srcs != null) { + try { + scanForProgramChanges("", new File(srcs), found); + } catch (IOException e) { + e.printStackTrace(); + System.exit(1); + } + } + return found; + } + + protected Map changedWebResources() { + Map found = new HashMap<>(); + if (web != null) { + try { + scanForWebResources("", new File(web), found); + } catch (IOException e) { + e.printStackTrace(); + System.exit(1); + } + } + return found; + } + + @Override + protected void logMessage(String message) { + System.out.println(message); + } + + @Override + protected void error(Throwable t) { + t.printStackTrace(); + System.exit(1); + } + + @Override + protected void done() { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + run(); + } + + private void scanForWebResources(String currentPath, File root, Map found) throws IOException { + File serverCurrent = new File(root, currentPath); + for (String part : serverCurrent.list()) { + String fullPart = (currentPath.isEmpty() ? "" : (currentPath + File.separatorChar)) + part; + File f = new File(serverCurrent, part); + if (f.isDirectory()) { + scanForWebResources(fullPart, root, found); + } else { + File localFile = new File(serverCurrent, part); + Long recordedChange = resourceChangeTimes.get(fullPart); + long lastModified = localFile.lastModified(); + if (recordedChange == null) { + recordedChange = agentStart; + } + if (recordedChange < lastModified) { + found.put(fullPart, readFile(localFile)); + resourceChangeTimes.put(fullPart, lastModified); + } + } + } + } + + private void scanForProgramChanges(String currentPath, File root, Map found) + throws IOException { + File serverCurrent = new File(root, currentPath); + for (String part : serverCurrent.list()) { + String fullPart = (currentPath.isEmpty() ? "" : (currentPath + File.separatorChar)) + part; + File f = new File(serverCurrent, part); + if (f.isDirectory()) { + scanForProgramChanges(fullPart, root, found); + } else if (part.contains(".")) { + File localFile = new File(serverCurrent, part); + String fullPartAsClassFile = fullPart.substring(0, fullPart.lastIndexOf('.')) + ".class"; + + Long recordedChange = classChangeTimes.get(fullPartAsClassFile); + long lastModified = localFile.lastModified(); + if (recordedChange == null) { + recordedChange = agentStart; + } + System.out.println("file " + localFile + " " + recordedChange + " " + lastModified); + if (recordedChange < lastModified) { + found.put(fullPart, readFile(localFile)); + classChangeTimes.put(fullPartAsClassFile, lastModified); + } + } + } + } + + private byte[] readFile(File localFile) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try (FileInputStream in = new FileInputStream(localFile)) { + byte[] buf = new byte[1024]; + int r; + while ((r = in.read(buf)) > 0) { + out.write(buf, 0, r); + } + } + return out.toByteArray(); + } +} diff --git a/devtools/common/src/main/java/io/quarkus/remotedev/QuarkusWebsocketProtocol.java b/devtools/common/src/main/java/io/quarkus/remotedev/QuarkusWebsocketProtocol.java new file mode 100644 index 0000000000000..27576f7ddb8c6 --- /dev/null +++ b/devtools/common/src/main/java/io/quarkus/remotedev/QuarkusWebsocketProtocol.java @@ -0,0 +1,108 @@ +/* + * Copyright 2016, Stuart Douglas, and individual contributors as indicated + * by the @authors tag. + * + * Licensed 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.remotedev; + +import java.io.DataOutputStream; +import java.io.OutputStream; +import java.util.Map; +import java.util.zip.DeflaterOutputStream; + +import javax.websocket.CloseReason; +import javax.websocket.Endpoint; +import javax.websocket.EndpointConfig; +import javax.websocket.MessageHandler; +import javax.websocket.Session; + +/** + * Implements the Fakereplace Websocket protocol + */ +public abstract class QuarkusWebsocketProtocol extends Endpoint implements MessageHandler.Whole { + private static final int CLASS_CHANGE_RESPONSE = 2; + private static final int CLASS_CHANGE_REQUEST = 1; + private volatile Session session; + + protected abstract Map changedSrcs(); + + protected abstract Map changedWebResources(); + + protected abstract void logMessage(String message); + + protected abstract void error(Throwable t); + + protected abstract void done(); + + @Override + public void onOpen(Session session, EndpointConfig endpointConfig) { + logMessage("Connected to remote server"); + session.addMessageHandler(this); + this.session = session; + } + + @Override + public void onClose(Session session, CloseReason closeReason) { + logMessage("Connection closed " + closeReason); + done(); + } + + @Override + public void onError(Session session, Throwable thr) { + error(thr); + } + + @Override + public void onMessage(byte[] bytes) { + switch (bytes[0]) { + case CLASS_CHANGE_REQUEST: { + logMessage("Scanning for changed classes"); + sendChangedClasses(); + break; + } + default: { + logMessage("Ignoring unknown message type " + bytes[0]); + } + } + } + + private void sendChangedClasses() { + final Map changedSrcs = changedSrcs(); + final Map changedResources = changedWebResources(); + logMessage("Scan complete changed srcs " + changedSrcs.keySet() + + " changes resources " + changedResources); + try (OutputStream out = session.getBasicRemote().getSendStream()) { + + out.write(CLASS_CHANGE_RESPONSE); + DataOutputStream data = new DataOutputStream(new DeflaterOutputStream(out)); + data.writeInt(changedSrcs.size()); + for (Map.Entry entry : changedSrcs.entrySet()) { + data.writeUTF(entry.getKey()); + data.writeInt(entry.getValue().length); + data.write(entry.getValue()); + } + data.writeInt(changedResources.size()); + for (Map.Entry entry : changedResources.entrySet()) { + data.writeUTF(entry.getKey()); + data.writeInt(entry.getValue().length); + data.write(entry.getValue()); + } + data.close(); + } catch (Exception e) { + e.printStackTrace(); + System.exit(1); + } + } +} diff --git a/devtools/common/src/main/java/io/quarkus/templates/BuildTool.java b/devtools/common/src/main/java/io/quarkus/templates/BuildTool.java new file mode 100644 index 0000000000000..cf82442a39e90 --- /dev/null +++ b/devtools/common/src/main/java/io/quarkus/templates/BuildTool.java @@ -0,0 +1,26 @@ +package io.quarkus.templates; + +/** + * An enum of build tools, such as Maven and Gradle. + */ +public enum BuildTool { + + /** Maven build tool */ + MAVEN("\n# Maven\ntarget/\npom.xml.tag\npom.xml.releaseBackup\npom.xml.versionsBackup\nrelease.properties"), + + /** Gradle build tool */ + GRADLE("\n# Gradle\n.gradle/\nbuild/"); + + private final String gitIgnoreEntries; + + private BuildTool(String gitIgnoreEntries) { + this.gitIgnoreEntries = gitIgnoreEntries; + } + + /** + * @return {@code \n}-separated lines to add to a {@code .gitignore} file + */ + public String getGitIgnoreEntries() { + return gitIgnoreEntries; + } +} diff --git a/devtools/common/src/main/java/io/quarkus/templates/QuarkusTemplate.java b/devtools/common/src/main/java/io/quarkus/templates/QuarkusTemplate.java new file mode 100644 index 0000000000000..7eb11fec10682 --- /dev/null +++ b/devtools/common/src/main/java/io/quarkus/templates/QuarkusTemplate.java @@ -0,0 +1,22 @@ +package io.quarkus.templates; + +import java.io.IOException; +import java.util.Map; + +import io.quarkus.cli.commands.writer.ProjectWriter; + +public interface QuarkusTemplate { + String PROJECT_GROUP_ID = "project_groupId"; + String PROJECT_ARTIFACT_ID = "project_artifactId"; + String PROJECT_VERSION = "project_version"; + String QUARKUS_VERSION = "quarkus_version"; + String PACKAGE_NAME = "package_name"; + String SOURCE_TYPE = "source_type"; + String CLASS_NAME = "class_name"; + String RESOURCE_PATH = "path"; + String ADDITIONAL_GITIGNORE_ENTRIES = "additional_gitignore_entries"; + + String getName(); + + void generate(final ProjectWriter writer, Map parameters) throws IOException; +} diff --git a/devtools/common/src/main/java/io/quarkus/templates/SourceType.java b/devtools/common/src/main/java/io/quarkus/templates/SourceType.java new file mode 100644 index 0000000000000..4e69cf59bc5f9 --- /dev/null +++ b/devtools/common/src/main/java/io/quarkus/templates/SourceType.java @@ -0,0 +1,67 @@ +package io.quarkus.templates; + +import io.quarkus.maven.utilities.MojoUtils; + +public enum SourceType { + JAVA(MojoUtils.JAVA_EXTENSION), + + KOTLIN(MojoUtils.KOTLIN_EXTENSION); + + private static final String srcDirPrefix = "src/main/"; + private static final String testSrcDirPrefix = "src/test/"; + + private static final String POM_RESOURCE_TEMPLATE = "templates/%s/%s/pom-template.ftl"; + private static final String RESOURCE_TEMPLATE = "templates/%s/%s/resource-template.ftl"; + private static final String TEST_RESOURCE_TEMPLATE = "templates/%s/%s/test-resource-template.ftl"; + private static final String NATIVE_TEST_RESOURCE_TEMPLATE = "templates/%s/%s/native-test-resource-template.ftl"; + + private final String extension; + + SourceType(String extension) { + this.extension = extension; + } + + public String getSrcDir() { + return srcDirPrefix + getPathDiscriminator(); + } + + private String getPathDiscriminator() { + return name().toLowerCase(); + } + + public String getTestSrcDir() { + return testSrcDirPrefix + getPathDiscriminator(); + } + + public String getPomResourceTemplate(String templateName) { + return computeTemplateFile(POM_RESOURCE_TEMPLATE, templateName); + } + + public String getSrcResourceTemplate(String templateName) { + return computeTemplateFile(RESOURCE_TEMPLATE, templateName); + } + + public String getTestResourceTemplate(String templateName) { + return computeTemplateFile(TEST_RESOURCE_TEMPLATE, templateName); + } + + public String getNativeTestResourceTemplate(String templateName) { + return computeTemplateFile(NATIVE_TEST_RESOURCE_TEMPLATE, templateName); + } + + public String getExtension() { + return extension; + } + + public String stripExtensionFrom(String className) { + if (className != null && className.endsWith(extension)) { + return className.substring(0, className.length() - extension.length()); + } else { + return className; + } + } + + private String computeTemplateFile(String genericTemplate, String templateName) { + return String.format(genericTemplate, templateName, getPathDiscriminator()); + } +} diff --git a/devtools/common/src/main/java/io/quarkus/templates/TemplateRegistry.java b/devtools/common/src/main/java/io/quarkus/templates/TemplateRegistry.java new file mode 100644 index 0000000000000..06a3eee99bf43 --- /dev/null +++ b/devtools/common/src/main/java/io/quarkus/templates/TemplateRegistry.java @@ -0,0 +1,51 @@ +/** + * Copyright 2019 Red Hat, Inc. and/or its affiliates. + *

+ * Licensed under the Eclipse Public License version 1.0, available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package io.quarkus.templates; + +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.ServiceLoader; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author Christophe Laprun + */ +public class TemplateRegistry { + + private static final Map templates = new ConcurrentHashMap<>(7); + private static final TemplateRegistry INSTANCE = new TemplateRegistry(); + + private TemplateRegistry() { + loadTemplates(); + } + + public static TemplateRegistry getInstance() { + return INSTANCE; + } + + public static QuarkusTemplate createTemplateWith(String name) throws NoSuchElementException { + final QuarkusTemplate template = templates.get(name); + if (template == null) { + throw new NoSuchElementException("Unknown template: " + name); + } + + return template; + } + + private static void register(QuarkusTemplate template) { + if (template != null) { + templates.put(template.getName(), template); + } else { + throw new NullPointerException("Cannot register null templates"); + } + } + + private static void loadTemplates() { + ServiceLoader serviceLoader = ServiceLoader.load(QuarkusTemplate.class); + serviceLoader.iterator().forEachRemaining(TemplateRegistry::register); + } +} diff --git a/devtools/common/src/main/java/io/quarkus/templates/rest/BasicRest.java b/devtools/common/src/main/java/io/quarkus/templates/rest/BasicRest.java new file mode 100644 index 0000000000000..de3be867e4016 --- /dev/null +++ b/devtools/common/src/main/java/io/quarkus/templates/rest/BasicRest.java @@ -0,0 +1,167 @@ +package io.quarkus.templates.rest; + +import static java.lang.String.format; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Collectors; + +import org.apache.maven.model.Model; + +import io.quarkus.cli.commands.writer.ProjectWriter; +import io.quarkus.maven.utilities.MojoUtils; +import io.quarkus.templates.QuarkusTemplate; +import io.quarkus.templates.SourceType; + +public class BasicRest implements QuarkusTemplate { + + public static final String TEMPLATE_NAME = "basic-rest"; + + private Map context; + private String path = "/hello"; + private ProjectWriter writer; + private String srcMainPath; + private String testMainPath; + private SourceType type; + + public BasicRest() { + } + + @Override + public String getName() { + return TEMPLATE_NAME; + } + + @Override + public void generate(final ProjectWriter writer, Map parameters) throws IOException { + this.writer = writer; + this.context = parameters == null ? Collections.emptyMap() : parameters; + this.type = (SourceType) context.get(SOURCE_TYPE); + + initProject(); + setupContext(); + + createClasses(); + + createIndexPage(); + createDockerFiles(); + createDockerIgnore(); + createApplicationConfig(); + + createGitIgnore(); + } + + private void setupContext() throws IOException { + if (context.get(CLASS_NAME) != null) { + String packageName = (String) context.get(PACKAGE_NAME); + + if (packageName != null) { + String packageDir = srcMainPath + '/' + packageName.replace('.', '/'); + String testPackageDir = testMainPath + '/' + packageName.replace('.', '/'); + srcMainPath = writer.mkdirs(packageDir); + testMainPath = writer.mkdirs(testPackageDir); + } else { + throw new NullPointerException("Need a non-null package name"); + } + } + } + + private void createClasses() throws IOException { + Object className = context.get(CLASS_NAME); + // If className is null we disable the generation of the JAX-RS resource. + if (className != null) { + String extension = type.getExtension(); + String classFile = srcMainPath + '/' + className + extension; + String testClassFile = testMainPath + '/' + className + "Test" + extension; + String itTestClassFile = testMainPath + '/' + "Native" + className + "IT" + extension; + String name = getName(); + generate(type.getSrcResourceTemplate(name), context, classFile, "resource code"); + generate(type.getTestResourceTemplate(name), context, testClassFile, "test code"); + generate(type.getNativeTestResourceTemplate(name), context, itTestClassFile, "IT code"); + } + } + + @SuppressWarnings("unchecked") + private T get(final String key, final String defaultValue) { + return (T) context.getOrDefault(key, defaultValue); + } + + private boolean initProject() throws IOException { + boolean newProject = !writer.exists("pom.xml"); + if (newProject) { + generate(type.getPomResourceTemplate(getName()), context, "pom.xml", "pom.xml"); + } else { + final Model model = MojoUtils.readPom(new ByteArrayInputStream(writer.getContent("pom.xml"))); + context.put(PROJECT_GROUP_ID, model.getGroupId()); + context.put(PROJECT_ARTIFACT_ID, model.getArtifactId()); + } + + path = get(RESOURCE_PATH, path); + + srcMainPath = writer.mkdirs(type.getSrcDir()); + testMainPath = writer.mkdirs(type.getTestSrcDir()); + + return newProject; + } + + private void generate(final String templateName, final Map context, final String outputFilePath, + final String resourceType) + throws IOException { + if (!writer.exists(outputFilePath)) { + String path = templateName.startsWith("/") ? templateName : "/" + templateName; + try (final BufferedReader stream = new BufferedReader( + new InputStreamReader(getClass().getResourceAsStream(path), StandardCharsets.UTF_8))) { + String template = stream.lines().collect(Collectors.joining("\n")); + for (Entry e : context.entrySet()) { + if (e.getValue() != null) { // Exclude null values (classname and path can be null) + template = template.replace(format("${%s}", e.getKey()), e.getValue().toString()); + } + } + writer.write(outputFilePath, template); + } + } + } + + private void createIndexPage() throws IOException { + // Generate index page + 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"); + } + + } + + private void createDockerFiles() throws IOException { + String dockerRoot = "src/main/docker"; + String dockerRootDir = writer.mkdirs(dockerRoot); + generate("templates/dockerfile-native.ftl", context, dockerRootDir + "Dockerfile.native", + "native docker file"); + generate("templates/dockerfile-jvm.ftl", context, dockerRootDir + "Dockerfile.jvm", "jvm docker file"); + } + + private void createDockerIgnore() throws IOException { + String docker = writer.mkdirs("") + ".dockerignore"; + generate("templates/dockerignore.ftl", context, docker, "docker ignore"); + } + + private void createGitIgnore() throws IOException { + String gitignore = writer.mkdirs("") + ".gitignore"; + generate("templates/gitignore.ftl", context, gitignore, "git ignore"); + } + + private void createApplicationConfig() throws IOException { + String meta = "src/main/resources"; + String file = writer.mkdirs(meta) + "application.properties"; + if (!writer.exists(file)) { + writer.write(file, "# Configuration file" + System.lineSeparator() + "# key = value"); + System.out.println("Configuration file created in " + file); + } + } +} diff --git a/devtools/common/src/main/java/io/quarkus/utilities/JavaBinFinder.java b/devtools/common/src/main/java/io/quarkus/utilities/JavaBinFinder.java new file mode 100644 index 0000000000000..dd49fd59a347d --- /dev/null +++ b/devtools/common/src/main/java/io/quarkus/utilities/JavaBinFinder.java @@ -0,0 +1,86 @@ +package io.quarkus.utilities; + +import java.io.File; + +public class JavaBinFinder { + /** + * Search for the java command in the order: + * 1. maven-toolchains plugin configuration + * 2. java.home location + * 3. java[.exe] on the system path + * + * @return the java command to use + */ + public static String findBin() { + // use the same JVM as the one used to run Maven (the "java.home" one) + String java = System.getProperty("java.home") + File.separator + "bin" + File.separator + "java"; + File javaCheck = new File(java); + if (!javaCheck.canExecute()) { + + java = null; + // Try executable extensions if windows + if (OS.determineOS() == OS.WINDOWS && System.getenv().containsKey("PATHEXT")) { + String extpath = System.getenv("PATHEXT"); + String[] exts = extpath.split(";"); + for (String ext : exts) { + File winExe = new File(javaCheck.getAbsolutePath() + ext); + if (winExe.canExecute()) { + java = winExe.getAbsolutePath(); + break; + } + } + } + // Fallback to java on the path + if (java == null) { + if (OS.determineOS() == OS.WINDOWS) { + java = "java.exe"; + } else { + java = "java"; + } + } + } + return java; + } + + /** + * Enum to classify the os.name system property + */ + static enum OS { + WINDOWS, + LINUX, + MAC, + OTHER; + + private String version; + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + static OS determineOS() { + final String osName = System.getProperty("os.name").toLowerCase(); + final OS os; + if (osName.contains("windows")) { + os = OS.WINDOWS; + } else if (osName.contains("linux") + || osName.contains("freebsd") + || osName.contains("unix") + || osName.contains("sunos") + || osName.contains("solaris") + || osName.contains("aix")) { + os = OS.LINUX; + } else if (osName.contains("mac os")) { + os = OS.MAC; + } else { + os = OS.OTHER; + } + + os.setVersion(System.getProperty("os.version")); + return os; + } + } +} diff --git a/devtools/common/src/main/resources/META-INF/services/io.quarkus.templates.QuarkusTemplate b/devtools/common/src/main/resources/META-INF/services/io.quarkus.templates.QuarkusTemplate new file mode 100644 index 0000000000000..fc8e29c817cb2 --- /dev/null +++ b/devtools/common/src/main/resources/META-INF/services/io.quarkus.templates.QuarkusTemplate @@ -0,0 +1 @@ +io.quarkus.templates.rest.BasicRest \ No newline at end of file diff --git a/devtools/common/src/main/resources/templates/native-test-resource-template.ftl b/devtools/common/src/main/resources/templates/basic-rest/java/native-test-resource-template.ftl similarity index 100% rename from devtools/common/src/main/resources/templates/native-test-resource-template.ftl rename to devtools/common/src/main/resources/templates/basic-rest/java/native-test-resource-template.ftl diff --git a/devtools/common/src/main/resources/templates/pom-template.ftl b/devtools/common/src/main/resources/templates/basic-rest/java/pom-template.ftl similarity index 93% rename from devtools/common/src/main/resources/templates/pom-template.ftl rename to devtools/common/src/main/resources/templates/basic-rest/java/pom-template.ftl index 6725979229f50..4aa11bdc62f54 100644 --- a/devtools/common/src/main/resources/templates/pom-template.ftl +++ b/devtools/common/src/main/resources/templates/basic-rest/java/pom-template.ftl @@ -33,7 +33,7 @@ quarkus-resteasy - + io.quarkus quarkus-junit5 @@ -48,6 +48,7 @@ + ${plugin_groupId} ${plugin_artifactId} @@ -60,6 +61,7 @@ + org.apache.maven.plugins maven-surefire-plugin @@ -74,6 +76,7 @@ + native @@ -98,6 +101,7 @@ + org.apache.maven.plugins maven-failsafe-plugin diff --git a/devtools/common/src/main/resources/templates/resource-template.ftl b/devtools/common/src/main/resources/templates/basic-rest/java/resource-template.ftl similarity index 100% rename from devtools/common/src/main/resources/templates/resource-template.ftl rename to devtools/common/src/main/resources/templates/basic-rest/java/resource-template.ftl diff --git a/devtools/common/src/main/resources/templates/test-resource-template.ftl b/devtools/common/src/main/resources/templates/basic-rest/java/test-resource-template.ftl similarity index 100% rename from devtools/common/src/main/resources/templates/test-resource-template.ftl rename to devtools/common/src/main/resources/templates/basic-rest/java/test-resource-template.ftl diff --git a/devtools/common/src/main/resources/templates/native-test-resource-template-kotlin.ftl b/devtools/common/src/main/resources/templates/basic-rest/kotlin/native-test-resource-template.ftl similarity index 100% rename from devtools/common/src/main/resources/templates/native-test-resource-template-kotlin.ftl rename to devtools/common/src/main/resources/templates/basic-rest/kotlin/native-test-resource-template.ftl diff --git a/devtools/common/src/main/resources/templates/pom-template-kotlin.ftl b/devtools/common/src/main/resources/templates/basic-rest/kotlin/pom-template.ftl similarity index 95% rename from devtools/common/src/main/resources/templates/pom-template-kotlin.ftl rename to devtools/common/src/main/resources/templates/basic-rest/kotlin/pom-template.ftl index 38745e7609c5e..885d9fbd2c7ee 100644 --- a/devtools/common/src/main/resources/templates/pom-template-kotlin.ftl +++ b/devtools/common/src/main/resources/templates/basic-rest/kotlin/pom-template.ftl @@ -33,10 +33,6 @@ io.quarkus quarkus-resteasy - - io.quarkus - quarkus-arc - org.jetbrains.kotlin @@ -47,6 +43,7 @@ io.quarkus quarkus-junit5 + test io.rest-assured @@ -56,8 +53,8 @@ - ${project.basedir}/src/main/kotlin - ${project.basedir}/src/test/kotlin + src/main/kotlin + src/test/kotlin ${plugin_groupId} diff --git a/devtools/common/src/main/resources/templates/resource-template-kotlin.ftl b/devtools/common/src/main/resources/templates/basic-rest/kotlin/resource-template.ftl similarity index 100% rename from devtools/common/src/main/resources/templates/resource-template-kotlin.ftl rename to devtools/common/src/main/resources/templates/basic-rest/kotlin/resource-template.ftl diff --git a/devtools/common/src/main/resources/templates/test-resource-template-kotlin.ftl b/devtools/common/src/main/resources/templates/basic-rest/kotlin/test-resource-template.ftl similarity index 100% rename from devtools/common/src/main/resources/templates/test-resource-template-kotlin.ftl rename to devtools/common/src/main/resources/templates/basic-rest/kotlin/test-resource-template.ftl diff --git a/devtools/common/src/main/resources/templates/dockerfile-jvm.ftl b/devtools/common/src/main/resources/templates/dockerfile-jvm.ftl new file mode 100644 index 0000000000000..9b4ef6b635175 --- /dev/null +++ b/devtools/common/src/main/resources/templates/dockerfile-jvm.ftl @@ -0,0 +1,22 @@ +#### +# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode +# +# Before building the docker image run: +# +# mvn package +# +# Then, build the image with: +# +# docker build -f src/main/docker/Dockerfile.jvm -t quarkus/${project_artifactId}-jvm . +# +# Then run the container using: +# +# docker run -i --rm -p 8080:8080 quarkus/${project_artifactId}-jvm +# +### +FROM fabric8/java-alpine-openjdk8-jre +ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" +ENV AB_ENABLED=jmx_exporter +COPY target/lib/* /deployments/lib/ +COPY target/*-runner.jar /deployments/app.jar +ENTRYPOINT [ "/deployments/run-java.sh" ] diff --git a/devtools/common/src/main/resources/templates/dockerfile.ftl b/devtools/common/src/main/resources/templates/dockerfile-native.ftl similarity index 59% rename from devtools/common/src/main/resources/templates/dockerfile.ftl rename to devtools/common/src/main/resources/templates/dockerfile-native.ftl index fa4f3ba3c959f..ef6fcc6b7d161 100644 --- a/devtools/common/src/main/resources/templates/dockerfile.ftl +++ b/devtools/common/src/main/resources/templates/dockerfile-native.ftl @@ -1,11 +1,13 @@ #### +# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode +# # Before building the docker image run: # # mvn package -Pnative -Dnative-image.docker-build=true # # Then, build the image with: # -# docker build -f src/main/docker/Dockerfile -t quarkus/${project_artifactId} . +# docker build -f src/main/docker/Dockerfile.native -t quarkus/${project_artifactId} . # # Then run the container using: # @@ -17,4 +19,4 @@ WORKDIR /work/ COPY target/*-runner /work/application RUN chmod 775 /work EXPOSE 8080 -CMD ["./application", "-Dquarkus.http.host=0.0.0.0"] \ No newline at end of file +CMD ["./application", "-Dquarkus.http.host=0.0.0.0"] diff --git a/devtools/common/src/main/resources/templates/gitignore.ftl b/devtools/common/src/main/resources/templates/gitignore.ftl new file mode 100644 index 0000000000000..02195a58079c1 --- /dev/null +++ b/devtools/common/src/main/resources/templates/gitignore.ftl @@ -0,0 +1,29 @@ +# Eclipse +.project +.classpath +.settings/ +bin/ + +# IntelliJ +.idea +*.ipr +*.iml +*.iws + +# NetBeans +nb-configuration.xml + +# Visual Studio Code +.vscode + +# OSX +.DS_Store + +# Vim +*.swp +*.swo + +# patch +*.orig +*.rej +${additional_gitignore_entries} diff --git a/devtools/common/src/test/java/io/quarkus/cli/commands/AddExtensionsTest.java b/devtools/common/src/test/java/io/quarkus/cli/commands/AddExtensionsTest.java index f574e11401ead..fa279cd9ca2f8 100644 --- a/devtools/common/src/test/java/io/quarkus/cli/commands/AddExtensionsTest.java +++ b/devtools/common/src/test/java/io/quarkus/cli/commands/AddExtensionsTest.java @@ -11,6 +11,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import io.quarkus.cli.commands.writer.FileProjectWriter; import io.quarkus.maven.utilities.MojoUtils; public class AddExtensionsTest { @@ -19,13 +20,14 @@ public void addExtension() throws IOException { final File pom = new File("target/extensions-test", "pom.xml"); CreateProjectTest.delete(pom.getParentFile()); - new CreateProject(pom.getParentFile()) + new CreateProject(new FileProjectWriter(pom.getParentFile())) .groupId("org.acme") .artifactId("add-extension-test") .version("0.0.1-SNAPSHOT") .doCreateProject(new HashMap<>()); - new AddExtensions(pom) + File pomFile = new File(pom.getAbsolutePath()); + new AddExtensions(new FileProjectWriter(pomFile.getParentFile()), pomFile.getName()) .addExtensions(new HashSet<>(asList("agroal", "arc", " hibernate-validator"))); Model model = MojoUtils.readPom(pom); diff --git a/devtools/common/src/test/java/io/quarkus/cli/commands/CreateProjectTest.java b/devtools/common/src/test/java/io/quarkus/cli/commands/CreateProjectTest.java index 9f3888ba5714b..8912de325ddc6 100644 --- a/devtools/common/src/test/java/io/quarkus/cli/commands/CreateProjectTest.java +++ b/devtools/common/src/test/java/io/quarkus/cli/commands/CreateProjectTest.java @@ -1,34 +1,70 @@ package io.quarkus.cli.commands; -import static io.quarkus.maven.utilities.MojoUtils.*; +import static io.quarkus.maven.utilities.MojoUtils.QUARKUS_VERSION_PROPERTY; +import static io.quarkus.maven.utilities.MojoUtils.getBomArtifactId; +import static io.quarkus.maven.utilities.MojoUtils.getPluginArtifactId; +import static io.quarkus.maven.utilities.MojoUtils.getPluginGroupId; 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; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.Comparator; import java.util.HashMap; import java.util.Map; import java.util.stream.Stream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; -import org.apache.commons.io.FileUtils; import org.apache.maven.model.Model; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import io.quarkus.cli.commands.writer.FileProjectWriter; +import io.quarkus.cli.commands.writer.ZipProjectWriter; import io.quarkus.maven.utilities.MojoUtils; +import io.quarkus.templates.BuildTool; public class CreateProjectTest { @Test public void create() throws IOException { final File file = new File("target/basic-rest"); delete(file); - final CreateProject createProject = new CreateProject(file).groupId("io.quarkus") + final CreateProject createProject = new CreateProject(new FileProjectWriter(file)).groupId("io.quarkus") .artifactId("basic-rest") .version("1.0.0-SNAPSHOT"); Assertions.assertTrue(createProject.doCreateProject(new HashMap<>())); + + final File gitignore = new File(file, ".gitignore"); + Assertions.assertTrue(gitignore.exists()); + final String gitignoreContent = new String(Files.readAllBytes(gitignore.toPath()), StandardCharsets.UTF_8); + Assertions.assertTrue(gitignoreContent.contains("\ntarget/\n")); + } + + @Test + public void createGradle() throws IOException { + final File file = new File("target/basic-rest-gradle"); + delete(file); + final CreateProject createProject = new CreateProject(new FileProjectWriter(file)).groupId("io.quarkus") + .artifactId("basic-rest") + .version("1.0.0-SNAPSHOT") + .buildTool(BuildTool.GRADLE); + + Assertions.assertTrue(createProject.doCreateProject(new HashMap<>())); + + final File gitignore = new File(file, ".gitignore"); + Assertions.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")); } @Test @@ -39,18 +75,18 @@ public void createOnTopPomWithoutResource() throws IOException { Model model = new Model(); model.setModelVersion("4.0.0"); - model.setGroupId("com.acme"); + model.setGroupId("org.acme"); model.setArtifactId("foobar"); model.setVersion("10.1.2"); final File pom = new File(testDir, "pom.xml"); MojoUtils.write(model, pom); - final CreateProject createProject = new CreateProject(testDir).groupId("something.is") + final CreateProject createProject = new CreateProject(new FileProjectWriter(testDir)).groupId("something.is") .artifactId("wrong") .version("1.0.0-SNAPSHOT"); Assertions.assertTrue(createProject.doCreateProject(new HashMap<>())); - assertThat(FileUtils.readFileToString(pom, "UTF-8")) + assertThat(contentOf(pom, "UTF-8")) .contains(getPluginArtifactId(), QUARKUS_VERSION_PROPERTY, getPluginGroupId()); assertThat(new File(testDir, "src/main/java")).isDirectory(); assertThat(new File(testDir, "src/test/java")).isDirectory(); @@ -66,7 +102,7 @@ public void createOnTopPomWithoutResource() throws IOException { return list != null && list.length == 0; }); - assertThat(FileUtils.readFileToString(new File(testDir, "pom.xml"), "UTF-8")) + assertThat(contentOf(new File(testDir, "pom.xml"), "UTF-8")) .containsIgnoringCase(getBomArtifactId()); } @@ -79,20 +115,19 @@ public void createOnTopPomWithResource() throws IOException { Model model = new Model(); model.setModelVersion("4.0.0"); - model.setGroupId("com.acme"); + model.setGroupId("org.acme"); model.setArtifactId("foobar"); model.setVersion("10.1.2"); final File pom = new File(testDir, "pom.xml"); MojoUtils.write(model, pom); - final CreateProject createProject = new CreateProject(testDir).groupId("something.is") + final CreateProject createProject = new CreateProject(new FileProjectWriter(testDir)).groupId("something.is") .artifactId("wrong") + .className("org.foo.MyResource") .version("1.0.0-SNAPSHOT"); - Map ctxt = new HashMap<>(); - ctxt.put("className", "org.foo.MyResource"); - Assertions.assertTrue(createProject.doCreateProject(ctxt)); + Assertions.assertTrue(createProject.doCreateProject(new HashMap<>())); - assertThat(FileUtils.readFileToString(pom, "UTF-8")) + assertThat(contentOf(pom, "UTF-8")) .contains(getPluginArtifactId(), QUARKUS_VERSION_PROPERTY, getPluginGroupId()); assertThat(new File(testDir, "src/main/java")).isDirectory(); assertThat(new File(testDir, "src/test/java")).isDirectory(); @@ -105,8 +140,7 @@ public void createOnTopPomWithResource() throws IOException { assertThat(new File(testDir, "src/test/java/org/foo/MyResourceTest.java")).isFile(); assertThat(new File(testDir, "src/test/java/org/foo/NativeMyResourceIT.java")).isFile(); - assertThat(FileUtils.readFileToString(new File(testDir, "pom.xml"), "UTF-8")) - .containsIgnoringCase(getBomArtifactId()); + assertThat(contentOf(new File(testDir, "pom.xml"))).contains(getBomArtifactId()); } @@ -123,16 +157,17 @@ public void createNewWithCustomizations() throws IOException { properties.put("className", "org.acme.MyResource"); properties.put("extensions", "commons-io:commons-io:2.5"); - Assertions.assertTrue(new CreateProject(testDir).groupId("org.acme") + Assertions.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(); assertThat(new File(testDir, "src/main/java/org/acme/MyApplication.java")).doesNotExist(); - assertThat(FileUtils.readFileToString(pom, "UTF-8")) + assertThat(contentOf(pom, "UTF-8")) .contains(getPluginArtifactId(), QUARKUS_VERSION_PROPERTY, getPluginGroupId()); assertThat(new File(testDir, "src/main/java")).isDirectory(); assertThat(new File(testDir, "src/test/java")).isDirectory(); @@ -140,7 +175,7 @@ public void createNewWithCustomizations() throws IOException { 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")) + assertThat(contentOf(new File(testDir, "pom.xml"), "UTF-8")) .containsIgnoringCase(MojoUtils.QUARKUS_VERSION_PROPERTY); } @@ -158,4 +193,59 @@ public static void delete(final File file) throws IOException { Assertions.assertFalse( Files.exists(file.toPath()), "Directory still exists"); } + + @Test + public void createZip() throws IOException { + final File file = new File("target/zip"); + delete(file); + 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)) { + final CreateProject createProject = new CreateProject(zipWriter).groupId("io.quarkus") + .artifactId("basic-rest") + .version("1.0.0-SNAPSHOT"); + Assertions.assertTrue(createProject.doCreateProject(new HashMap<>())); + } + Assertions.assertTrue(zipFile.exists()); + File unzipProject = new File(file, "unzipProject"); + try (FileInputStream fis = new FileInputStream(zipFile); ZipInputStream zis = new ZipInputStream(fis)) { + ZipEntry zipEntry = zis.getNextEntry(); + byte[] buffer = new byte[1024]; + while (zipEntry != null) { + File newFile = newFile(unzipProject, zipEntry); + if (zipEntry.isDirectory()) { + newFile.mkdirs(); + } else { + new File(newFile.getParent()).mkdirs(); + FileOutputStream fos = new FileOutputStream(newFile); + int len; + while ((len = zis.read(buffer)) > 0) { + fos.write(buffer, 0, len); + } + fos.close(); + } + zipEntry = zis.getNextEntry(); + } + zis.closeEntry(); + } + final File gitignore = new File(unzipProject, ".gitignore"); + Assertions.assertTrue(gitignore.exists()); + final String gitignoreContent = new String(Files.readAllBytes(gitignore.toPath()), StandardCharsets.UTF_8); + Assertions.assertTrue(gitignoreContent.contains("\ntarget/\n")); + } + + private static File newFile(File destinationDir, ZipEntry zipEntry) throws IOException { + File destFile = new File(destinationDir, zipEntry.getName()); + + String destDirPath = destinationDir.getCanonicalPath(); + String destFilePath = destFile.getCanonicalPath(); + + if (!destFilePath.startsWith(destDirPath + File.separator)) { + throw new IOException("Entry is outside of the target dir: " + zipEntry.getName()); + } + + return destFile; + } } diff --git a/devtools/common/src/test/java/io/quarkus/cli/commands/ListExtensionsTest.java b/devtools/common/src/test/java/io/quarkus/cli/commands/ListExtensionsTest.java index 809f4dd1d9063..d4de5fc8a675c 100644 --- a/devtools/common/src/test/java/io/quarkus/cli/commands/ListExtensionsTest.java +++ b/devtools/common/src/test/java/io/quarkus/cli/commands/ListExtensionsTest.java @@ -19,6 +19,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import io.quarkus.cli.commands.writer.FileProjectWriter; import io.quarkus.maven.utilities.MojoUtils; import io.quarkus.maven.utilities.QuarkusDependencyPredicate; @@ -31,13 +32,14 @@ public void listWithBom() throws IOException { CreateProjectTest.delete(pom.getParentFile()); final HashMap context = new HashMap<>(); - new CreateProject(pom.getParentFile()) + new CreateProject(new FileProjectWriter(pom.getParentFile())) .groupId(getPluginGroupId()) .artifactId("add-extension-test") .version("0.0.1-SNAPSHOT") .doCreateProject(context); - new AddExtensions(pom) + File pomFile = new File(pom.getAbsolutePath()); + new AddExtensions(new FileProjectWriter(pomFile.getParentFile()), pomFile.getName()) .addExtensions(new HashSet<>(asList("commons-io:commons-io:2.5", "Agroal"))); Model model = readPom(pom); @@ -62,13 +64,14 @@ public void listWithBomExtensionWithSpaces() throws IOException { CreateProjectTest.delete(pom.getParentFile()); final HashMap context = new HashMap<>(); - new CreateProject(pom.getParentFile()) + new CreateProject(new FileProjectWriter(pom.getParentFile())) .groupId(getPluginGroupId()) .artifactId("add-extension-test") .version("0.0.1-SNAPSHOT") .doCreateProject(context); - new AddExtensions(pom) + File pomFile = new File(pom.getAbsolutePath()); + new AddExtensions(new FileProjectWriter(pomFile.getParentFile()), pomFile.getName()) .addExtensions(new HashSet<>(asList("resteasy", " hibernate-validator "))); Model model = readPom(pom); @@ -88,7 +91,7 @@ public void listWithoutBom() throws IOException { CreateProjectTest.delete(pom.getParentFile()); final HashMap context = new HashMap<>(); - new CreateProject(pom.getParentFile()) + new CreateProject(new FileProjectWriter(pom.getParentFile())) .groupId(getPluginGroupId()) .artifactId("add-extension-test") .version("0.0.1-SNAPSHOT") @@ -103,7 +106,8 @@ public void listWithoutBom() throws IOException { MojoUtils.write(model, pom); - new AddExtensions(pom) + File pomFile = new File(pom.getAbsolutePath()); + new AddExtensions(new FileProjectWriter(pomFile.getParentFile()), pomFile.getName()) .addExtensions(new HashSet<>(asList("commons-io:commons-io:2.5", "Agroal"))); model = readPom(pom); @@ -113,7 +117,7 @@ public void listWithoutBom() throws IOException { try (final PrintStream printStream = new PrintStream(baos, false, "UTF-8")) { System.setOut(printStream); new ListExtensions(model) - .listExtensions(); + .listExtensions(true, "full"); } finally { System.setOut(out); } @@ -127,7 +131,10 @@ public void listWithoutBom() throws IOException { agroal = true; } else if (line.contains(" RESTEasy ")) { assertTrue(line.startsWith("update"), "RESTEasy should list as having an update: " + line); - assertTrue(line.endsWith(getPluginVersion()), "RESTEasy should list as having an update: " + line); + assertTrue( + line.endsWith( + String.format("%-16s %s", getPluginVersion(), "https://quarkus.io/guides/rest-json-guide")), + "RESTEasy should list as having an update: " + line); resteasy = true; } else if (line.contains(" Hibernate Validator ")) { assertTrue(line.startsWith(" "), "Hibernate Validator should not list as anything: " + line); diff --git a/devtools/extension-plugin/pom.xml b/devtools/extension-plugin/pom.xml index 8cad1f30fb5da..8c00feec8be39 100644 --- a/devtools/extension-plugin/pom.xml +++ b/devtools/extension-plugin/pom.xml @@ -59,14 +59,17 @@ org.junit.jupiter junit-jupiter-api + test org.junit.jupiter junit-jupiter-params + test org.junit.jupiter junit-jupiter-engine + test org.apache.maven.shared @@ -77,6 +80,7 @@ org.assertj assertj-core + test diff --git a/devtools/gradle-it/build.gradle b/devtools/gradle-it/build.gradle deleted file mode 100644 index 374cedb01b40c..0000000000000 --- a/devtools/gradle-it/build.gradle +++ /dev/null @@ -1,22 +0,0 @@ -apply plugin: 'java' -apply plugin: 'io.quarkus.gradle.plugin' - -buildscript { - repositories { - mavenCentral() - } - dependencies { - classpath fileTree(dir: '../gradle/build/libs', include: 'quarkus-gradle-plugin-999-SNAPSHOT.jar') - classpath fileTree(dir: '../gradle/target/dependencies/compile', include: '*.jar') - } -} - -repositories { - mavenCentral() -} - -dependencies { - // quarkus-resteasy, quarkus-junit5, rest are copied in pom.xml - compileOnly fileTree(dir: 'target/dependencies/compile', include: '*.jar') - testCompile fileTree(dir: 'target/dependencies/test', include: '*.jar') -} diff --git a/devtools/gradle-it/pom.xml b/devtools/gradle-it/pom.xml deleted file mode 100644 index e2d607817420d..0000000000000 --- a/devtools/gradle-it/pom.xml +++ /dev/null @@ -1,93 +0,0 @@ - - - 4.0.0 - - - io.quarkus - quarkus-build-parent - 999-SNAPSHOT - ../../build-parent/pom.xml - - - quarkus-gradle-plugin-integration-test - pom - Quarkus - Gradle Plugin IT - Quarkus - Gradle Plugin Integration Test Dependencies - - - - io.quarkus - quarkus-resteasy - ${project.version} - - - io.quarkus - quarkus-junit5 - ${project.version} - test - - - io.rest-assured - rest-assured - 3.3.0 - test - - - - - - - org.apache.maven.plugins - maven-dependency-plugin - - - copy-compile-dependencies - generate-resources - - copy-dependencies - - false - - compile - ${project.build.directory}/dependencies/compile - - - - copy-test-dependencies - generate-resources - - copy-dependencies - - false - - test - ${project.build.directory}/dependencies/test - - - - - - org.codehaus.mojo - exec-maven-plugin - - - gradle-integration-test - prepare-package - - ../gradle/gradlew - - quarkus-build - -S - - ${skip.gradle.build} - - - exec - - - - - - - diff --git a/devtools/gradle-it/src/main/java/org/acme/quickstart/GreetingResource.java b/devtools/gradle-it/src/main/java/org/acme/quickstart/GreetingResource.java deleted file mode 100644 index efe807ea01aa3..0000000000000 --- a/devtools/gradle-it/src/main/java/org/acme/quickstart/GreetingResource.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.acme.quickstart; - -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; - -@Path("/hello") -public class GreetingResource { - - @Inject - GreetingService service; - - @GET - @Produces(MediaType.TEXT_PLAIN) - @Path("/greeting/{name}") - public String greeting(@PathParam("name") String name) { - return service.greeting(name); - } - - @GET - @Produces(MediaType.TEXT_PLAIN) - public String hello() { - return "hello"; - } -} \ No newline at end of file diff --git a/devtools/gradle-it/src/main/java/org/acme/quickstart/GreetingService.java b/devtools/gradle-it/src/main/java/org/acme/quickstart/GreetingService.java deleted file mode 100644 index a253c95484495..0000000000000 --- a/devtools/gradle-it/src/main/java/org/acme/quickstart/GreetingService.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.acme.quickstart; - -import javax.enterprise.context.ApplicationScoped; - -@ApplicationScoped -public class GreetingService { - - public String greeting(String name) { - return "hello " + name; - } - -} \ No newline at end of file diff --git a/devtools/gradle-it/src/test/java/org/acme/quickstart/GreetingResourceTest.java b/devtools/gradle-it/src/test/java/org/acme/quickstart/GreetingResourceTest.java deleted file mode 100644 index 91aeae9a9e96f..0000000000000 --- a/devtools/gradle-it/src/test/java/org/acme/quickstart/GreetingResourceTest.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.acme.quickstart; - -import static io.restassured.RestAssured.given; -import static org.hamcrest.CoreMatchers.is; - -import java.util.UUID; - -import org.junit.jupiter.api.Test; - -import io.quarkus.test.junit.QuarkusTest; - -@QuarkusTest -public class GreetingResourceTest { - - @Test - public void testHelloEndpoint() { - given() - .when().get("/hello") - .then() - .statusCode(200) - .body(is("hello")); - } - - @Test - public void testGreetingEndpoint() { - String uuid = UUID.randomUUID().toString(); - given() - .pathParam("name", uuid) - .when().get("/hello/greeting/{name}") - .then() - .statusCode(200) - .body(is("hello " + uuid)); - } - -} diff --git a/devtools/gradle/build.gradle b/devtools/gradle/build.gradle index d48f5b8c3dfc6..ae8ff09aedeaa 100644 --- a/devtools/gradle/build.gradle +++ b/devtools/gradle/build.gradle @@ -1,44 +1,61 @@ plugins { - id 'java' + id 'com.gradle.build-scan' version '2.3' + id 'com.gradle.plugin-publish' version '0.10.1' + id 'java-gradle-plugin' +} + +compileJava.options.encoding = 'UTF-8' +compileTestJava.options.encoding = 'UTF-8' +if (JavaVersion.current().isJava9Compatible()) { + compileJava.options.compilerArgs.addAll(['--release', '8']) +} + +compileJava { + sourceCompatibility = '1.8' + targetCompatibility = '1.8' } repositories { - mavenLocal() + maven { url 'target/dependencies/' } mavenCentral() } dependencies { - compile localGroovy() - compile gradleApi() - compile fileTree(dir: 'target/dependencies/compile', include: '*.jar') - testCompile gradleTestKit() - testCompile fileTree(dir: 'target/dependencies/test', include: '*.jar') -} + compile "io.quarkus:quarkus-bootstrap-core:${version}" + compile "io.quarkus:quarkus-devtools-common:${version}" + compile "io.quarkus:quarkus-devtools-common-core:${version}" + compile "io.quarkus:quarkus-development-mode:${version}" + compile "io.quarkus:quarkus-creator:${version}" -jar { - manifest { - attributes 'Implementation-Version': (version ? version : 'unknown') - } + testCompile 'org.junit.jupiter:junit-jupiter-api:5.4.1' + testCompile 'org.junit.jupiter:junit-jupiter-params:5.4.1' + testCompile 'org.junit.jupiter:junit-jupiter-engine:5.4.1' } test { testLogging { events "passed", "skipped", "failed" } + useJUnitPlatform() } -task sourcesJar(type: Jar) { - classifier = 'sources' - from sourceSets.main.allSource +javadoc { + options.addStringOption('encoding', 'UTF-8') } -task javadocJar(type: Jar) { - classifier = "javadoc" - from javadoc +gradlePlugin { + plugins { + simplePlugin { + id = 'io.quarkus' + implementationClass = 'io.quarkus.gradle.QuarkusPlugin' + displayName = 'Quarkus Plugin' + description = 'Builds a Quarkus application, and provides helpers to launch dev-mode, the Quarkus CLI, building of native images' + } + } } -artifacts { - archives sourcesJar - archives javadocJar +buildScan { + //See also: https://docs.gradle.com/build-scan-plugin/ + termsOfServiceUrl = 'https://gradle.com/terms-of-service'; + termsOfServiceAgree = 'yes' } - diff --git a/devtools/gradle/gradle.properties b/devtools/gradle/gradle.properties deleted file mode 100644 index 44606f622e9ea..0000000000000 --- a/devtools/gradle/gradle.properties +++ /dev/null @@ -1 +0,0 @@ -projectName = quarkus-gradle-plugin diff --git a/devtools/gradle/gradle/wrapper/gradle-wrapper.jar b/devtools/gradle/gradle/wrapper/gradle-wrapper.jar index 87b738cbd0516..5c2d1cf016b38 100644 Binary files a/devtools/gradle/gradle/wrapper/gradle-wrapper.jar and b/devtools/gradle/gradle/wrapper/gradle-wrapper.jar differ diff --git a/devtools/gradle/gradle/wrapper/gradle-wrapper.properties b/devtools/gradle/gradle/wrapper/gradle-wrapper.properties index 5a7a6a11ceaba..f4d7b2bf616f7 100644 --- a/devtools/gradle/gradle/wrapper/gradle-wrapper.properties +++ b/devtools/gradle/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/devtools/gradle/gradlew b/devtools/gradle/gradlew index af6708ff229fd..b0d6d0ab5deb5 100755 --- a/devtools/gradle/gradlew +++ b/devtools/gradle/gradlew @@ -1,5 +1,21 @@ #!/usr/bin/env sh +# +# Copyright 2015 the original author or authors. +# +# Licensed 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. +# + ############################################################################## ## ## Gradle start up script for UN*X @@ -28,7 +44,7 @@ APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m"' +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" diff --git a/devtools/gradle/gradlew.bat b/devtools/gradle/gradlew.bat index 0f8d5937c4ad1..15e1ee37a70d7 100644 --- a/devtools/gradle/gradlew.bat +++ b/devtools/gradle/gradlew.bat @@ -1,3 +1,19 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem http://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @@ -14,7 +30,7 @@ set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome diff --git a/devtools/gradle/pom.xml b/devtools/gradle/pom.xml index c2d17aafb26a9..a7d912e83876c 100644 --- a/devtools/gradle/pom.xml +++ b/devtools/gradle/pom.xml @@ -20,7 +20,20 @@ false + + + io.quarkus + quarkus-bom + ${project.version} + pom + + + + + io.quarkus + quarkus-bootstrap-core + io.quarkus quarkus-devtools-common @@ -41,17 +54,22 @@ quarkus-creator ${project.version} + + org.junit.jupiter junit-jupiter-api + test org.junit.jupiter junit-jupiter-params + test org.junit.jupiter junit-jupiter-engine + test @@ -61,27 +79,18 @@ maven-dependency-plugin - copy-compile-dependencies + copy-dependencies generate-resources copy-dependencies false - compile - ${project.build.directory}/dependencies/compile - - - - copy-test-dependencies - generate-resources - - copy-dependencies - - false - - test - ${project.build.directory}/dependencies/test + true + true + true + true + ${project.build.directory}/dependencies @@ -136,6 +145,25 @@ + + maven-assembly-plugin + + + src/assembly/copy.xml + + false + false + + + + create-repo + package + + single + + + + org.codehaus.mojo build-helper-maven-plugin diff --git a/devtools/gradle/src/assembly/copy.xml b/devtools/gradle/src/assembly/copy.xml new file mode 100644 index 0000000000000..d2261e83989cf --- /dev/null +++ b/devtools/gradle/src/assembly/copy.xml @@ -0,0 +1,18 @@ + + + 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/main/java/io/quarkus/gradle/AppModelGradleResolver.java b/devtools/gradle/src/main/java/io/quarkus/gradle/AppModelGradleResolver.java new file mode 100644 index 0000000000000..36922d66b0ed6 --- /dev/null +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/AppModelGradleResolver.java @@ -0,0 +1,226 @@ +/* + * Copyright 2018 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed 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.gradle; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.stream.Stream; + +import org.gradle.api.GradleException; +import org.gradle.api.Project; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.artifacts.Dependency; +import org.gradle.api.artifacts.ModuleIdentifier; +import org.gradle.api.artifacts.ModuleVersionIdentifier; +import org.gradle.api.artifacts.ResolvedArtifact; +import org.gradle.api.artifacts.ResolvedConfiguration; +import org.gradle.api.file.RegularFile; +import org.gradle.api.internal.artifacts.DefaultModuleIdentifier; +import org.gradle.api.provider.Provider; +import org.gradle.jvm.tasks.Jar; + +import io.quarkus.bootstrap.BootstrapConstants; +import io.quarkus.bootstrap.model.AppArtifact; +import io.quarkus.bootstrap.model.AppDependency; +import io.quarkus.bootstrap.model.AppModel; +import io.quarkus.bootstrap.resolver.AppModelResolver; +import io.quarkus.bootstrap.resolver.AppModelResolverException; + +public class AppModelGradleResolver implements AppModelResolver { + + private AppModel appModel; + private final Project project; + + public AppModelGradleResolver(Project project) { + this.project = project; + } + + @Override + public String getLatestVersion(AppArtifact arg0, String arg1, boolean arg2) throws AppModelResolverException { + throw new UnsupportedOperationException(); + } + + @Override + public String getNextVersion(AppArtifact arg0, String fromVersion, boolean fromVersionIncluded, String arg1, boolean arg2) + throws AppModelResolverException { + throw new UnsupportedOperationException(); + } + + @Override + public List listLaterVersions(AppArtifact arg0, String arg1, boolean arg2) throws AppModelResolverException { + throw new UnsupportedOperationException(); + } + + @Override + public void relink(AppArtifact arg0, Path arg1) throws AppModelResolverException { + + } + + @Override + public Path resolve(AppArtifact appArtifact) throws AppModelResolverException { + if (!appArtifact.isResolved()) { + throw new AppModelResolverException("Artifact has not been resolved: " + appArtifact); + } + return appArtifact.getPath(); + } + + @Override + public List resolveUserDependencies(AppArtifact appArtifact, List directDeps) + throws AppModelResolverException { + return Collections.emptyList(); + } + + @Override + public AppModel resolveModel(AppArtifact appArtifact) throws AppModelResolverException { + if (appModel != null && appModel.getAppArtifact().equals(appArtifact)) { + return appModel; + } + final Configuration compileCp = project.getConfigurations().getByName("compileClasspath"); + final List extensionDeps = new ArrayList<>(); + final List userDeps = new ArrayList<>(); + Map userModules = new HashMap<>(); + for (ResolvedArtifact a : compileCp.getResolvedConfiguration().getResolvedArtifacts()) { + if (!"jar".equals(a.getExtension())) { + continue; + } + userModules.put(getModuleId(a), a.getModuleVersion().getId()); + userDeps.add(toAppDependency(a)); + + final File f = a.getFile(); + final Dependency dep; + if (f.isDirectory()) { + dep = processQuarkusDir(a, f.toPath().resolve(BootstrapConstants.META_INF)); + } else { + try (FileSystem artifactFs = FileSystems.newFileSystem(f.toPath(), null)) { + dep = processQuarkusDir(a, artifactFs.getPath(BootstrapConstants.META_INF)); + } catch (IOException e) { + throw new GradleException("Failed to process " + f, e); + } + } + if (dep != null) { + extensionDeps.add(dep); + } + } + List deploymentDeps = new ArrayList<>(); + if (!extensionDeps.isEmpty()) { + final Configuration deploymentConfig = project.getConfigurations() + .detachedConfiguration(extensionDeps.toArray(new Dependency[extensionDeps.size()])); + final ResolvedConfiguration rc = deploymentConfig.getResolvedConfiguration(); + for (ResolvedArtifact a : rc.getResolvedArtifacts()) { + final ModuleVersionIdentifier userVersion = userModules.get(getModuleId(a)); + if (userVersion != null) { + if (!userVersion.equals(a.getModuleVersion().getId())) { + project.getLogger().warn("User dependency " + userVersion + " overrides Quarkus platform dependency " + + a.getModuleVersion().getId()); + } + continue; + } + deploymentDeps.add(toAppDependency(a)); + } + } + + /* + * System.out.println("USER APP DEPENDENCIES"); + * for (AppDependency dep : userDeps) { + * System.out.println(" " + dep); + * } + * System.out.println("DEPLOYMENT DEPENDENCIES"); + * for (AppDependency dep : deploymentDeps) { + * System.out.println(" " + dep); + * } + */ + + // 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"); + if (jarTask == null) { + throw new AppModelResolverException("Failed to locate task 'jar' in the project."); + } + final Provider jarProvider = jarTask.getArchiveFile(); + if (jarProvider.isPresent()) { + final File f = jarProvider.get().getAsFile(); + if (f.exists()) { + appArtifact.setPath(f.toPath()); + } + } + } + return this.appModel = new AppModel(appArtifact, userDeps, deploymentDeps); + } + + @Override + public AppModel resolveModel(AppArtifact arg0, List arg1) throws AppModelResolverException { + throw new UnsupportedOperationException(); + } + + private ModuleIdentifier getModuleId(ResolvedArtifact a) { + final String[] split = a.getModuleVersion().toString().split(":"); + return DefaultModuleIdentifier.newId(split[0], split[1]); + } + + private AppDependency toAppDependency(ResolvedArtifact a) { + final String[] split = a.getModuleVersion().toString().split(":"); + final AppArtifact appArtifact = new AppArtifact(split[0], split[1], split[2]); + appArtifact.setPath(a.getFile().toPath()); + return new AppDependency(appArtifact, "runtime"); + } + + private Dependency processQuarkusDir(ResolvedArtifact a, Path quarkusDir) { + if (!Files.exists(quarkusDir)) { + return null; + } + final Path quarkusDescr = quarkusDir.resolve(BootstrapConstants.DESCRIPTOR_FILE_NAME); + if (!Files.exists(quarkusDescr)) { + return null; + } + final Properties extProps = resolveDescriptor(quarkusDescr); + if (extProps == null) { + return null; + } + String value = extProps.getProperty(BootstrapConstants.PROP_DEPLOYMENT_ARTIFACT); + final String[] split = value.split(":"); + + return new QuarkusExtDependency(split[0], split[1], split[2], null); + } + + private Properties resolveDescriptor(final Path path) { + final Properties rtProps; + if (!Files.exists(path)) { + // not a platform artifact + return null; + } + rtProps = new Properties(); + try (BufferedReader reader = Files.newBufferedReader(path)) { + rtProps.load(reader); + } catch (IOException e) { + throw new GradleException("Failed to load extension description " + path, e); + } + return rtProps; + } +} diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusExtDependency.java b/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusExtDependency.java new file mode 100644 index 0000000000000..52044d702f159 --- /dev/null +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusExtDependency.java @@ -0,0 +1,32 @@ +package io.quarkus.gradle; + +import org.gradle.api.artifacts.Dependency; +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 ExternalModuleDependency copy() { + return new QuarkusExtDependency(group, name, version, configuration); + } + + @Override + public boolean contentEquals(Dependency arg0) { + return true; + } +} 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 c13915cb00205..eeeda6ec16e38 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPlugin.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPlugin.java @@ -18,6 +18,10 @@ import org.gradle.api.GradleException; import org.gradle.api.Plugin; import org.gradle.api.Project; +import org.gradle.api.Task; +import org.gradle.api.plugins.BasePlugin; +import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.tasks.TaskContainer; import org.gradle.util.GradleVersion; import io.quarkus.gradle.tasks.QuarkusAddExtension; @@ -34,20 +38,32 @@ public class QuarkusPlugin implements Plugin { @Override public void apply(Project project) { verifyGradleVersion(); - //register extension + // register extension project.getExtensions().create("quarkus", QuarkusPluginExtension.class, project); - registerListExtensions(project); + registerTasks(project); } - private void registerListExtensions(Project project) { - project.getTasks().create("list-extensions", QuarkusListExtensions.class); - project.getTasks().create("add-extension", QuarkusAddExtension.class); - project.getTasks().create("quarkus-dev", QuarkusDev.class).dependsOn("build"); - project.getTasks().create("quarkus-build", QuarkusBuild.class).dependsOn("build"); - project.getTasks() - .create("quarkus-native", QuarkusNative.class) - .dependsOn("quarkus-build"); + private void registerTasks(Project project) { + TaskContainer tasks = project.getTasks(); + tasks.create("listExtensions", QuarkusListExtensions.class); + tasks.create("addExtension", QuarkusAddExtension.class); + + Task quarkusBuild = tasks.create("quarkusBuild", QuarkusBuild.class); + Task quarkusDev = tasks.create("quarkusDev", QuarkusDev.class); + + project.getPlugins().withType( + BasePlugin.class, + basePlugin -> tasks.getByName(BasePlugin.ASSEMBLE_TASK_NAME).dependsOn(quarkusBuild)); + project.getPlugins().withType( + JavaPlugin.class, + javaPlugin -> { + Task classesTask = tasks.getByName(JavaPlugin.CLASSES_TASK_NAME); + quarkusDev.dependsOn(classesTask); + quarkusBuild.dependsOn(classesTask); + }); + + tasks.create("buildNative", QuarkusNative.class).dependsOn(quarkusBuild); } private void verifyGradleVersion() { 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 0dcd478053537..32f9aaa3dcd60 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPluginExtension.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/QuarkusPluginExtension.java @@ -21,6 +21,10 @@ import org.gradle.api.Project; import org.gradle.api.plugins.JavaPluginConvention; import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.SourceSetContainer; + +import io.quarkus.bootstrap.model.AppArtifact; +import io.quarkus.bootstrap.resolver.AppModelResolver; /** * @author Ståle Pedersen @@ -29,8 +33,6 @@ public class QuarkusPluginExtension { private final Project project; - private File buildDir; - private String transformedClassesDirectory = "transformed-classes"; private String wiringClassesDirectory = "wiring-classes"; @@ -43,21 +45,12 @@ public class QuarkusPluginExtension { private String sourceDir; + private String outputConfigDirectory; + public QuarkusPluginExtension(Project project) { this.project = project; } - public File buildDir() { - if (buildDir == null) - return project.getBuildDir(); - else - return buildDir; - } - - public void setBuildDir(File buildDir) { - this.buildDir = buildDir; - } - public File transformedClassesDirectory() { return new File(project.getBuildDir() + File.separator + transformedClassesDirectory); } @@ -78,6 +71,18 @@ public void setOutputDirectory(String outputDirectory) { this.outputDirectory = outputDirectory; } + public File outputConfigDirectory() { + if (outputConfigDirectory == null) + outputConfigDirectory = project.getConvention().getPlugin(JavaPluginConvention.class) + .getSourceSets().getByName("main").getOutput().getResourcesDir().getAbsolutePath(); + + return new File(outputConfigDirectory); + } + + public void setConfigOutputDirectory(String outputDirectory) { + this.outputConfigDirectory = outputDirectory; + } + public File sourceDir() { if (sourceDir == null) sourceDir = project.getConvention().getPlugin(JavaPluginConvention.class) @@ -117,18 +122,6 @@ public void setFinalName(String finalName) { this.finalName = finalName; } - public String groupId() { - return project.getGroup().toString(); - } - - public String artifactId() { - return project.getName(); - } - - public String version() { - return project.getVersion().toString(); - } - public boolean uberJar() { return false; } @@ -139,8 +132,18 @@ public Set resourcesDir() { } public Set dependencyFiles() { - SourceSet sourceSet = project.getConvention().getPlugin(JavaPluginConvention.class).getSourceSets().getByName("main"); - return sourceSet.getCompileClasspath().getFiles(); + SourceSetContainer sourceSets = project.getConvention().getPlugin(JavaPluginConvention.class).getSourceSets(); + SourceSet sourceSet = sourceSets.getByName("main"); + Set files = sourceSet.getCompileClasspath().getFiles(); + return files; } + public AppArtifact getAppArtifact() { + return new AppArtifact(project.getGroup().toString(), project.getName(), + project.getVersion().toString()); + } + + public AppModelResolver resolveAppModel() { + return new AppModelGradleResolver(project); + } } diff --git a/devtools/gradle/src/main/java/io/quarkus/gradle/ResolvedGradleArtifactDeps.java b/devtools/gradle/src/main/java/io/quarkus/gradle/ResolvedGradleArtifactDeps.java index d8175350aeae6..78422b27e511f 100644 --- a/devtools/gradle/src/main/java/io/quarkus/gradle/ResolvedGradleArtifactDeps.java +++ b/devtools/gradle/src/main/java/io/quarkus/gradle/ResolvedGradleArtifactDeps.java @@ -20,8 +20,10 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; @@ -32,15 +34,14 @@ import org.gradle.api.plugins.JavaPluginConvention; import org.gradle.api.tasks.SourceSet; -import io.quarkus.creator.AppArtifact; -import io.quarkus.creator.AppArtifactResolverBase; +import io.quarkus.bootstrap.model.AppArtifact; +import io.quarkus.bootstrap.model.AppDependency; import io.quarkus.creator.AppCreatorException; -import io.quarkus.creator.AppDependency; /** * @author Ståle Pedersen */ -public class ResolvedGradleArtifactDeps extends AppArtifactResolverBase { +public class ResolvedGradleArtifactDeps { private final String groupId; private final String artifactId; @@ -64,9 +65,9 @@ public ResolvedGradleArtifactDeps(Project project) { } private List extractDependencies(Project project) { - List dependencies = new ArrayList<>(); + Set dependencies = new HashSet<>(); - Configuration configuration = project.getConfigurations().getByName("compileOnly"); + Configuration configuration = project.getConfigurations().getByName("compileClasspath"); ResolutionResult resolutionResult = configuration.getIncoming().getResolutionResult(); ResolvedComponentResult root = resolutionResult.getRoot(); @@ -82,33 +83,39 @@ private List extractDependencies(Project project) { artifactId + "-" + version + ".jar"); traverseDependencies(root.getDependencies(), dependencies); - return dependencies; + return new ArrayList<>(dependencies); } - private void traverseDependencies(Set dependencies, List appDependencies) { + private void traverseDependencies(Set dependencies, Set appDependencies) { dependencies.forEach(dependency -> { if (dependency instanceof ResolvedDependencyResult) { ResolvedComponentResult result = ((ResolvedDependencyResult) dependency).getSelected(); - appDependencies.add(new AppDependency(toAppArtifact(result), "provided")); - traverseDependencies(result.getDependencies(), appDependencies); + //if the resolved component is platform-compile its a bom, which do not have a jar, lets ignore it + AtomicBoolean platformCompile = new AtomicBoolean(false); + result.getVariants().forEach(variant -> { + if (variant.getDisplayName().equals("platform-compile")) + platformCompile.set(true); + }); + if (!platformCompile.get()) { + appDependencies.add(new AppDependency(toAppArtifact(result), "provided")); + traverseDependencies(result.getDependencies(), appDependencies); + } } }); } - @Override public void relink(AppArtifact artifact, Path path) throws AppCreatorException { throw new UnsupportedOperationException(); } - @Override protected void doResolve(AppArtifact coords) throws AppCreatorException { if (coords.getGroupId().equals(groupId) && coords.getArtifactId().equals(artifactId)) { - setPath(coords, projectFile.toPath()); + coords.setPath(projectFile.toPath()); } else { - File dep = findMatchingDependencyFile(coords.getGroupId(), coords.getArtifactId()); + File dep = findMatchingDependencyFile(coords.getGroupId(), coords.getArtifactId(), coords.getVersion()); if (dep != null) - setPath(coords, dep.toPath()); + coords.setPath(dep.toPath()); else throw new AppCreatorException("Did not find dependency file for: " + coords.toString()); } @@ -118,24 +125,28 @@ private AppArtifact toAppArtifact(ResolvedComponentResult result) { AppArtifact appArtifacat = new AppArtifact(result.getModuleVersion().getGroup(), result.getModuleVersion().getName(), result.getModuleVersion().getVersion()); - File dep = findMatchingDependencyFile(appArtifacat.getGroupId(), appArtifacat.getArtifactId()); + File dep = findMatchingDependencyFile(appArtifacat.getGroupId(), appArtifacat.getArtifactId(), + appArtifacat.getVersion()); if (dep != null) { - setPath(appArtifacat, dep.toPath()); + appArtifacat.setPath(dep.toPath()); } return appArtifacat; } - private File findMatchingDependencyFile(String group, String artifact) { - String searchCriteria1 = group + File.separator + artifact; - String searchCriteria2 = group.replace('.', File.separatorChar) + File.separator + artifact; + private File findMatchingDependencyFile(String group, String artifact, String version) { + String searchCriteria1 = group + File.separator + artifact + "-" + version; + String searchCriteria2 = group + File.separator + artifact + File.separatorChar + version; + String searchCriteria3 = group.replace('.', File.separatorChar) + + File.separatorChar + artifact + File.separatorChar + version; for (File file : dependencyFiles) { - if (file.getPath().contains(searchCriteria1) || file.getPath().contains(searchCriteria2)) + if (file.getPath().contains(searchCriteria1) || + file.getPath().contains(searchCriteria2) || + file.getPath().contains(searchCriteria3)) return file; } return null; } - @Override public List collectDependencies(AppArtifact coords) throws AppCreatorException { if (!coords.getGroupId().equals(groupId) || !coords.getArtifactId().equals(artifactId) || @@ -148,38 +159,34 @@ public List collectDependencies(AppArtifact coords) throws AppCre return deps; } - @Override public List collectDependencies(AppArtifact root, List deps) throws AppCreatorException { throw new UnsupportedOperationException(); } - @Override public List listLaterVersions(AppArtifact artifact, String upToVersion, boolean inclusive) throws AppCreatorException { throw new UnsupportedOperationException(); } - @Override public String getNextVersion(AppArtifact artifact, String upToVersion, boolean inclusive) throws AppCreatorException { throw new UnsupportedOperationException(); } - @Override public String getLatestVersion(AppArtifact artifact, String upToVersion, boolean inclusive) throws AppCreatorException { throw new UnsupportedOperationException(); } @Override public String toString() { - return "ResolvedGradleArtifactDeps{" + + return "ResolvedGradleArtifactDeps{\n" + "groupId='" + groupId + '\n' + - ", artifactId='" + artifactId + '\n' + - ", classifier='" + classifier + '\n' + - ", type='" + type + '\n' + - ", version='" + version + '\n' + - ", deps=" + deps + - ", dependencyFiles=" + dependencyFiles + - ", projectFile=" + projectFile + + "artifactId='" + artifactId + '\n' + + "classifier='" + classifier + '\n' + + "type='" + type + '\n' + + "version='" + version + '\n' + + "deps=" + deps + '\n' + + "dependencyFiles=" + dependencyFiles + '\n' + + "projectFile=" + projectFile + '\n' + '}'; } } 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 1c0a9723e98f1..f73b9d0f1af85 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 @@ -16,21 +16,25 @@ package io.quarkus.gradle.tasks; import java.io.File; +import java.util.ArrayList; import java.util.List; +import org.gradle.api.GradleException; +import org.gradle.api.tasks.Input; import org.gradle.api.tasks.Optional; import org.gradle.api.tasks.TaskAction; import org.gradle.api.tasks.options.Option; -import io.quarkus.creator.AppArtifact; +import io.quarkus.bootstrap.model.AppArtifact; +import io.quarkus.bootstrap.model.AppModel; +import io.quarkus.bootstrap.resolver.AppModelResolver; +import io.quarkus.bootstrap.resolver.AppModelResolverException; import io.quarkus.creator.AppCreator; import io.quarkus.creator.AppCreatorException; -import io.quarkus.creator.AppDependency; import io.quarkus.creator.phase.augment.AugmentPhase; import io.quarkus.creator.phase.curate.CurateOutcome; import io.quarkus.creator.phase.runnerjar.RunnerJarOutcome; import io.quarkus.creator.phase.runnerjar.RunnerJarPhase; -import io.quarkus.gradle.ResolvedGradleArtifactDeps; /** * @author Ståle Pedersen @@ -41,8 +45,6 @@ public class QuarkusBuild extends QuarkusTask { private String wiringClassesDirectory; - private String buildDir; - private String libDir; private String mainClass = "io.quarkus.runner.GeneratedMain"; @@ -51,6 +53,8 @@ public class QuarkusBuild extends QuarkusTask { private boolean uberJar = false; + private List ignoredEntries = new ArrayList<>(); + public QuarkusBuild() { super("Quarkus builds a runner jar based on the build jar"); } @@ -63,11 +67,12 @@ public File getTransformedClassesDirectory() { } @Option(description = "The directory for application classes transformed by processing.", option = "transformed-classes-directory") - @Optional public void setTransformedClassesDirectory(String transformedClassesDirectory) { this.transformedClassesDirectory = transformedClassesDirectory; } + @Optional + @Input public File getWiringClassesDirectory() { if (wiringClassesDirectory == null) return extension().wiringClassesDirectory(); @@ -76,24 +81,12 @@ public File getWiringClassesDirectory() { } @Option(description = "The directory for classes generated by processing", option = "wiring-classes-directory") - @Optional public void setWiringClassesDirectory(String wiringClassesDirectory) { this.wiringClassesDirectory = wiringClassesDirectory; } - public File getBuildDir() { - if (buildDir == null) - return extension().buildDir(); - else - return new File(buildDir); - } - - @Option(description = "The directory where built classes are stored", option = "build-dir") @Optional - public void setBuildDir(String buildDir) { - this.buildDir = buildDir; - } - + @Input public File getLibDir() { if (libDir == null) return extension().libDir(); @@ -102,81 +95,95 @@ public File getLibDir() { } @Option(description = "The directory for library jars", option = "lib-dir") - @Optional public void setLibDir(String libDir) { this.libDir = libDir; } + @Input + @Optional public String getMainClass() { return mainClass; } @Option(description = "Name of the main class generated by the quarkus build process", option = "main-class") - @Optional public void setMainClass(String mainClass) { this.mainClass = mainClass; } + @Optional + @Input public boolean isUseStaticInit() { return useStaticInit; } @Option(description = "", option = "use-static-init") - @Optional public void setUseStaticInit(boolean useStaticInit) { this.useStaticInit = useStaticInit; } + @Optional + @Input public boolean isUberJar() { return uberJar; } @Option(description = "Set to true if the build task should build an uberjar", option = "uber-jar") - @Optional public void setUberJar(boolean uberJar) { this.uberJar = uberJar; } + @Optional + @Input + public List getIgnoredEntries() { + return ignoredEntries; + } + + @Option(description = "When using the uber-jar option, this option can be used to " + + "specify one or more entries that should be excluded from the final jar", option = "ignored-entry") + public void setIgnoredEntries(List ignoredEntries) { + this.ignoredEntries.addAll(ignoredEntries); + } + @TaskAction public void buildQuarkus() { getLogger().lifecycle("building quarkus runner"); + final AppArtifact appArtifact = extension().getAppArtifact(); + final AppModel appModel; + final AppModelResolver modelResolver = extension().resolveAppModel(); + try { + appModel = modelResolver.resolveModel(appArtifact); + } catch (AppModelResolverException e) { + throw new GradleException("Failed to resolve application model " + appArtifact + " dependencies", e); + } + try (AppCreator appCreator = AppCreator.builder() // configure the build phases we want the app to go through .addPhase(new AugmentPhase() .setAppClassesDir(extension().outputDirectory().toPath()) + .setConfigDir(extension().outputConfigDirectory().toPath()) .setTransformedClassesDir(getTransformedClassesDirectory().toPath()) .setWiringClassesDir(getWiringClassesDirectory().toPath())) .addPhase(new RunnerJarPhase() .setLibDir(getLibDir().toPath()) .setFinalName(extension().finalName()) .setMainClass(getMainClass()) - .setUberJar(isUberJar())) - .setWorkDir(extension().buildDir().toPath()) + .setUberJar(isUberJar()) + .setUserConfiguredIgnoredEntries(getIgnoredEntries())) + .setWorkDir(getProject().getBuildDir().toPath()) .build()) { - final AppArtifact appArtifact = new AppArtifact(extension().groupId(), extension().artifactId(), - extension().version()); - try { - ResolvedGradleArtifactDeps resolvedGradleArtifactDeps = new ResolvedGradleArtifactDeps(getProject()); - final List appDeps = resolvedGradleArtifactDeps.collectDependencies(appArtifact); - - // push resolved application state - appCreator.pushOutcome(CurateOutcome.builder() - .setAppArtifact(appArtifact) - .setArtifactResolver(resolvedGradleArtifactDeps) - .setInitialDeps(appDeps) - .build()); - - // resolve the outcome we need here - appCreator.resolveOutcome(RunnerJarOutcome.class); - } catch (AppCreatorException e) { - e.printStackTrace(); - } + // push resolved application state + appCreator.pushOutcome(CurateOutcome.builder() + .setAppModelResolver(modelResolver) + .setAppModel(appModel) + .build()); + + // resolve the outcome we need here + appCreator.resolveOutcome(RunnerJarOutcome.class); } catch (AppCreatorException e) { - e.printStackTrace(); + throw new GradleException("Failed to build a runnable JAR", e); } - } } 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 8ace0592c9927..7e9c5aeec151a 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 @@ -15,40 +15,62 @@ */ package io.quarkus.gradle.tasks; +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.ObjectOutputStream; import java.net.InetAddress; import java.net.MalformedURLException; import java.net.Socket; import java.net.URI; import java.net.URL; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; import java.util.List; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.jar.Attributes; import java.util.jar.Manifest; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import org.gradle.api.GradleException; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.artifacts.ResolvedDependency; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.InputDirectory; import org.gradle.api.tasks.Optional; import org.gradle.api.tasks.TaskAction; import org.gradle.api.tasks.options.Option; -import io.quarkus.dev.ClassLoaderCompiler; +import io.quarkus.bootstrap.model.AppArtifact; +import io.quarkus.bootstrap.model.AppDependency; +import io.quarkus.bootstrap.model.AppModel; +import io.quarkus.bootstrap.resolver.AppModelResolver; +import io.quarkus.bootstrap.resolver.AppModelResolverException; +import io.quarkus.deployment.ApplicationInfoUtil; +import io.quarkus.dev.DevModeContext; import io.quarkus.dev.DevModeMain; import io.quarkus.gradle.QuarkusPluginExtension; +import io.quarkus.utilities.JavaBinFinder; /** * @author Ståle Pedersen */ public class QuarkusDev extends QuarkusTask { + private Set filesIncludedInClasspath = new HashSet<>(); + private String debug; - private String buildDir; + private File buildDir; private String sourceDir; @@ -56,12 +78,12 @@ public class QuarkusDev extends QuarkusTask { private boolean preventnoverify = false; - private static final String RESOURCES_PROP = "quarkus.undertow.resources"; - public QuarkusDev() { - super("Creates a native image"); + super("Development mode: enables hot deployment with background compilation"); } + @Optional + @Input public String getDebug() { return debug; } @@ -73,24 +95,24 @@ public String getDebug() { " \"true\" - The JVM is started in debug mode and suspends until a debugger is attached to port 5005\n" + " \"client\" - The JVM is started in client mode, and attempts to connect to localhost:5005\n" + "\"{port}\" - The JVM is started in debug mode and suspends until a debugger is attached to {port}", option = "debug") - @Optional public void setDebug(String debug) { this.debug = debug; } + @InputDirectory + @Optional public File getBuildDir() { if (buildDir == null) - return extension().buildDir(); - else - return new File(buildDir); + buildDir = getProject().getBuildDir(); + return buildDir; } - @Option(description = "Set build directory", option = "build-dir") - @Optional - public void setBuildDir(String buildDir) { + public void setBuildDir(File buildDir) { this.buildDir = buildDir; } + @Optional + @InputDirectory public File getSourceDir() { if (sourceDir == null) return extension().sourceDir(); @@ -99,21 +121,23 @@ public File getSourceDir() { } @Option(description = "Set source directory", option = "source-dir") - @Optional public void setSourceDir(String sourceDir) { this.sourceDir = sourceDir; } + @Optional + @Input public String getJvmArgs() { return jvmArgs; } - @Option(description = "Set debug", option = "jvm-args") - @Optional + @Option(description = "Set JVM arguments", option = "jvm-args") public void setJvmArgs(String jvmArgs) { this.jvmArgs = jvmArgs; } + @Optional + @Input public boolean isPreventnoverify() { return preventnoverify; } @@ -121,7 +145,6 @@ public boolean isPreventnoverify() { @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") - @Optional public void setPreventnoverify(boolean preventnoverify) { this.preventnoverify = preventnoverify; } @@ -135,16 +158,15 @@ public void startDev() { throw new GradleException("The `src/main/java` directory is required, please create it."); } - if (!getBuildDir().isDirectory() || - !new File(getBuildDir(), "classes").isDirectory()) { + if (!extension().outputDirectory().isDirectory()) { throw new GradleException("The project has no output yet, " + "this should not happen as build should have been executed first. " + "Do the project have any source files?"); } - + DevModeContext context = new DevModeContext(); try { List args = new ArrayList<>(); - args.add("java"); + args.add(JavaBinFinder.findBin()); if (getDebug() == null) { // debug mode not specified // make sure 5005 is not used, we don't want to just fail if something else is using it @@ -176,17 +198,6 @@ public void startDev() { if (getJvmArgs() != null) { args.addAll(Arrays.asList(getJvmArgs().split(" "))); } - //Add env to enable quarkus dev mode logging - args.add("-Dquarkus.devMode"); - - for (File f : extension.resourcesDir()) { - File servletRes = new File(f, "META-INF/resources"); - if (servletRes.exists()) { - args.add("-D" + RESOURCES_PROP + "=" + servletRes.getAbsolutePath()); - System.out.println("Using servlet resources " + servletRes.getAbsolutePath()); - break; - } - } // the following flags reduce startup time and are acceptable only for dev purposes args.add("-XX:TieredStopAtLevel=1"); @@ -198,35 +209,30 @@ public void startDev() { //this stuff does not change // Do not include URIs in the manifest, because some JVMs do not like that StringBuilder classPathManifest = new StringBuilder(); - StringBuilder classPath = new StringBuilder(); - for (File f : extension.dependencyFiles()) { - addToClassPaths(classPathManifest, classPath, f); + + final AppModel appModel; + final AppModelResolver modelResolver = extension().resolveAppModel(); + try { + final AppArtifact appArtifact = extension.getAppArtifact(); + appArtifact.setPath(extension.outputDirectory().toPath()); + appModel = modelResolver.resolveModel(appArtifact); + } catch (AppModelResolverException e) { + throw new GradleException("Failed to resolve application model " + extension.getAppArtifact() + " dependencies", + e); } + for (AppDependency appDep : appModel.getAllDependencies()) { + addToClassPaths(classPathManifest, context, appDep.getArtifact().getPath().toFile()); + } + args.add("-Djava.util.logging.manager=org.jboss.logmanager.LogManager"); File wiringClassesDirectory = new File(getBuildDir(), "wiring-classes"); wiringClassesDirectory.mkdirs(); - - addToClassPaths(classPathManifest, classPath, wiringClassesDirectory); + 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 - URL classFile = DevModeMain.class.getClassLoader() - .getResource(DevModeMain.class.getName().replace('.', '/') + ".class"); - File path; - if (classFile.getProtocol().equals("jar")) { - String jarPath = classFile.getPath().substring(0, classFile.getPath().lastIndexOf('!')); - if (jarPath.startsWith("file:")) - jarPath = jarPath.substring(5); - path = new File(jarPath); - } else if (classFile.getProtocol().equals("file")) { - String filePath = classFile.getPath().substring(0, - classFile.getPath().lastIndexOf(DevModeMain.class.getName().replace('.', '/'))); - path = new File(filePath); - } else { - throw new GradleException("Unsupported DevModeMain artifact URL:" + classFile); - } - addToClassPaths(classPathManifest, classPath, path); + addGradlePluginDeps(classPathManifest, context); //now we need to build a temporary jar to actually run @@ -234,6 +240,19 @@ public void startDev() { tempFile.delete(); tempFile.deleteOnExit(); + StringBuilder resources = new StringBuilder(); + String res = null; + for (File file : extension.resourcesDir()) { + if (resources.length() > 0) + resources.append(File.pathSeparator); + resources.append(file.getAbsolutePath()); + res = file.getAbsolutePath(); + } + DevModeContext.ModuleInfo moduleInfo = new DevModeContext.ModuleInfo(getProject().getName(), + getSourceDir().getAbsolutePath(), + extension.outputDirectory().getAbsolutePath(), res); + context.getModules().add(moduleInfo); + try (ZipOutputStream out = new ZipOutputStream(new FileOutputStream(tempFile))) { out.putNextEntry(new ZipEntry("META-INF/")); Manifest manifest = new Manifest(); @@ -243,39 +262,32 @@ public void startDev() { out.putNextEntry(new ZipEntry("META-INF/MANIFEST.MF")); manifest.write(out); - out.putNextEntry(new ZipEntry(ClassLoaderCompiler.DEV_MODE_CLASS_PATH)); - out.write(classPath.toString().getBytes(StandardCharsets.UTF_8)); - } - StringBuilder resources = new StringBuilder(); - for (File file : extension.resourcesDir()) { - if (resources.length() > 0) - resources.append(File.pathSeparator); - resources.append(file.getAbsolutePath()); + out.putNextEntry(new ZipEntry(DevModeMain.DEV_MODE_CONTEXT)); + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + ObjectOutputStream obj = new ObjectOutputStream(new DataOutputStream(bytes)); + obj.writeObject(context); + obj.close(); + out.write(bytes.toByteArray()); } extension.outputDirectory().mkdirs(); + ApplicationInfoUtil.writeApplicationInfoProperties(appModel.getAppArtifact(), extension.outputDirectory().toPath()); - args.add("-Dquarkus.runner.classes=" + extension.outputDirectory().getAbsolutePath()); - args.add("-Dquarkus.runner.sources=" + getSourceDir().getAbsolutePath()); - if (resources != null) { - args.add("-Dquarkus.runner.resources=" + resources.toString()); - } args.add("-jar"); args.add(tempFile.getAbsolutePath()); args.add(extension.outputDirectory().getAbsolutePath()); args.add(wiringClassesDirectory.getAbsolutePath()); args.add(new File(getBuildDir(), "transformer-cache").getAbsolutePath()); ProcessBuilder pb = new ProcessBuilder(args.toArray(new String[0])); - pb.redirectError(ProcessBuilder.Redirect.INHERIT); - pb.redirectOutput(ProcessBuilder.Redirect.INHERIT); + pb.redirectErrorStream(true); pb.redirectInput(ProcessBuilder.Redirect.INHERIT); pb.directory(extension.outputDirectory()); System.out.println("Starting process: "); pb.command().forEach(System.out::println); System.out.println("Args: "); args.forEach(System.out::println); - Process p = pb.start(); + Process p = pb.start(); Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { @Override public void run() { @@ -283,6 +295,9 @@ public void run() { } }, "Development Mode Shutdown Hook")); try { + ExecutorService es = Executors.newSingleThreadExecutor(); + es.submit(() -> copyOutputToConsole(p.getInputStream())); + p.waitFor(); } catch (Exception e) { p.destroy(); @@ -294,17 +309,50 @@ public void run() { } } - private void addToClassPaths(StringBuilder classPathManifest, StringBuilder classPath, File file) - throws MalformedURLException { - URI uri = file.toPath().toAbsolutePath().toUri(); - classPathManifest.append(uri.getPath()); - classPath.append(uri.toURL().toString()); - if (file.isDirectory()) { - classPathManifest.append("/"); - classPath.append("/"); + private void copyOutputToConsole(InputStream is) { + try (InputStreamReader isr = new InputStreamReader(is); + BufferedReader br = new BufferedReader(isr)) { + String line; + while ((line = br.readLine()) != null) { + System.out.println(line); + } + } catch (Exception e) { + throw new GradleException("Failed to copy output to console", e); + } + } + + 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); + + URI uri = file.toPath().toAbsolutePath().toUri(); + classPathManifest.append(uri.getPath()); + context.getClassPath().add(toUrl(uri)); + if (file.isDirectory()) { + classPathManifest.append("/"); + } + classPathManifest.append(" "); + } + } + + private URL toUrl(URI uri) { + try { + return uri.toURL(); + } catch (MalformedURLException e) { + throw new IllegalStateException("Failed to convert URI to URL: " + uri, e); } - classPathManifest.append(" "); - classPath.append(" "); } } 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 8cbdd2094435e..f4f956bdf7a76 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 @@ -15,18 +15,17 @@ */ package io.quarkus.gradle.tasks; -import java.io.File; import java.nio.file.Path; import java.util.List; import org.gradle.api.GradleException; +import org.gradle.api.tasks.Input; import org.gradle.api.tasks.Optional; import org.gradle.api.tasks.TaskAction; import org.gradle.api.tasks.options.Option; import io.quarkus.creator.AppCreator; import io.quarkus.creator.AppCreatorException; -import io.quarkus.creator.AppDependency; import io.quarkus.creator.phase.augment.AugmentOutcome; import io.quarkus.creator.phase.nativeimage.NativeImageOutcome; import io.quarkus.creator.phase.nativeimage.NativeImagePhase; @@ -37,8 +36,6 @@ */ public class QuarkusNative extends QuarkusTask { - private String wiringClassesDirectory; - private boolean reportErrorsAtRuntime = false; private boolean debugSymbols = false; @@ -71,10 +68,16 @@ public class QuarkusNative extends QuarkusTask { private String nativeImageXmx; + private String containerRuntime = "docker"; + + private String containerRuntimeOptions; + private String dockerBuild; private boolean enableVMInspection = false; + private boolean enableFallbackImages = false; + private boolean fullStackTraces = true; private boolean disableReports; @@ -87,129 +90,142 @@ public QuarkusNative() { super("Building a native image"); } - public File getWiringClassesDirectory() { - if (wiringClassesDirectory == null) - return extension().wiringClassesDirectory(); - else - return new File(wiringClassesDirectory); - } - - @Option(description = "The directory for classes generated by processing", option = "wiring-classes-directory") @Optional - public void setWiringClassesDirectory(String wiringClassesDirectory) { - this.wiringClassesDirectory = wiringClassesDirectory; - } - + @Input 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") - @Optional public void setAddAllCharsets(final boolean addAllCharsets) { this.addAllCharsets = addAllCharsets; } + @Optional + @Input public boolean isReportErrorsAtRuntime() { return reportErrorsAtRuntime; } @Option(description = "Report errors at runtime", option = "report-errors-runtime") - @Optional public void setReportErrorsAtRuntime(boolean reportErrorsAtRuntime) { this.reportErrorsAtRuntime = reportErrorsAtRuntime; } + @Optional + @Input public boolean isDebugSymbols() { return debugSymbols; } @Option(description = "Specify if debug symbols should be set", option = "debug-symbols") - @Optional public void setDebugSymbols(boolean debugSymbols) { this.debugSymbols = debugSymbols; } + @Optional + @Input public boolean isDebugBuildProcess() { return debugBuildProcess; } @Option(description = "Specify if debug is set during build process", option = "debug-build-process") - @Optional public void setDebugBuildProcess(boolean debugBuildProcess) { this.debugBuildProcess = debugBuildProcess; } + @Optional + @Input public boolean isCleanupServer() { return cleanupServer; } @Option(description = "Cleanup server", option = "cleanup-server") - @Optional public void setCleanupServer(boolean cleanupServer) { this.cleanupServer = cleanupServer; } + @Optional + @Input public boolean isEnableHttpUrlHandler() { return enableHttpUrlHandler; } - @Option(description = "Specify if http url handler is enabled", option = "enable-http-url-handler") @Optional + @Input + 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) { + this.enableFallbackImages = enableFallbackImages; + } + + @Option(description = "Specify if http url handler is enabled", option = "enable-http-url-handler") public void setEnableHttpUrlHandler(boolean enableHttpUrlHandler) { this.enableHttpUrlHandler = enableHttpUrlHandler; } + @Optional + @Input public boolean isEnableHttpsUrlHandler() { return enableHttpsUrlHandler; } @Option(description = "Specify if https url handler is enabled", option = "enable-https-url-handler") - @Optional public void setEnableHttpsUrlHandler(boolean enableHttpsUrlHandler) { this.enableHttpsUrlHandler = enableHttpsUrlHandler; } + @Optional + @Input public boolean isEnableAllSecurityServices() { return enableAllSecurityServices; } @Option(description = "Enable all security services", option = "enable-all-security-services") - @Optional public void setEnableAllSecurityServices(boolean enableAllSecurityServices) { this.enableAllSecurityServices = enableAllSecurityServices; } + @Optional + @Input public boolean isEnableRetainedHeapReporting() { return enableRetainedHeapReporting; } @Option(description = "Specify if retained heap reporting should be enabled", option = "enable-retained-heap-reporting") - @Optional public void setEnableRetainedHeapReporting(boolean enableRetainedHeapReporting) { this.enableRetainedHeapReporting = enableRetainedHeapReporting; } + @Optional + @Input public boolean isEnableIsolates() { return enableIsolates; } @Option(description = "Report errors at runtime", option = "enable-isolates") - @Optional public void setEnableIsolates(boolean enableIsolates) { this.enableIsolates = enableIsolates; } + @Optional + @Input public boolean isEnableCodeSizeReporting() { return enableCodeSizeReporting; } @Option(description = "Report errors at runtime", option = "enable-code-size-reporting") - @Optional public void setEnableCodeSizeReporting(boolean enableCodeSizeReporting) { this.enableCodeSizeReporting = enableCodeSizeReporting; } + @Optional + @Input public String getGraalvmHome() { if (graalvmHome == null || graalvmHome.length() < 1) throw new GradleException( @@ -218,107 +234,140 @@ public String getGraalvmHome() { } @Option(description = "Specify the GraalVM directory (default to $GRAALVM_HOME)", option = "graalvm") - @Optional public void setGraalvmHome(String graalvmHome) { this.graalvmHome = graalvmHome; } + @Optional + @Input public boolean isEnableServer() { return enableServer; } @Option(description = "Enable server", option = "enable-server") - @Optional public void setEnableServer(boolean enableServer) { this.enableServer = enableServer; } + @Optional + @Input public boolean isEnableJni() { return enableJni; } @Option(description = "Enable jni", option = "enable-jni") - @Optional public void setEnableJni(boolean enableJni) { this.enableJni = enableJni; } + @Optional + @Input public boolean isAutoServiceLoaderRegistration() { return autoServiceLoaderRegistration; } @Option(description = "Auto ServiceLoader registration", option = "auto-serviceloader-registration") - @Optional public void setAutoServiceLoaderRegistration(boolean autoServiceLoaderRegistration) { this.autoServiceLoaderRegistration = autoServiceLoaderRegistration; } + @Optional + @Input public boolean isDumpProxies() { return dumpProxies; } @Option(description = "Dump proxies", option = "dump-proxies") - @Optional public void setDumpProxies(boolean dumpProxies) { this.dumpProxies = dumpProxies; } + @Optional + @Input public String getNativeImageXmx() { return nativeImageXmx; } @Option(description = "Specify the native image maximum heap size", option = "native-image-xmx") - @Optional public void setNativeImageXmx(String nativeImageXmx) { this.nativeImageXmx = nativeImageXmx; } + @Optional + @Input + public String getContainerRuntime() { + return containerRuntime; + } + + @Optional + @Input + public String getContainerRuntimeOptions() { + return containerRuntimeOptions; + } + + @Optional + @Input public String getDockerBuild() { return dockerBuild; } - @Option(description = "Docker build", option = "docker-build") + @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; + } + + @Option(description = "Docker build", option = "docker-build") public void setDockerBuild(String dockerBuild) { this.dockerBuild = dockerBuild; } + @Optional + @Input public boolean isEnableVMInspection() { return enableVMInspection; } @Option(description = "Enable VM inspection", option = "enable-vm-inspection") - @Optional public void setEnableVMInspection(boolean enableVMInspection) { this.enableVMInspection = enableVMInspection; } + @Optional + @Input public boolean isFullStackTraces() { return fullStackTraces; } @Option(description = "Specify full stacktraces", option = "full-stacktraces") - @Optional public void setFullStackTraces(boolean fullStackTraces) { this.fullStackTraces = fullStackTraces; } + @Optional + @Input public boolean isDisableReports() { return disableReports; } @Option(description = "Disable reports", option = "disable-reports") - @Optional public void setDisableReports(boolean disableReports) { this.disableReports = disableReports; } + @Optional + @Input public List getAdditionalBuildArgs() { return additionalBuildArgs; } @Option(description = "Additional build arguments", option = "additional-build-args") - @Optional public void setAdditionalBuildArgs(List additionalBuildArgs) { this.additionalBuildArgs = additionalBuildArgs; } @@ -332,11 +381,13 @@ public void buildNative() { .setAddAllCharsets(addAllCharsets) .setAdditionalBuildArgs(getAdditionalBuildArgs()) .setAutoServiceLoaderRegistration(isAutoServiceLoaderRegistration()) - .setOutputDir(extension().buildDir().toPath()) + .setOutputDir(getProject().getBuildDir().toPath()) .setCleanupServer(isCleanupServer()) .setDebugBuildProcess(isDebugBuildProcess()) .setDebugSymbols(isDebugSymbols()) .setDisableReports(isDisableReports()) + .setContainerRuntime(getContainerRuntime()) + .setContainerRuntimeOptions(getContainerRuntimeOptions()) .setDockerBuild(getDockerBuild()) .setDumpProxies(isDumpProxies()) .setEnableAllSecurityServices(isEnableAllSecurityServices()) @@ -348,6 +399,7 @@ public void buildNative() { .setEnableRetainedHeapReporting(isEnableRetainedHeapReporting()) .setEnableServer(isEnableServer()) .setEnableVMInspection(isEnableVMInspection()) + .setEnableFallbackImages(isEnableFallbackImages()) .setFullStackTraces(isFullStackTraces()) .setGraalvmHome(getGraalvmHome()) .setNativeImageXmx(getNativeImageXmx()) @@ -376,12 +428,12 @@ public Path getWiringClassesDir() { } @Override - public boolean isWhitelisted(AppDependency dep) { - // not relevant for this mojo - throw new UnsupportedOperationException(); + public Path getConfigDir() { + return extension().outputConfigDirectory().toPath(); } }).pushOutcome(RunnerJarOutcome.class, new RunnerJarOutcome() { - final Path runnerJar = extension().buildDir().toPath().resolve(extension().finalName() + "-runner.jar"); + final Path runnerJar = getProject().getBuildDir().toPath().resolve(extension().finalName() + "-runner.jar"); + final Path originalJar = getProject().getBuildDir().toPath().resolve(extension().finalName() + ".jar"); @Override public Path getRunnerJar() { @@ -392,6 +444,11 @@ public Path getRunnerJar() { public Path getLibDir() { return runnerJar.getParent().resolve("lib"); } + + @Override + public Path getOriginalJar() { + return originalJar; + } }).resolveOutcome(NativeImageOutcome.class); } catch (AppCreatorException e) { diff --git a/devtools/gradle/src/main/resources/META-INF/gradle-plugins/io.quarkus.gradle.plugin.properties b/devtools/gradle/src/main/resources/META-INF/gradle-plugins/io.quarkus.gradle.plugin.properties deleted file mode 100644 index 3a619df8e4331..0000000000000 --- a/devtools/gradle/src/main/resources/META-INF/gradle-plugins/io.quarkus.gradle.plugin.properties +++ /dev/null @@ -1 +0,0 @@ -implementation-class=io.quarkus.gradle.QuarkusPlugin 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 9d0867249baec..101c1b647bf03 100644 --- a/devtools/gradle/src/test/java/io/quarkus/gradle/QuarkusPluginTest.java +++ b/devtools/gradle/src/test/java/io/quarkus/gradle/QuarkusPluginTest.java @@ -1,22 +1,49 @@ package io.quarkus.gradle; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; import org.gradle.api.Project; +import org.gradle.api.tasks.TaskContainer; import org.gradle.testfixtures.ProjectBuilder; import org.junit.jupiter.api.Test; public class QuarkusPluginTest { @Test - public void quarkusTest() { + public void shouldCreateTasks() { Project project = ProjectBuilder.builder().build(); - project.getPluginManager().apply("io.quarkus.gradle.plugin"); + project.getPluginManager().apply("io.quarkus"); - assertTrue(project.getPluginManager().hasPlugin("io.quarkus.gradle.plugin")); + assertTrue(project.getPluginManager().hasPlugin("io.quarkus")); - assertNotNull(project.getTasks().getByName("quarkus-build")); + TaskContainer tasks = project.getTasks(); + assertNotNull(tasks.getByName("quarkusBuild")); + assertNotNull(tasks.getByName("quarkusDev")); + assertNotNull(tasks.getByName("buildNative")); + assertNotNull(tasks.getByName("listExtensions")); + assertNotNull(tasks.getByName("addExtension")); } + @Test + public void shouldMakeAssembleDependOnQuarkusBuild() { + Project project = ProjectBuilder.builder().build(); + project.getPluginManager().apply("io.quarkus"); + project.getPluginManager().apply("base"); + + TaskContainer tasks = project.getTasks(); + + assertTrue(tasks.getByName("assemble").getDependsOn().contains(tasks.getByName("quarkusBuild"))); + } + + @Test + public void shouldMakeQuarkusDevAndQuarkusBuildDependOnClassesTask() { + Project project = ProjectBuilder.builder().build(); + project.getPluginManager().apply("io.quarkus"); + project.getPluginManager().apply("java"); + + TaskContainer tasks = project.getTasks(); + + assertTrue(tasks.getByName("quarkusBuild").getDependsOn().contains(tasks.getByName("classes"))); + assertTrue(tasks.getByName("quarkusDev").getDependsOn().contains(tasks.getByName("classes"))); + } } diff --git a/devtools/maven/pom.xml b/devtools/maven/pom.xml index b1c6ee5954541..f76b7df4e4b93 100644 --- a/devtools/maven/pom.xml +++ b/devtools/maven/pom.xml @@ -34,6 +34,10 @@ ${project.version} + + io.quarkus + quarkus-bootstrap-core + io.quarkus quarkus-devtools-common @@ -71,7 +75,13 @@ --> io.quarkus - quarkus-kotlin + quarkus-kotlin-deployment + test + + + + io.quarkus + quarkus-undertow-websockets-deployment test @@ -114,22 +124,33 @@ jackson-databind + + + org.twdata.maven + mojo-executor + 2.3.0 + + 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.shared @@ -179,7 +200,6 @@ org.apache.maven.plugins maven-plugin-plugin - 3.5.2 quarkus true diff --git a/devtools/maven/src/main/java/io/quarkus/maven/AddExtensionMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/AddExtensionMojo.java index 70c69c088e30f..354fb417c6989 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/AddExtensionMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/AddExtensionMojo.java @@ -1,5 +1,6 @@ package io.quarkus.maven; +import java.io.File; import java.io.IOException; import java.util.HashSet; import java.util.Set; @@ -14,7 +15,14 @@ import org.apache.maven.project.MavenProject; import io.quarkus.cli.commands.AddExtensions; +import io.quarkus.cli.commands.writer.FileProjectWriter; +/** + * Allow adding an extension to an existing pom.xml file. + * Because you can add one or several extension in one go, there are 2 mojos: + * {@code add-extensions} and {@code add-extension}. Both supports the {@code extension} and {@code extensions} + * parameters. + */ @Mojo(name = "add-extension") public class AddExtensionMojo extends AbstractMojo { @@ -50,11 +58,10 @@ public void execute() throws MojoExecutionException { ext.add(extension); } - getLog().info("FOO " + ext); - try { Model model = project.getOriginalModel().clone(); - new AddExtensions(model.getPomFile()) + File pomFile = new File(model.getPomFile().getAbsolutePath()); + new AddExtensions(new FileProjectWriter(pomFile.getParentFile()), pomFile.getName()) .addExtensions(ext.stream().map(String::trim).collect(Collectors.toSet())); } catch (IOException e) { throw new MojoExecutionException("Unable to update the pom.xml file", e); diff --git a/devtools/maven/src/main/java/io/quarkus/maven/AddExtensionsMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/AddExtensionsMojo.java index bb18c58af7f62..0a1bd7f1afd0d 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/AddExtensionsMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/AddExtensionsMojo.java @@ -3,7 +3,7 @@ import org.apache.maven.plugins.annotations.Mojo; /** - * Allow adding an extension to an existing pom.xml file. + * Allow adding extensions to an existing pom.xml file. * Because you can add one or several extension in one go, there are 2 mojos: * {@code add-extensions} and {@code add-extension}. Both supports the {@code extension} and {@code extensions} * parameters. 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 71f7888d607ae..057f93f0863bf 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/AnalyseCallTreeMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/AnalyseCallTreeMojo.java @@ -26,6 +26,11 @@ import io.quarkus.creator.phase.nativeimage.ReportAnalyzer; +/** + * Analyze call tree of a method or a class based on an existing report produced by Substrate when using + * -H:+PrintAnalysisCallTree, + * and does a more meaningful analysis of what is causing a type to be retained. + */ @Mojo(name = "analyze-call-tree") public class AnalyseCallTreeMojo extends AbstractMojo { 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 6ddef193b3145..572aaf8b3e557 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/BuildMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/BuildMojo.java @@ -18,8 +18,11 @@ package io.quarkus.maven; import java.io.File; +import java.util.Arrays; +import java.util.Collections; import java.util.List; +import org.apache.maven.artifact.Artifact; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugins.annotations.Component; @@ -28,21 +31,27 @@ import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.project.MavenProject; +import org.apache.maven.project.MavenProjectHelper; import org.eclipse.aether.RepositorySystem; import org.eclipse.aether.RepositorySystemSession; import org.eclipse.aether.repository.RemoteRepository; -import io.quarkus.creator.AppArtifact; +import io.quarkus.bootstrap.model.AppArtifact; +import io.quarkus.bootstrap.model.AppModel; +import io.quarkus.bootstrap.resolver.AppModelResolverException; +import io.quarkus.bootstrap.resolver.BootstrapAppModelResolver; +import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver; import io.quarkus.creator.AppCreator; import io.quarkus.creator.AppCreatorException; -import io.quarkus.creator.AppDependency; import io.quarkus.creator.phase.augment.AugmentPhase; import io.quarkus.creator.phase.curate.CurateOutcome; import io.quarkus.creator.phase.runnerjar.RunnerJarOutcome; import io.quarkus.creator.phase.runnerjar.RunnerJarPhase; -import io.quarkus.creator.resolver.maven.ResolvedMavenArtifactDeps; /** + * Build the application. + *

+ * You can build a native application runner with {@code native-image} * * @author Alexey Loubyansky */ @@ -57,6 +66,9 @@ public class BuildMojo extends AbstractMojo { @Component private RepositorySystem repoSystem; + @Component + private MavenProjectHelper projectHelper; + /** * The current repository/network configuration of Maven. * @@ -107,6 +119,7 @@ public class BuildMojo extends AbstractMojo { @Parameter(defaultValue = "${project.build.directory}") private File buildDir; + /** * The directory for library jars */ @@ -122,42 +135,81 @@ public class BuildMojo extends AbstractMojo { @Parameter(defaultValue = "true") private boolean useStaticInit; - @Parameter(defaultValue = "false") + @Parameter(property = "uberJar", defaultValue = "false") private boolean uberJar; + /** + * When using the uberJar option, this array specifies entries that should + * be excluded from the final jar. The entries are relative to the root of + * the file. An example of this configuration could be: + *

+     * <configuration>
+     *   <uberJar>true</uberJar>
+     *   <ignoredEntries>
+     *     <ignoredEntry>META-INF/BC2048KE.SF</ignoredEntry>
+     *     <ignoredEntry>META-INF/BC2048KE.DSA</ignoredEntry>
+     *     <ignoredEntry>META-INF/BC1024KE.SF</ignoredEntry>
+     *     <ignoredEntry>META-INF/BC1024KE.DSA</ignoredEntry>
+     *   </ignoredEntries>
+     * </configuration>
+     * 
+ */ + @Parameter(property = "ignoredEntries") + private String[] ignoredEntries; + public BuildMojo() { MojoLogger.logSupplier = this::getLog; } @Override public void execute() throws MojoExecutionException { + final Artifact projectArtifact = project.getArtifact(); + final AppArtifact appArtifact = new AppArtifact(projectArtifact.getGroupId(), projectArtifact.getArtifactId(), + projectArtifact.getClassifier(), projectArtifact.getType(), projectArtifact.getVersion()); + final AppModel appModel; + final BootstrapAppModelResolver modelResolver; + try { + modelResolver = new BootstrapAppModelResolver( + MavenArtifactResolver.builder() + .setRepositorySystem(repoSystem) + .setRepositorySystemSession(repoSession) + .setRemoteRepositories(repos) + .build()); + appModel = modelResolver.resolveModel(appArtifact); + } catch (AppModelResolverException e) { + throw new MojoExecutionException("Failed to resolve application model " + appArtifact + " dependencies", e); + } try (AppCreator appCreator = AppCreator.builder() // configure the build phases we want the app to go through .addPhase(new AugmentPhase() .setAppClassesDir(outputDirectory.toPath()) + .setConfigDir(outputDirectory.toPath()) .setTransformedClassesDir(transformedClassesDirectory.toPath()) .setWiringClassesDir(wiringClassesDirectory.toPath())) .addPhase(new RunnerJarPhase() .setLibDir(libDir.toPath()) .setFinalName(finalName) .setMainClass(mainClass) - .setUberJar(uberJar)) + .setUberJar(uberJar) + .setUserConfiguredIgnoredEntries( + this.ignoredEntries == null ? Collections.emptySet() : Arrays.asList(this.ignoredEntries))) .setWorkDir(buildDir.toPath()) .build()) { - final AppArtifact appArtifact = new AppArtifact(project.getGroupId(), project.getArtifactId(), - project.getVersion()); - final List appDeps = new ResolvedMavenArtifactDeps(project.getGroupId(), project.getArtifactId(), - project.getVersion(), project.getArtifacts()).collectDependencies(appArtifact); - // push resolved application state appCreator.pushOutcome(CurateOutcome.builder() - .setAppArtifact(appArtifact) - .setInitialDeps(appDeps) + .setAppModelResolver(modelResolver) + .setAppModel(appModel) .build()); // resolve the outcome we need here - appCreator.resolveOutcome(RunnerJarOutcome.class); + RunnerJarOutcome result = appCreator.resolveOutcome(RunnerJarOutcome.class); + Artifact original = project.getArtifact(); + original.setFile(result.getOriginalJar().toFile()); + if (uberJar) { + projectHelper.attachArtifact(project, result.getRunnerJar().toFile(), "runner"); + } + } catch (AppCreatorException e) { throw new MojoExecutionException("Failed to build a runnable JAR", e); } 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 3cc1afb700fc2..ae99f54912cb3 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/CreateProjectMojo.java @@ -18,6 +18,16 @@ package io.quarkus.maven; import static org.fusesource.jansi.Ansi.ansi; +import static org.twdata.maven.mojoexecutor.MojoExecutor.artifactId; +import static org.twdata.maven.mojoexecutor.MojoExecutor.configuration; +import static org.twdata.maven.mojoexecutor.MojoExecutor.element; +import static org.twdata.maven.mojoexecutor.MojoExecutor.executeMojo; +import static org.twdata.maven.mojoexecutor.MojoExecutor.executionEnvironment; +import static org.twdata.maven.mojoexecutor.MojoExecutor.goal; +import static org.twdata.maven.mojoexecutor.MojoExecutor.groupId; +import static org.twdata.maven.mojoexecutor.MojoExecutor.name; +import static org.twdata.maven.mojoexecutor.MojoExecutor.plugin; +import static org.twdata.maven.mojoexecutor.MojoExecutor.version; import java.io.File; import java.io.IOException; @@ -27,21 +37,27 @@ import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; +import org.apache.maven.execution.DefaultMavenExecutionRequest; +import org.apache.maven.execution.MavenExecutionRequest; import org.apache.maven.execution.MavenSession; import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.BuildPluginManager; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugins.annotations.Component; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.project.DefaultProjectBuildingRequest; import org.apache.maven.project.MavenProject; +import org.apache.maven.project.ProjectBuilder; import org.fusesource.jansi.Ansi; -import io.quarkus.SourceType; import io.quarkus.cli.commands.AddExtensions; import io.quarkus.cli.commands.CreateProject; +import io.quarkus.cli.commands.writer.FileProjectWriter; import io.quarkus.maven.components.MavenVersionEnforcer; import io.quarkus.maven.components.Prompter; import io.quarkus.maven.utilities.MojoUtils; +import io.quarkus.templates.SourceType; /** * This goal helps in setting up Quarkus Maven project with quarkus-maven-plugin, with sensible defaults @@ -83,6 +99,12 @@ public class CreateProjectMojo extends AbstractMojo { @Component private MavenVersionEnforcer mavenVersionEnforcer; + @Component + private BuildPluginManager pluginManager; + + @Component + private ProjectBuilder projectBuilder; + @Override public void execute() throws MojoExecutionException { // We detect the Maven version during the project generation to indicate the user immediately that the installed @@ -119,23 +141,28 @@ public void execute() throws MojoExecutionException { boolean success; try { - sanitizeOptions(); + final SourceType sourceType = CreateProject.determineSourceType(extensions); + sanitizeOptions(sourceType); final Map context = new HashMap<>(); - context.put("className", className); context.put("path", path); - success = new CreateProject(projectRoot) + success = new CreateProject(new FileProjectWriter(projectRoot)) .groupId(projectGroupId) .artifactId(projectArtifactId) .version(projectVersion) - .sourceType(determineSourceType(extensions)) + .sourceType(sourceType) + .className(className) .doCreateProject(context); + File createdPomFile = new File(projectRoot, "pom.xml"); if (success) { - new AddExtensions(new File(projectRoot, "pom.xml")) + File pomFile = new File(createdPomFile.getAbsolutePath()); + new AddExtensions(new FileProjectWriter(pomFile.getParentFile()), pomFile.getName()) .addExtensions(extensions); } + + createMavenWrapper(createdPomFile); } catch (IOException e) { throw new MojoExecutionException(e.getMessage(), e); } @@ -144,10 +171,36 @@ public void execute() throws MojoExecutionException { } } - private SourceType determineSourceType(Set extensions) { - return extensions.stream().anyMatch(e -> e.toLowerCase().contains("kotlin")) - ? SourceType.KOTLIN - : SourceType.JAVA; + private void createMavenWrapper(File createdPomFile) { + 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 + MavenProject newProject = projectBuilder.build( + createdPomFile, new DefaultProjectBuildingRequest(session.getProjectBuildingRequest())).getProject(); + + MavenExecutionRequest newExecutionRequest = DefaultMavenExecutionRequest.copy(session.getRequest()); + newExecutionRequest.setBaseDirectory(createdPomFile.getParentFile()); + + MavenSession newSession = new MavenSession(session.getContainer(), session.getRepositorySession(), + newExecutionRequest, session.getResult()); + newSession.setCurrentProject(newProject); + + executeMojo( + plugin( + groupId("io.takari"), + artifactId("maven"), + version(MojoUtils.getMavenWrapperVersion())), + goal("wrapper"), + configuration( + element(name("maven"), MojoUtils.getProposedMavenVersion())), + executionEnvironment( + newProject, + newSession, + pluginManager)); + } catch (Exception e) { + // no reason to fail if the wrapper could not be created + getLog().error("Unable to install the Maven wrapper (./mvnw) in the project", e); + } } private void askTheUserForMissingValues() throws MojoExecutionException { @@ -180,7 +233,7 @@ private void askTheUserForMissingValues() throws MojoExecutionException { } if (StringUtils.isBlank(projectVersion)) { - projectVersion = prompter.promptWithDefaultValue("Set the Quarkus version", + projectVersion = prompter.promptWithDefaultValue("Set the project version", "1.0-SNAPSHOT"); } @@ -219,14 +272,10 @@ private boolean isTrueOrYes(String answer) { return "true".equalsIgnoreCase(content) || "yes".equalsIgnoreCase(content) || "y".equalsIgnoreCase(content); } - private void sanitizeOptions() { + private void sanitizeOptions(SourceType sourceType) { // If className is null, we won't create the REST resource, if (className != null) { - if (className.endsWith(MojoUtils.JAVA_EXTENSION)) { - className = className.substring(0, className.length() - MojoUtils.JAVA_EXTENSION.length()); - } else if (className.endsWith(MojoUtils.KOTLIN_EXTENSION)) { - className = className.substring(0, className.length() - MojoUtils.KOTLIN_EXTENSION.length()); - } + className = sourceType.stripExtensionFrom(className); if (!className.contains(".")) { // No package name, inject one 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 7bb34616e305c..d5bdf56c0c737 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java @@ -16,15 +16,21 @@ package io.quarkus.maven; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.io.ObjectOutputStream; import java.net.InetAddress; import java.net.MalformedURLException; import java.net.Socket; import java.net.URI; import java.net.URL; +import java.net.URLDecoder; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -34,11 +40,7 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; -import org.apache.maven.artifact.Artifact; import org.apache.maven.execution.MavenSession; -import org.apache.maven.model.Plugin; -import org.apache.maven.model.PluginExecution; -import org.apache.maven.model.Resource; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; @@ -48,23 +50,31 @@ import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.project.MavenProject; -import org.apache.maven.toolchain.Toolchain; import org.apache.maven.toolchain.ToolchainManager; - -import io.quarkus.dev.ClassLoaderCompiler; +import org.eclipse.aether.RepositorySystem; +import org.eclipse.aether.RepositorySystemSession; +import org.eclipse.aether.repository.RemoteRepository; + +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.workspace.LocalProject; +import io.quarkus.deployment.ApplicationInfoUtil; +import io.quarkus.dev.DevModeContext; import io.quarkus.dev.DevModeMain; import io.quarkus.maven.components.MavenVersionEnforcer; import io.quarkus.maven.utilities.MojoUtils; +import io.quarkus.utilities.JavaBinFinder; /** - * The dev mojo, that runs a quarkus app in a forked process + * The dev mojo, that runs a quarkus app in a forked process. A background compilation process is launched and any changes are + * automatically reflected in your running application. *

+ * You can use this dev mode in a remote container environment with {@code remote-dev}. */ @Mojo(name = "dev", defaultPhase = LifecyclePhase.PREPARE_PACKAGE, requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME) public class DevMojo extends AbstractMojo { - - private static final String RESOURCES_PROP = "quarkus.undertow.resources"; - /** * The directory for compiled classes. */ @@ -115,9 +125,21 @@ public class DevMojo extends AbstractMojo { @Parameter(defaultValue = "${session}") private MavenSession session; + @Parameter(defaultValue = "TRUE") + private boolean deleteDevJar; + @Component private MavenVersionEnforcer mavenVersionEnforcer; + @Component + private RepositorySystem repoSystem; + + @Parameter(defaultValue = "${repositorySystemSession}", readonly = true) + private RepositorySystemSession repoSession; + + @Parameter(defaultValue = "${project.remoteProjectRepositories}", readonly = true, required = true) + private List repos; + /** * This 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) @@ -139,18 +161,8 @@ public MavenSession getSession() { @Override public void execute() throws MojoFailureException, MojoExecutionException { mavenVersionEnforcer.ensureMavenVersion(getLog(), session); - boolean found = false; - for (Plugin i : project.getBuildPlugins()) { - if (i.getGroupId().equals(MojoUtils.getPluginGroupId()) - && i.getArtifactId().equals(MojoUtils.getPluginArtifactId())) { - for (PluginExecution p : i.getExecutions()) { - if (p.getGoals().contains("build")) { - found = true; - break; - } - } - } - } + boolean found = MojoUtils.checkProjectForMavenBuildPlugin(project); + if (!found) { getLog().warn("The quarkus-maven-plugin build goal was not configured for this project, " + "skipping quarkus:dev as this is assumed to be a support library. If you want to run quarkus dev" + @@ -159,7 +171,7 @@ public void execute() throws MojoFailureException, MojoExecutionException { } if (!sourceDir.isDirectory()) { - throw new MojoFailureException("The `src/main/java` directory is required, please create it."); + getLog().warn("The `src/main/java` directory does not exist"); } if (!buildDir.isDirectory() || !new File(buildDir, "classes").isDirectory()) { @@ -168,8 +180,8 @@ public void execute() throws MojoFailureException, MojoExecutionException { try { List args = new ArrayList<>(); - String javaTool = findJavaTool(); - getLog().info("Using javaTool: " + javaTool); + String javaTool = JavaBinFinder.findBin(); + getLog().debug("Using javaTool: " + javaTool); args.add(javaTool); if (debug == null) { // debug mode not specified @@ -202,24 +214,6 @@ public void execute() throws MojoFailureException, MojoExecutionException { if (jvmArgs != null) { args.addAll(Arrays.asList(jvmArgs.split(" "))); } - //we don't want to just copy every system property, as a lot of them are set by the JVM - for (Map.Entry i : System.getProperties().entrySet()) { - if (i.getKey().toString().startsWith("quarkus.")) { - args.add("-D" + i.getKey() + "=" + i.getValue()); - } - } - //Add env to enable quarkus dev mode logging - args.add("-Dquarkus.devMode"); - - for (Resource r : project.getBuild().getResources()) { - File f = new File(r.getDirectory()); - File servletRes = new File(f, "META-INF/resources"); - if (servletRes.exists()) { - args.add("-D" + RESOURCES_PROP + "=" + servletRes.getAbsolutePath()); - getLog().info("Using servlet resources " + servletRes.getAbsolutePath()); - break; - } - } // the following flags reduce startup time and are acceptable only for dev purposes args.add("-XX:TieredStopAtLevel=1"); @@ -231,15 +225,63 @@ public void execute() throws MojoFailureException, MojoExecutionException { //this stuff does not change // Do not include URIs in the manifest, because some JVMs do not like that StringBuilder classPathManifest = new StringBuilder(); - StringBuilder classPath = new StringBuilder(); - for (Artifact artifact : project.getArtifacts()) { - addToClassPaths(classPathManifest, classPath, artifact.getFile()); + DevModeContext devModeContext = new DevModeContext(); + for (Map.Entry e : System.getProperties().entrySet()) { + devModeContext.getSystemProperties().put(e.getKey().toString(), (String) e.getValue()); } + + final AppModel appModel; + try { + final LocalProject localProject = LocalProject.loadWorkspace(outputDirectory.toPath()); + for (LocalProject project : localProject.getSelfWithLocalDeps()) { + String sourcePath = null; + String classesPath = null; + String resourcePath = null; + Path javaSourcesDir = project.getSourcesSourcesDir(); + if (Files.isDirectory(javaSourcesDir)) { + sourcePath = javaSourcesDir.toAbsolutePath().toString(); + } + Path classesDir = project.getClassesDir(); + if (Files.isDirectory(classesDir)) { + classesPath = classesDir.toAbsolutePath().toString(); + } + Path resourcesSourcesDir = project.getResourcesSourcesDir(); + if (Files.isDirectory(resourcesSourcesDir)) { + resourcePath = resourcesSourcesDir.toAbsolutePath().toString(); + } + DevModeContext.ModuleInfo moduleInfo = new DevModeContext.ModuleInfo(project.getArtifactId(), sourcePath, + classesPath, resourcePath); + devModeContext.getModules().add(moduleInfo); + } + + /* + * 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) + .setRepositorySystemSession(repoSession) + .setRemoteRepositories(repos) + .setWorkspace(localProject.getWorkspace()) + .build()) + .setDevMode(true) + .resolveModel(localProject.getAppArtifact()); + } catch (Exception e) { + throw new MojoExecutionException("Failed to resolve Quarkus application model", e); + } + for (AppDependency appDep : appModel.getAllDependencies()) { + addToClassPaths(classPathManifest, devModeContext, appDep.getArtifact().getPath().toFile()); + } + args.add("-Djava.util.logging.manager=org.jboss.logmanager.LogManager"); File wiringClassesDirectory = new File(buildDir, "wiring-devmode"); wiringClassesDirectory.mkdirs(); - - addToClassPaths(classPathManifest, classPath, wiringClassesDirectory); + addToClassPaths(classPathManifest, devModeContext, 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 @@ -251,21 +293,26 @@ public void execute() throws MojoFailureException, MojoExecutionException { String jarPath = classFile.getPath().substring(0, classFile.getPath().lastIndexOf('!')); if (jarPath.startsWith("file:")) jarPath = jarPath.substring(5); - path = new File(jarPath); + // The resource will be URL encoded, so decode is so when addToClassPaths is called the encoding is correct + path = new File(URLDecoder.decode(jarPath, StandardCharsets.UTF_8.name())); } else if (classFile.getProtocol().equals("file")) { String filePath = classFile.getPath().substring(0, classFile.getPath().lastIndexOf(DevModeMain.class.getName().replace('.', '/'))); - path = new File(filePath); + path = new File(URLDecoder.decode(filePath, StandardCharsets.UTF_8.name())); } else { throw new MojoFailureException("Unsupported DevModeMain artifact URL:" + classFile); } - addToClassPaths(classPathManifest, classPath, path); + addToClassPaths(classPathManifest, devModeContext, path); //now we need to build a temporary jar to actually run File tempFile = new File(buildDir, project.getArtifactId() + "-dev.jar"); tempFile.delete(); - tempFile.deleteOnExit(); + // Only delete the -dev.jar on exit if requested + if (deleteDevJar) { + tempFile.deleteOnExit(); + } + getLog().debug("Executable jar: " + tempFile.getAbsolutePath()); try (ZipOutputStream out = new ZipOutputStream(new FileOutputStream(tempFile))) { out.putNextEntry(new ZipEntry("META-INF/")); @@ -276,28 +323,24 @@ public void execute() throws MojoFailureException, MojoExecutionException { out.putNextEntry(new ZipEntry("META-INF/MANIFEST.MF")); manifest.write(out); - out.putNextEntry(new ZipEntry(ClassLoaderCompiler.DEV_MODE_CLASS_PATH)); - out.write(classPath.toString().getBytes(StandardCharsets.UTF_8)); - } - String resources = null; - for (Resource i : project.getBuild().getResources()) { - //todo: support multiple resources dirs for config hot deployment - resources = i.getDirectory(); - break; + out.putNextEntry(new ZipEntry(DevModeMain.DEV_MODE_CONTEXT)); + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + ObjectOutputStream obj = new ObjectOutputStream(new DataOutputStream(bytes)); + obj.writeObject(devModeContext); + obj.close(); + out.write(bytes.toByteArray()); } outputDirectory.mkdirs(); + ApplicationInfoUtil.writeApplicationInfoProperties(appModel.getAppArtifact(), outputDirectory.toPath()); - args.add("-Dquarkus.runner.classes=" + outputDirectory.getAbsolutePath()); - args.add("-Dquarkus.runner.sources=" + sourceDir.getAbsolutePath()); - if (resources != null) { - args.add("-Dquarkus.runner.resources=" + new File(resources).getAbsolutePath()); - } args.add("-jar"); args.add(tempFile.getAbsolutePath()); args.add(outputDirectory.getAbsolutePath()); args.add(wiringClassesDirectory.getAbsolutePath()); args.add(new File(buildDir, "transformer-cache").getAbsolutePath()); + // Display the launch command line in debug mode + getLog().debug("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); @@ -313,7 +356,10 @@ public void run() { } }, "Development Mode Shutdown Hook")); try { - p.waitFor(); + int ret = p.waitFor(); + if (ret != 0) { + throw new MojoFailureException("JVM exited with error code: " + ret); + } } catch (Exception e) { p.destroy(); throw e; @@ -324,113 +370,18 @@ public void run() { } } - /** - * Search for the java command in the order: - * 1. maven-toolchains plugin configuration - * 2. java.home location - * 3. java[.exe] on the system path - * - * @return the java command to use - */ - protected String findJavaTool() { - String java = null; - - // See if a toolchain is configured - if (getToolchainManager() != null) { - Toolchain toolchain = getToolchainManager().getToolchainFromBuildContext("jdk", getSession()); - if (toolchain != null) { - java = toolchain.findTool("java"); - getLog().info("JVM from toolchain: " + java); - } - } - if (java == null) { - // use the same JVM as the one used to run Maven (the "java.home" one) - java = System.getProperty("java.home") + File.separator + "bin" + File.separator + "java"; - File javaCheck = new File(java); - getLog().debug("Checking: " + javaCheck.getAbsolutePath()); - if (!javaCheck.canExecute()) { - getLog().debug(javaCheck.getAbsolutePath() + " is not executable"); - - java = null; - // Try executable extensions if windows - if (OS.determineOS() == OS.WINDOWS && System.getenv().containsKey("PATHEXT")) { - String extpath = System.getenv("PATHEXT"); - String[] exts = extpath.split(";"); - for (String ext : exts) { - File winExe = new File(javaCheck.getAbsolutePath() + ext); - getLog().debug("Checking: " + winExe.getAbsolutePath()); - if (winExe.canExecute()) { - java = winExe.getAbsolutePath(); - getLog().debug("Executable: " + winExe.getAbsolutePath()); - break; - } - } - } - // Fallback to java on the path - if (java == null) { - if (OS.determineOS() == OS.WINDOWS) { - java = "java.exe"; - } else { - java = "java"; - } - } - } - } - getLog().debug("findJavaTool, selected JVM: " + java); - return java; - } - - private void addToClassPaths(StringBuilder classPathManifest, StringBuilder classPath, File file) - throws MalformedURLException { + private void addToClassPaths(StringBuilder classPathManifest, DevModeContext classPath, File file) { URI uri = file.toPath().toAbsolutePath().toUri(); - classPathManifest.append(uri.getRawPath()); - classPath.append(uri.toString()); + try { + classPath.getClassPath().add(uri.toURL()); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + String path = uri.getRawPath(); + classPathManifest.append(path); if (file.isDirectory()) { classPathManifest.append("/"); - classPath.append("/"); } classPathManifest.append(" "); - classPath.append(" "); } - - /** - * Enum to classify the os.name system property - */ - static enum OS { - WINDOWS, LINUX, MAC, OTHER; - - private String version; - - public String getVersion() { - return version; - } - - public void setVersion(String version) { - this.version = version; - } - - static OS determineOS() { - OS os = OS.OTHER; - String osName = System.getProperty("os.name"); - osName = osName.toLowerCase(); - if (osName.contains("windows")) { - os = OS.WINDOWS; - } else if (osName.contains("linux") - || osName.contains("freebsd") - || osName.contains("unix") - || osName.contains("sunos") - || osName.contains("solaris") - || osName.contains("aix")) { - os = OS.LINUX; - } else if (osName.contains("mac os")) { - os = OS.MAC; - } else { - os = OS.OTHER; - } - - os.setVersion(System.getProperty("os.version")); - return os; - } - } - } diff --git a/devtools/maven/src/main/java/io/quarkus/maven/ExampleConfigMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/ExampleConfigMojo.java index e91b6e606865e..dc4d6d95e8d74 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/ExampleConfigMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/ExampleConfigMojo.java @@ -38,6 +38,9 @@ import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.project.MavenProject; +/** + * Creates an example configuration file. + */ @Mojo(name = "create-example-config", defaultPhase = LifecyclePhase.NONE, requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME) public class ExampleConfigMojo extends AbstractMojo { diff --git a/devtools/maven/src/main/java/io/quarkus/maven/ListExtensionsMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/ListExtensionsMojo.java index 823d00a0450bf..d8ccf41e5fee3 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/ListExtensionsMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/ListExtensionsMojo.java @@ -2,22 +2,40 @@ import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.project.MavenProject; -import io.quarkus.cli.commands.AddExtensions; -import io.quarkus.maven.utilities.MojoUtils; +import io.quarkus.cli.commands.ListExtensions; +/** + * List the available extensions. + * You can add one or several extensions in one go, with the 2 following mojos: + * {@code add-extensions} and {@code add-extension}. + * You can list all extension or just installable and choose simple or full format. + */ @Mojo(name = "list-extensions", requiresProject = false) public class ListExtensionsMojo extends AbstractMojo { + /** + * The Maven project which will define and configure the quarkus-maven-plugin + */ + @Parameter(defaultValue = "${project}") + protected MavenProject project; + + /** + * list all extensions or just the installable. + */ + @Parameter(property = "quarkus.extension.all", alias = "quarkus.extension.all", defaultValue = "true") + protected boolean all; + + /** + * display in simplified format. + */ + @Parameter(property = "quarkus.extension.format", alias = "quarkus.extension.format", defaultValue = "full") + protected String format; + @Override public void execute() { - getLog().info("Available extensions:"); - MojoUtils.loadExtensions().stream() - .sorted((o1, o2) -> o1.getName().compareToIgnoreCase(o2.getName())) - .forEach(ext -> getLog() - .info("\t * " + ext.getName() + " (" + ext.getGroupId() + ":" + ext.getArtifactId() + ")")); - - getLog().info("\nAdd an extension to your project by adding the dependency to your " + - "project or use `mvn quarkus:add-extension -Dextensions=\"name\"`"); + new ListExtensions(project.getModel()).listExtensions(all, format); } } 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 2481c0a7c82ac..233082508fd61 100644 --- a/devtools/maven/src/main/java/io/quarkus/maven/NativeImageMojo.java +++ b/devtools/maven/src/main/java/io/quarkus/maven/NativeImageMojo.java @@ -31,12 +31,14 @@ import io.quarkus.creator.AppCreator; import io.quarkus.creator.AppCreatorException; -import io.quarkus.creator.AppDependency; import io.quarkus.creator.phase.augment.AugmentOutcome; import io.quarkus.creator.phase.nativeimage.NativeImageOutcome; import io.quarkus.creator.phase.nativeimage.NativeImagePhase; import io.quarkus.creator.phase.runnerjar.RunnerJarOutcome; +/** + * Build a native executable of your application. + */ @Mojo(name = "native-image", defaultPhase = LifecyclePhase.PACKAGE, requiresDependencyResolution = ResolutionScope.RUNTIME) public class NativeImageMojo extends AbstractMojo { @@ -109,6 +111,12 @@ public class NativeImageMojo extends AbstractMojo { @Parameter(defaultValue = "${native-image.docker-build}") private String dockerBuild; + @Parameter(defaultValue = "${native-image.container-runtime}") + private String containerRuntime; + + @Parameter(defaultValue = "${native-image.container-runtime-options}") + private String containerRuntimeOptions; + @Parameter(defaultValue = "false") private boolean enableVMInspection; @@ -124,6 +132,9 @@ public class NativeImageMojo extends AbstractMojo { @Parameter(defaultValue = "false") private boolean addAllCharsets; + @Parameter(defaultValue = "false") + private boolean enableFallbackImages; + public NativeImageMojo() { MojoLogger.logSupplier = this::getLog; } @@ -148,9 +159,12 @@ public void execute() throws MojoExecutionException, MojoFailureException { .setDebugSymbols(debugSymbols) .setDisableReports(disableReports) .setDockerBuild(dockerBuild) + .setContainerRuntime(containerRuntime) + .setContainerRuntimeOptions(containerRuntimeOptions) .setDumpProxies(dumpProxies) .setEnableAllSecurityServices(enableAllSecurityServices) .setEnableCodeSizeReporting(enableCodeSizeReporting) + .setEnableFallbackImages(enableFallbackImages) .setEnableHttpsUrlHandler(enableHttpsUrlHandler) .setEnableHttpUrlHandler(enableHttpUrlHandler) .setEnableIsolates(enableIsolates) @@ -189,13 +203,13 @@ public Path getWiringClassesDir() { } @Override - public boolean isWhitelisted(AppDependency dep) { - // not relevant for this mojo - throw new UnsupportedOperationException(); + public Path getConfigDir() { + return classesDir; } }) .pushOutcome(RunnerJarOutcome.class, new RunnerJarOutcome() { final Path runnerJar = buildDir.toPath().resolve(finalName + "-runner.jar"); + final Path originalJar = buildDir.toPath().resolve(finalName + ".jar"); @Override public Path getRunnerJar() { @@ -206,6 +220,11 @@ public Path getRunnerJar() { public Path getLibDir() { return runnerJar.getParent().resolve("lib"); } + + @Override + public Path getOriginalJar() { + return originalJar; + } }) // resolve the outcome of the native image phase .resolveOutcome(NativeImageOutcome.class); diff --git a/devtools/maven/src/main/java/io/quarkus/maven/RemoteDevMojo.java b/devtools/maven/src/main/java/io/quarkus/maven/RemoteDevMojo.java new file mode 100644 index 0000000000000..cab82cfa3d23d --- /dev/null +++ b/devtools/maven/src/main/java/io/quarkus/maven/RemoteDevMojo.java @@ -0,0 +1,162 @@ +/* + * Copyright 2018 Red Hat, Inc. + * + * Licensed 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.maven; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Optional; + +import org.apache.maven.execution.MavenSession; +import org.apache.maven.model.Plugin; +import org.apache.maven.model.PluginExecution; +import org.apache.maven.model.Resource; +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.annotations.Component; +import org.apache.maven.plugins.annotations.LifecyclePhase; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.plugins.annotations.ResolutionScope; +import org.apache.maven.project.MavenProject; +import org.apache.maven.toolchain.ToolchainManager; +import org.eclipse.microprofile.config.Config; +import org.eclipse.microprofile.config.ConfigProvider; + +import io.quarkus.maven.components.MavenVersionEnforcer; +import io.quarkus.maven.utilities.MojoUtils; +import io.quarkus.remotedev.AgentRunner; +import io.smallrye.config.PropertiesConfigSource; +import io.smallrye.config.SmallRyeConfigProviderResolver; + +/** + * The dev mojo, that connects to a remote host. + */ +@Mojo(name = "remote-dev", defaultPhase = LifecyclePhase.PREPARE_PACKAGE, requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME) +public class RemoteDevMojo extends AbstractMojo { + + /** + * The directory for compiled classes. + */ + @Parameter(readonly = true, required = true, defaultValue = "${project.build.outputDirectory}") + private File outputDirectory; + + @Parameter(defaultValue = "${project}", readonly = true, required = true) + protected MavenProject project; + + @Parameter(defaultValue = "${project.build.sourceDirectory}") + private File sourceDir; + + @Parameter(defaultValue = "${jvm.args}") + private String jvmArgs; + + @Parameter(defaultValue = "${session}") + private MavenSession session; + + @Parameter(defaultValue = "TRUE") + private boolean deleteDevJar; + + @Component + private MavenVersionEnforcer mavenVersionEnforcer; + + @Component + private ToolchainManager toolchainManager; + + public ToolchainManager getToolchainManager() { + return toolchainManager; + } + + public MavenSession getSession() { + return session; + } + + @Override + public void execute() throws MojoFailureException, MojoExecutionException { + mavenVersionEnforcer.ensureMavenVersion(getLog(), session); + boolean found = MojoUtils.checkProjectForMavenBuildPlugin(project); + + if (!found) { + getLog().warn("The quarkus-maven-plugin build goal was not configured for this project, " + + "skipping quarkus:remote-dev as this is assumed to be a support library. If you want to run Quarkus remote-dev" + + + " on this project make sure the quarkus-maven-plugin is configured with a build goal."); + return; + } + + if (!sourceDir.isDirectory()) { + throw new MojoFailureException("The `src/main/java` directory is required, please create it."); + } + + String resources = null; + for (Resource i : project.getBuild().getResources()) { + //todo: support multiple resources dirs for config hot deployment + resources = i.getDirectory(); + break; + } + + String classes = outputDirectory.getAbsolutePath(); + String sources = sourceDir.getAbsolutePath(); + + //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 + if (resources != null) { + Path config = Paths.get(resources).resolve("application.properties"); + if (Files.exists(config)) { + try { + Config built = SmallRyeConfigProviderResolver.instance().getBuilder() + .addDefaultSources() + .addDiscoveredConverters() + .addDiscoveredSources() + .withSources(new PropertiesConfigSource(config.toUri().toURL())).build(); + SmallRyeConfigProviderResolver.instance().registerConfig(built, + Thread.currentThread().getContextClassLoader()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + + Optional url = ConfigProvider.getConfig().getOptionalValue("quarkus.live-reload.url", String.class); + Optional password = ConfigProvider.getConfig().getOptionalValue("quarkus.live-reload.password", + String.class); + if (!url.isPresent()) { + throw new MojoFailureException("To use remote-dev you must specify quarkus.live-reload.url"); + } + if (!password.isPresent()) { + throw new MojoFailureException("To use remote-dev you must specify quarkus.live-reload.password"); + } + System.out.println(sources); + String remotePath = url.get(); + if (remotePath.endsWith("/")) { + remotePath = remotePath.substring(0, remotePath.length() - 1); + } + AgentRunner runner = new AgentRunner(resources, sources, classes, remotePath + "/quarkus/live-reload", + password.get()); + + runner.run(); + for (;;) { + try { + Thread.sleep(10000); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } + +} diff --git a/devtools/maven/src/test/java/io/quarkus/maven/AddExtensionMojoTest.java b/devtools/maven/src/test/java/io/quarkus/maven/AddExtensionMojoTest.java index a0b719c048f44..5a231b76772ec 100644 --- a/devtools/maven/src/test/java/io/quarkus/maven/AddExtensionMojoTest.java +++ b/devtools/maven/src/test/java/io/quarkus/maven/AddExtensionMojoTest.java @@ -4,6 +4,8 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import java.io.*; +import java.nio.file.Files; +import java.nio.file.Paths; import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -90,7 +92,7 @@ void testThatAtLeastOneParameterMustBeSetWithBlankAndEmpty() { private Model reload() throws IOException, XmlPullParserException { MavenXpp3Reader reader = new MavenXpp3Reader(); - try (Reader fr = new FileReader(OUTPUT_POM)) { + try (Reader fr = Files.newBufferedReader(OUTPUT_POM.toPath())) { return reader.read(fr); } } diff --git a/devtools/maven/src/test/java/io/quarkus/maven/it/CreateProjectMojoIT.java b/devtools/maven/src/test/java/io/quarkus/maven/it/CreateProjectMojoIT.java index 34d750546a518..1ced42ee334a8 100644 --- a/devtools/maven/src/test/java/io/quarkus/maven/it/CreateProjectMojoIT.java +++ b/devtools/maven/src/test/java/io/quarkus/maven/it/CreateProjectMojoIT.java @@ -69,7 +69,8 @@ public void testProjectGenerationFromScratch() throws MavenInvocationException, .read(); assertThat(config).contains("key = value"); - assertThat(new File(testDir, "src/main/docker/Dockerfile")).isFile(); + assertThat(new File(testDir, "src/main/docker/Dockerfile.native")).isFile(); + assertThat(new File(testDir, "src/main/docker/Dockerfile.jvm")).isFile(); Model model = load(testDir); final DependencyManagement dependencyManagement = model.getDependencyManagement(); diff --git a/devtools/maven/src/test/java/io/quarkus/maven/it/DevMojoIT.java b/devtools/maven/src/test/java/io/quarkus/maven/it/DevMojoIT.java index 6c557ec88d0f1..4d694d5d79c53 100644 --- a/devtools/maven/src/test/java/io/quarkus/maven/it/DevMojoIT.java +++ b/devtools/maven/src/test/java/io/quarkus/maven/it/DevMojoIT.java @@ -40,14 +40,21 @@ public void cleanup() { } @Test - public void testThatClassAppCanRun() throws MavenInvocationException, FileNotFoundException { + public void testThatClassAppCanRun() throws MavenInvocationException, IOException { testDir = initProject("projects/classic", "projects/project-classic-run"); runAndCheck(); + + //make sure that the Class.getPackage() works for app classes + String pkg = getHttpResponse("/app/hello/package"); + assertThat(pkg).isEqualTo("org.acme"); + + //make sure webjars work + getHttpResponse("webjars/bootstrap/3.1.0/css/bootstrap.min.css"); + assertThatOutputWorksCorrectly(running.log()); } @Test - public void testThatTheApplicationIsReloadedOnJavaChange() - throws MavenInvocationException, IOException, InterruptedException { + public void testThatTheApplicationIsReloadedOnJavaChange() throws MavenInvocationException, IOException { testDir = initProject("projects/classic", "projects/project-classic-run-java-change"); runAndCheck(); @@ -75,8 +82,59 @@ public void testThatTheApplicationIsReloadedOnJavaChange() } @Test - public void testThatTheApplicationIsReloadedOnKotlinChange() - throws MavenInvocationException, IOException, InterruptedException { + public void testThatTheApplicationIsReloadedMultiModule() throws MavenInvocationException, IOException { + testDir = initProject("projects/multimodule", "projects/multimodule"); + runAndCheck(); + + // Edit the "Hello" message. + File source = new File(testDir, "rest/src/main/java/org/acme/HelloResource.java"); + final String uuid = UUID.randomUUID().toString(); + filter(source, ImmutableMap.of("return \"hello\";", "return \"" + uuid + "\";")); + + // Wait until we get "uuid" + await() + .pollDelay(1, TimeUnit.SECONDS) + .atMost(1, TimeUnit.MINUTES).until(() -> getHttpResponse("/app/hello").contains(uuid)); + + await() + .pollDelay(1, TimeUnit.SECONDS) + .pollInterval(1, TimeUnit.SECONDS) + .until(source::isFile); + + filter(source, ImmutableMap.of(uuid, "carambar")); + + // Wait until we get "carambar" + await() + .pollDelay(1, TimeUnit.SECONDS) + .atMost(1, TimeUnit.MINUTES).until(() -> getHttpResponse("/app/hello").contains("carambar")); + + // Create a new resource + source = new File(testDir, "html/src/main/resources/META-INF/resources/lorem.txt"); + FileUtils.write(source, + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", + "UTF-8"); + await() + .pollDelay(1, TimeUnit.SECONDS) + .atMost(1, TimeUnit.MINUTES) + .until(() -> getHttpResponse("/lorem.txt").contains("Lorem ipsum")); + + // Update the resource + FileUtils.write(source, uuid, "UTF-8"); + await() + .pollDelay(1, TimeUnit.SECONDS) + .atMost(1, TimeUnit.MINUTES) + .until(() -> getHttpResponse("/lorem.txt").contains(uuid)); + + // Delete the resource + source.delete(); + await() + .pollDelay(1, TimeUnit.SECONDS) + .atMost(1, TimeUnit.MINUTES) + .until(() -> getHttpResponse("/lorem.txt", 404)); + } + + @Test + public void testThatTheApplicationIsReloadedOnKotlinChange() throws MavenInvocationException, IOException { testDir = initProject("projects/classic-kotlin", "projects/project-classic-run-kotlin-change"); runAndCheck(); @@ -178,6 +236,41 @@ public void testThatTheApplicationIsReloadedOnConfigChange() throws MavenInvocat .until(() -> getHttpResponse("/app/hello/greeting").contains(uuid)); } + @Test + public void testThatAddingConfigFileWorksCorrectly() throws MavenInvocationException, IOException { + 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()); + + String resp = getHttpResponse(); + + assertThat(resp).containsIgnoringCase("ready").containsIgnoringCase("application").containsIgnoringCase("org.acme") + .containsIgnoringCase("1.0-SNAPSHOT"); + + String greeting = getHttpResponse("/app/hello/greeting"); + assertThat(greeting).contains("initialValue"); + + File configurationFile = new File(testDir, "src/main/resources/application.properties"); + assertThat(configurationFile).doesNotExist(); + + String uuid = UUID.randomUUID().toString(); + + FileUtils.write(configurationFile, + "greeting=" + uuid, + "UTF-8"); + await() + .pollDelay(1, TimeUnit.SECONDS) + .pollInterval(1, TimeUnit.SECONDS) + .until(configurationFile::isFile); + + // Wait until we get "uuid" + await() + .pollDelay(1, TimeUnit.SECONDS) + .atMost(10, TimeUnit.SECONDS) + .until(() -> getHttpResponse("/app/hello/greeting").contains(uuid)); + } + @Test public void testThatNewResourcesAreServed() throws MavenInvocationException, IOException { testDir = initProject("projects/classic", "projects/project-classic-run-resource-change"); @@ -288,16 +381,6 @@ public void testThatNewBeanAreDiscovered() throws IOException, MavenInvocationEx .atMost(1, TimeUnit.MINUTES).until(() -> getHttpResponse("/app/hello").contains("foobarbaz")); } - @Test - public void testErrorMessageWhenNoJavaSources() throws IOException, MavenInvocationException { - testDir = initProject("projects/classic", "projects/project-no-sources"); - FileUtils.deleteQuietly(new File(testDir, "src/main/java")); - running = new RunningInvoker(testDir, false); - MavenProcessInvocationResult result = running.execute(Arrays.asList("compile", "quarkus:dev"), Collections.emptyMap()); - await().until(() -> result.getProcess() != null && !result.getProcess().isAlive()); - assertThat(running.log()).containsIgnoringCase("BUILD FAILURE"); - } - @Test public void testErrorMessageWhenNoTarget() throws IOException, MavenInvocationException { testDir = initProject("projects/classic", "projects/project-no-target"); @@ -320,5 +403,4 @@ public void testErrorMessageWhenNoTargetClasses() throws IOException, MavenInvoc await().until(() -> result.getProcess() != null && !result.getProcess().isAlive()); assertThat(running.log()).containsIgnoringCase("BUILD FAILURE"); } - } diff --git a/devtools/maven/src/test/java/io/quarkus/maven/it/JarRunnerIT.java b/devtools/maven/src/test/java/io/quarkus/maven/it/JarRunnerIT.java new file mode 100644 index 0000000000000..79c94d32f6ee7 --- /dev/null +++ b/devtools/maven/src/test/java/io/quarkus/maven/it/JarRunnerIT.java @@ -0,0 +1,59 @@ +package io.quarkus.maven.it; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +import java.io.File; +import java.io.IOException; +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.concurrent.TimeUnit; + +import org.apache.commons.io.FileUtils; +import org.apache.maven.shared.invoker.MavenInvocationException; +import org.junit.jupiter.api.Test; + +import io.quarkus.maven.it.verifier.MavenProcessInvocationResult; +import io.quarkus.maven.it.verifier.RunningInvoker; +import io.quarkus.utilities.JavaBinFinder; + +public class JarRunnerIT extends MojoTestBase { + + @Test + public void testThatJarRunnerConsoleOutputWorksCorrectly() throws MavenInvocationException, IOException { + File testDir = initProject("projects/classic", "projects/project-classic-console-output"); + RunningInvoker running = new RunningInvoker(testDir, false); + + MavenProcessInvocationResult result = running.execute(Arrays.asList("package", "-DskipTests"), Collections.emptyMap()); + await().atMost(1, TimeUnit.MINUTES).until(() -> result.getProcess() != null && !result.getProcess().isAlive()); + assertThat(running.log()).containsIgnoringCase("BUILD SUCCESS"); + running.stop(); + + Path jar = testDir.toPath().toAbsolutePath().resolve(Paths.get("target/acme-1.0-SNAPSHOT-runner.jar")); + File output = new File(testDir, "target/output.log"); + output.createNewFile(); + + List commands = new ArrayList<>(); + commands.add(JavaBinFinder.findBin()); + commands.add("-jar"); + commands.add(jar.toString()); + ProcessBuilder processBuilder = new ProcessBuilder(commands.toArray(new String[0])); + processBuilder.redirectOutput(output); + processBuilder.redirectError(output); + Process process = processBuilder.start(); + // Wait until server up + await() + .pollDelay(1, TimeUnit.SECONDS) + .atMost(1, TimeUnit.MINUTES).until(() -> getHttpResponse("/app/hello/package", 200)); + + String logs = FileUtils.readFileToString(output, "UTF-8"); + + assertThatOutputWorksCorrectly(logs); + + process.destroy(); + } +} diff --git a/devtools/maven/src/test/java/io/quarkus/maven/it/MojoTestBase.java b/devtools/maven/src/test/java/io/quarkus/maven/it/MojoTestBase.java index dc9256d5bccbe..77b7cd3244df3 100644 --- a/devtools/maven/src/test/java/io/quarkus/maven/it/MojoTestBase.java +++ b/devtools/maven/src/test/java/io/quarkus/maven/it/MojoTestBase.java @@ -7,14 +7,15 @@ import java.io.IOException; import java.net.HttpURLConnection; import java.net.URL; -import java.net.URLConnection; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Predicate; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.regex.Pattern; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; @@ -74,15 +75,34 @@ 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"); - try { - filter(pom, VARIABLES); - } catch (IOException e) { - throw new IllegalArgumentException(e); + 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); + } + } + } } - return out; } public static void installPluginToLocalRepository(File local) { @@ -143,7 +163,7 @@ static void awaitUntilServerDown() { }); } - static String getHttpResponse() { + String getHttpResponse() { AtomicReference resp = new AtomicReference<>(); await() .pollDelay(1, TimeUnit.SECONDS) @@ -151,6 +171,12 @@ static String getHttpResponse() { //some, such as org.jetbrains.kotlin:kotlin-compiler, are huge and will take more than a minute. .atMost(20, TimeUnit.MINUTES).until(() -> { try { + String broken = getBrokenReason(); + if (broken != null) { + //try and avoid waiting 20m + resp.set("BROKEN: " + broken); + return true; + } String content = get(); resp.set(content); return true; @@ -161,6 +187,10 @@ static String getHttpResponse() { return resp.get(); } + protected String getBrokenReason() { + return null; + } + static String getHttpResponse(String path) { return getHttpResponse(path, false); } @@ -216,4 +246,14 @@ public static String get() throws IOException { URL url = new URL("http://localhost:8080"); return IOUtils.toString(url, "UTF-8"); } + + public static void assertThatOutputWorksCorrectly(String logs) { + assertThat(logs.isEmpty()).isFalse(); + String infoLogLevel = "INFO"; + assertThat(logs.contains(infoLogLevel)).isTrue(); + Predicate datePattern = Pattern.compile("\\d{4}-\\d{2}-\\d{2}\\s\\d{2}:\\d{2}:\\d{2},\\d{3}\\s").asPredicate(); + assertThat(datePattern.test(logs)).isTrue(); + assertThat(logs.contains("features: [cdi, resteasy, undertow-websockets]")).isTrue(); + assertThat(logs.contains("JBoss Threads version")).isFalse(); + } } diff --git a/devtools/maven/src/test/java/io/quarkus/maven/it/RemoteDevMojoIT.java b/devtools/maven/src/test/java/io/quarkus/maven/it/RemoteDevMojoIT.java new file mode 100644 index 0000000000000..5548f637a6370 --- /dev/null +++ b/devtools/maven/src/test/java/io/quarkus/maven/it/RemoteDevMojoIT.java @@ -0,0 +1,341 @@ +package io.quarkus.maven.it; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.Collections; +import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import org.apache.commons.io.FileUtils; +import org.apache.maven.shared.invoker.MavenInvocationException; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import com.google.common.collect.ImmutableMap; + +import io.quarkus.maven.it.verifier.RunningInvoker; + +/** + * @author Clement Escoffier + */ +public class RemoteDevMojoIT extends MojoTestBase { + + private RunningInvoker running; + private RunningInvoker runningAgent; + private File testDir; + private File agentDir; + + @AfterEach + public void cleanup() throws IOException { + if (running != null) { + running.stop(); + } + if (runningAgent != null) { + runningAgent.stop(); + } + awaitUntilServerDown(); + } + + @Test + public void testThatTheApplicationIsReloadedOnJavaChange() + throws MavenInvocationException, IOException, InterruptedException { + testDir = initProject("projects/classic", "projects/project-classic-run-java-change-remote"); + agentDir = initProject("projects/classic", "projects/project-classic-run-java-change-local"); + runAndCheck(); + + // Edit the "Hello" message. + File source = new File(agentDir, "src/main/java/org/acme/HelloResource.java"); + String uuid = UUID.randomUUID().toString(); + filter(source, ImmutableMap.of("return \"hello\";", "return \"" + uuid + "\";")); + + // Wait until we get "uuid" + await() + .pollDelay(1, TimeUnit.SECONDS) + .atMost(1, TimeUnit.MINUTES).until(() -> getHttpResponse("/app/hello").contains(uuid)); + + await() + .pollDelay(1, TimeUnit.SECONDS) + .pollInterval(1, TimeUnit.SECONDS) + .until(source::isFile); + + filter(source, ImmutableMap.of(uuid, "carambar")); + + // Wait until we get "carambar" + await() + .pollDelay(1, TimeUnit.SECONDS) + .atMost(1, TimeUnit.MINUTES).until(() -> getHttpResponse("/app/hello").contains("carambar")); + } + + @Override + protected String getBrokenReason() { + if (running != null && !running.getResult().getProcess().isAlive()) { + try { + return running.log(); + } catch (IOException e) { + e.printStackTrace(); + } + } + if (runningAgent != null && !runningAgent.getResult().getProcess().isAlive()) { + try { + return runningAgent.log(); + } catch (IOException e) { + e.printStackTrace(); + } + } + return null; + } + + @Test + public void testThatTheApplicationIsReloadedOnKotlinChange() + throws MavenInvocationException, IOException, InterruptedException { + testDir = initProject("projects/classic-kotlin", "projects/project-classic-run-kotlin-change-remote"); + agentDir = initProject("projects/classic-kotlin", "projects/project-classic-run-kotlin-change-local"); + runAndCheck(); + + // Edit the "Hello" message. + File source = new File(agentDir, "src/main/kotlin/org/acme/HelloResource.kt"); + String uuid = UUID.randomUUID().toString(); + filter(source, ImmutableMap.of("return \"hello\"", "return \"" + uuid + "\"")); + + // Wait until we get "uuid" + await() + .pollDelay(1, TimeUnit.SECONDS) + .atMost(1, TimeUnit.MINUTES).until(() -> getHttpResponse("/app/hello").contains(uuid)); + + await() + .pollDelay(1, TimeUnit.SECONDS) + .pollInterval(1, TimeUnit.SECONDS) + .until(source::isFile); + + filter(source, ImmutableMap.of(uuid, "carambar")); + + // Wait until we get "carambar" + await() + .pollDelay(1, TimeUnit.SECONDS) + .atMost(1, TimeUnit.MINUTES).until(() -> getHttpResponse("/app/hello").contains("carambar")); + } + + @Test + public void testThatTheApplicationIsReloadedOnNewResource() throws MavenInvocationException, IOException { + testDir = initProject("projects/classic", "projects/project-classic-run-new-resource-remote"); + agentDir = initProject("projects/classic", "projects/project-classic-run-new-resource-local"); + runAndCheck(); + + File source = new File(agentDir, "src/main/java/org/acme/MyNewResource.java"); + String myNewResource = "package org.acme;\n" + + "\n" + + "import javax.ws.rs.GET;\n" + + "import javax.ws.rs.Path;\n" + + "import javax.ws.rs.Produces;\n" + + "import javax.ws.rs.core.MediaType;\n" + + "\n" + + "@Path(\"/foo\")\n" + + "public class MyNewResource {\n" + + + " @GET\n" + + " @Produces(MediaType.TEXT_PLAIN)\n" + + " public String foo() {\n" + + " return \"bar\";\n" + + " }\n" + + "}\n"; + FileUtils.write(source, myNewResource, Charset.forName("UTF-8")); + + // Wait until we get "bar" + await() + .pollDelay(1, TimeUnit.SECONDS) + .atMost(1, TimeUnit.MINUTES).until(() -> getHttpResponse("/app/foo").contains("bar")); + } + + private void runAndCheck() throws FileNotFoundException, MavenInvocationException { + assertThat(testDir).isDirectory(); + running = new RunningInvoker(testDir, false); + running.execute(Arrays.asList("compile", "quarkus:dev"), Collections.emptyMap()); + + String resp = getHttpResponse(); + + assertThat(resp).containsIgnoringCase("ready").containsIgnoringCase("application").containsIgnoringCase("org.acme") + .containsIgnoringCase("1.0-SNAPSHOT"); + + String greeting = getHttpResponse("/app/hello"); + assertThat(greeting).containsIgnoringCase("hello"); + + try { + //file granularity is 1s on some platforms + //we check for changes since agent start + //so we need to make sure we start at least 1s after copying the files + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + runningAgent = new RunningInvoker(agentDir, false); + runningAgent.execute(Arrays.asList("compile", "quarkus:remote-dev"), Collections.emptyMap()); + + try { + Thread.sleep(1000); + await().pollDelay(100, TimeUnit.MILLISECONDS) + .pollInterval(100, TimeUnit.MILLISECONDS) + .atMost(1, TimeUnit.MINUTES) + .until(() -> runningAgent.log().contains("Connected to remote server")); + } catch (Exception e) { + throw new RuntimeException(e); + } + + } + + @Test + public void testThatTheApplicationIsReloadedOnConfigChange() throws MavenInvocationException, IOException { + testDir = initProject("projects/classic", "projects/project-classic-run-config-change-remote"); + 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()); + + String resp = getHttpResponse(); + runningAgent = new RunningInvoker(agentDir, false); + runningAgent.execute(Arrays.asList("compile", "quarkus:remote-dev"), Collections.emptyMap()); + + assertThat(resp).containsIgnoringCase("ready").containsIgnoringCase("application").containsIgnoringCase("org.acme") + .containsIgnoringCase("1.0-SNAPSHOT"); + + String greeting = getHttpResponse("/app/hello/greeting"); + assertThat(greeting).containsIgnoringCase("bonjour"); + + File source = new File(agentDir, "src/main/resources/application.properties"); + await() + .pollDelay(1, TimeUnit.SECONDS) + .pollInterval(1, TimeUnit.SECONDS) + .until(source::isFile); + + String uuid = UUID.randomUUID().toString(); + filter(source, ImmutableMap.of("bonjour", uuid)); + + // Wait until we get "uuid" + await() + .pollDelay(1, TimeUnit.SECONDS) + .atMost(1, TimeUnit.MINUTES) + .until(() -> getHttpResponse("/app/hello/greeting").contains(uuid)); + } + + @Test + public void testThatNewResourcesAreServed() throws MavenInvocationException, IOException { + testDir = initProject("projects/classic", "projects/project-classic-run-resource-change-remote"); + agentDir = initProject("projects/classic", "projects/project-classic-run-resource-change-local"); + runAndCheck(); + + // Create a new resource + File source = new File(agentDir, "src/main/resources/META-INF/resources/lorem.txt"); + FileUtils.write(source, + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", + "UTF-8"); + await() + .pollDelay(1, TimeUnit.SECONDS) + .atMost(1, TimeUnit.MINUTES) + .until(() -> getHttpResponse("/lorem.txt").contains("Lorem ipsum")); + + // Update the resource + String uuid = UUID.randomUUID().toString(); + FileUtils.write(source, uuid, "UTF-8"); + await() + .pollDelay(1, TimeUnit.SECONDS) + .atMost(1, TimeUnit.MINUTES) + .until(() -> getHttpResponse("/lorem.txt").contains(uuid)); + + // Delete the resource + //TODO: not supported yet in remote dev + // source.delete(); + // await() + // .pollDelay(1, TimeUnit.SECONDS) + // .atMost(1, TimeUnit.MINUTES) + // .until(() -> getHttpResponse("/lorem.txt", 404)); + } + + @Test + public void testThatApplicationRecoversCompilationIssue() throws MavenInvocationException, IOException { + testDir = initProject("projects/classic", "projects/project-classic-run-compilation-issue-remote"); + agentDir = initProject("projects/classic", "projects/project-classic-run-compilation-issue-local"); + runAndCheck(); + + // Edit the "Hello" message. + File source = new File(agentDir, "src/main/java/org/acme/HelloResource.java"); + String uuid = UUID.randomUUID().toString(); + filter(source, ImmutableMap.of("return \"hello\";", "return \"" + uuid + "\"")); // No semi-colon + + // Wait until we get "uuid" + AtomicReference last = new AtomicReference<>(); + await() + .pollDelay(1, TimeUnit.SECONDS) + .atMost(1, TimeUnit.MINUTES).until(() -> { + String content = getHttpResponse("/app/hello", true); + last.set(content); + return content.contains(uuid); + }); + + assertThat(last.get()).containsIgnoringCase("error") + .containsIgnoringCase("return \"" + uuid + "\"") + .containsIgnoringCase("compile"); + + await() + .pollDelay(1, TimeUnit.SECONDS) + .pollInterval(1, TimeUnit.SECONDS) + .until(source::isFile); + filter(source, ImmutableMap.of("\"" + uuid + "\"", "\"carambar\";")); + + // Wait until we get "uuid" + await() + .pollDelay(1, TimeUnit.SECONDS) + .atMost(1, TimeUnit.MINUTES).until(() -> getHttpResponse("/app/hello").contains("carambar")); + } + + @Test + public void testThatNewBeanAreDiscovered() throws IOException, MavenInvocationException { + testDir = initProject("projects/classic", "projects/project-classic-run-new-bean-remote"); + agentDir = initProject("projects/classic", "projects/project-classic-run-run-new-bean-local"); + runAndCheck(); + + // Edit the "Hello" message. + File source = new File(agentDir, "src/main/java/org/acme/MyBean.java"); + String content = "package org.acme;\n" + + "\n" + + "import javax.enterprise.context.ApplicationScoped;\n" + + "\n" + + "@ApplicationScoped\n" + + "public class MyBean {\n" + + "\n" + + " public String get() {\n" + + " return \"message\";\n" + + " }\n" + + " \n" + + "}"; + FileUtils.write(source, content, "UTF-8"); + + // Update the resource ot use the bean + File resource = new File(agentDir, "src/main/java/org/acme/HelloResource.java"); + filter(resource, Collections.singletonMap("String greeting;", "String greeting;\n @Inject MyBean bean;")); + filter(resource, Collections.singletonMap("\"hello\"", "bean.get()")); + + // Wait until we get "uuid" + await() + .pollDelay(1, TimeUnit.SECONDS) + .atMost(1, TimeUnit.MINUTES).until(() -> getHttpResponse("/app/hello").contains("message")); + + await() + .pollDelay(1, TimeUnit.SECONDS) + .pollInterval(1, TimeUnit.SECONDS) + .until(source::isFile); + + filter(source, ImmutableMap.of("message", "foobarbaz")); + + await() + .pollDelay(1, TimeUnit.SECONDS) + .atMost(1, TimeUnit.MINUTES).until(() -> getHttpResponse("/app/hello").contains("foobarbaz")); + } + +} diff --git a/devtools/maven/src/test/java/io/quarkus/maven/it/verifier/RunningInvoker.java b/devtools/maven/src/test/java/io/quarkus/maven/it/verifier/RunningInvoker.java index e2c2ae3b735a3..9a2b0e10d883a 100644 --- a/devtools/maven/src/test/java/io/quarkus/maven/it/verifier/RunningInvoker.java +++ b/devtools/maven/src/test/java/io/quarkus/maven/it/verifier/RunningInvoker.java @@ -88,4 +88,8 @@ public InvocationResult execute(InvocationRequest request) throws MavenInvocatio public String log() throws IOException { return FileUtils.readFileToString(log, "UTF-8"); } + + public MavenProcessInvocationResult getResult() { + return result; + } } diff --git a/devtools/maven/src/test/resources/projects/classic-kotlin/pom.xml b/devtools/maven/src/test/resources/projects/classic-kotlin/pom.xml index 27bc81c7e43e4..2d50897ddfd9d 100644 --- a/devtools/maven/src/test/resources/projects/classic-kotlin/pom.xml +++ b/devtools/maven/src/test/resources/projects/classic-kotlin/pom.xml @@ -17,19 +17,21 @@ @project.groupId@ quarkus-resteasy ${quarkus.version} - provided @project.groupId@ quarkus-arc ${quarkus.version} - provided @project.groupId@ - quarkus-kotlin + quarkus-kotlin-deployment + ${quarkus.version} + + + @project.groupId@ + quarkus-undertow-websockets ${quarkus.version} - provided @project.groupId@ @@ -40,7 +42,7 @@ io.rest-assured rest-assured - 3.2.0 + 3.3.0 test @@ -50,8 +52,8 @@ - ${project.basedir}/src/main/kotlin - ${project.basedir}/src/test/kotlin + src/main/kotlin + src/test/kotlin @project.groupId@ diff --git a/devtools/maven/src/test/resources/projects/classic-kotlin/src/main/resources/application.properties b/devtools/maven/src/test/resources/projects/classic-kotlin/src/main/resources/application.properties index a4bf738bbafb3..70026dec64e16 100644 --- a/devtools/maven/src/test/resources/projects/classic-kotlin/src/main/resources/application.properties +++ b/devtools/maven/src/test/resources/projects/classic-kotlin/src/main/resources/application.properties @@ -1,3 +1,6 @@ # Configuration file key = value greeting=bonjour + +quarkus.live-reload.password=secret +quarkus.live-reload.url=http://localhost:8080 \ No newline at end of file diff --git a/devtools/maven/src/test/resources/projects/classic-noconfig/pom.xml b/devtools/maven/src/test/resources/projects/classic-noconfig/pom.xml new file mode 100644 index 0000000000000..3ad1b2d10d0b5 --- /dev/null +++ b/devtools/maven/src/test/resources/projects/classic-noconfig/pom.xml @@ -0,0 +1,88 @@ + + + 4.0.0 + org.acme + acme + 1.0-SNAPSHOT + + @project.version@ + 1.8 + UTF-8 + 1.8 + + + + @project.groupId@ + quarkus-resteasy + ${quarkus.version} + + + @project.groupId@ + quarkus-arc + ${quarkus.version} + + + @project.groupId@ + quarkus-undertow-websockets + ${quarkus.version} + + + org.webjars + bootstrap + 3.1.0 + + + @project.groupId@ + quarkus-junit5 + ${quarkus.version} + test + + + io.rest-assured + rest-assured + 3.3.0 + test + + + + + + @project.groupId@ + @project.artifactId@ + ${quarkus.version} + + + + build + + + + + + + + + native + + + + @project.groupId@ + @project.artifactId@ + ${quarkus.version} + + + + native-image + + + true + + + + + + + + + diff --git a/devtools/maven/src/test/resources/projects/classic-noconfig/src/main/java/org/acme/HelloResource.java b/devtools/maven/src/test/resources/projects/classic-noconfig/src/main/java/org/acme/HelloResource.java new file mode 100644 index 0000000000000..423468dfb8864 --- /dev/null +++ b/devtools/maven/src/test/resources/projects/classic-noconfig/src/main/java/org/acme/HelloResource.java @@ -0,0 +1,24 @@ +package org.acme; + +import org.eclipse.microprofile.config.inject.ConfigProperty; + +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("/hello") +public class HelloResource { + + @Inject + @ConfigProperty(name = "greeting", defaultValue = "initialValue") + String greeting; + + @GET + @Path("/greeting") + @Produces(MediaType.TEXT_PLAIN) + public String greeting() { + return greeting; + } +} diff --git a/devtools/maven/src/test/resources/projects/classic-noconfig/src/main/java/org/acme/MyApplication.java b/devtools/maven/src/test/resources/projects/classic-noconfig/src/main/java/org/acme/MyApplication.java new file mode 100644 index 0000000000000..2b41cd0385afb --- /dev/null +++ b/devtools/maven/src/test/resources/projects/classic-noconfig/src/main/java/org/acme/MyApplication.java @@ -0,0 +1,9 @@ +package org.acme; + +import javax.ws.rs.ApplicationPath; +import javax.ws.rs.core.Application; + +@ApplicationPath("/app") +public class MyApplication extends Application { + +} diff --git a/devtools/maven/src/test/resources/projects/classic-noconfig/src/main/resources/META-INF/resources/index.html b/devtools/maven/src/test/resources/projects/classic-noconfig/src/main/resources/META-INF/resources/index.html new file mode 100644 index 0000000000000..ab969caa08e3a --- /dev/null +++ b/devtools/maven/src/test/resources/projects/classic-noconfig/src/main/resources/META-INF/resources/index.html @@ -0,0 +1,153 @@ + + + + + acme - 1.0-SNAPSHOT + + + + +

+ +
+
+

Congratulations, you have created a new Quarkus application.

+ +

Why do you see this?

+ +

This page is served by Quarkus. The source is in + src/main/resources/META-INF/resources/index.html.

+ +

What can I do from here?

+ +

If not already done, run the application in dev mode using: mvn compile quarkus:dev. +

+
    +
  • Add REST resources, Servlets, functions and other services in src/main/java.
  • +
  • Your static assets are located in src/main/resources/META-INF/resources.
  • +
  • Configure your application in src/main/resources/application.properties. +
  • +
+ +

How do I get rid of this page?

+

Just delete the src/main/resources/META-INF/resources/index.html file.

+
+
+
+

Application

+
    +
  • GroupId: org.acme
  • +
  • ArtifactId: acme
  • +
  • Version: 1.0-SNAPSHOT
  • +
  • Quarkus Version: 999-SNAPSHOT
  • +
+
+
+

Next steps

+ +
+
+
+ + + + \ No newline at end of file diff --git a/devtools/maven/src/test/resources/projects/classic-noconfig/src/test/java/org/acme/HelloResourceTest.java b/devtools/maven/src/test/resources/projects/classic-noconfig/src/test/java/org/acme/HelloResourceTest.java new file mode 100644 index 0000000000000..c2f29e2c9f711 --- /dev/null +++ b/devtools/maven/src/test/resources/projects/classic-noconfig/src/test/java/org/acme/HelloResourceTest.java @@ -0,0 +1,21 @@ +package org.acme; + +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 HelloResourceTest { + + @Test + public void testHelloEndpoint() { + given() + .when().get("/app/hello") + .then() + .statusCode(200) + .body(is("hello")); + } + +} diff --git a/devtools/maven/src/test/resources/projects/classic/pom.xml b/devtools/maven/src/test/resources/projects/classic/pom.xml index df48287a89fee..3ad1b2d10d0b5 100644 --- a/devtools/maven/src/test/resources/projects/classic/pom.xml +++ b/devtools/maven/src/test/resources/projects/classic/pom.xml @@ -16,13 +16,21 @@ @project.groupId@ quarkus-resteasy ${quarkus.version} - provided
@project.groupId@ quarkus-arc ${quarkus.version} - provided + + + @project.groupId@ + quarkus-undertow-websockets + ${quarkus.version} + + + org.webjars + bootstrap + 3.1.0 @project.groupId@ @@ -33,7 +41,7 @@ io.rest-assured rest-assured - 3.2.0 + 3.3.0 test diff --git a/devtools/maven/src/test/resources/projects/classic/src/main/java/org/acme/HelloResource.java b/devtools/maven/src/test/resources/projects/classic/src/main/java/org/acme/HelloResource.java index 793e59bb74c60..94d1aad900334 100644 --- a/devtools/maven/src/test/resources/projects/classic/src/main/java/org/acme/HelloResource.java +++ b/devtools/maven/src/test/resources/projects/classic/src/main/java/org/acme/HelloResource.java @@ -27,4 +27,16 @@ public String hello() { public String greeting() { return greeting; } + + @GET + @Path("/package") + @Produces(MediaType.TEXT_PLAIN) + public String pkg() { + return Blah.class.getPackage().getName(); + } + + + public static class Blah { + + } } diff --git a/devtools/maven/src/test/resources/projects/classic/src/main/resources/application.properties b/devtools/maven/src/test/resources/projects/classic/src/main/resources/application.properties index a4bf738bbafb3..9f58e0badff94 100644 --- a/devtools/maven/src/test/resources/projects/classic/src/main/resources/application.properties +++ b/devtools/maven/src/test/resources/projects/classic/src/main/resources/application.properties @@ -1,3 +1,10 @@ # Configuration file key = value greeting=bonjour +quarkus.live-reload.password=secret +quarkus.live-reload.url=http://localhost:8080/ +quarkus.log.level=INFO +quarkus.log.file.enable=false +quarkus.log.console.format=%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c{3.}] (%t) %s%e%n +quarkus.log.file.format=%d{yyyy-MM-dd HH:mm:ss,SSS} %h %N[%i] %-5p [%c{3.}] (%t) %s%e%n +quarkus.log.category."io.quarkus".level=INFO diff --git a/devtools/maven/src/test/resources/projects/multimodule/html/pom.xml b/devtools/maven/src/test/resources/projects/multimodule/html/pom.xml new file mode 100644 index 0000000000000..7ab25c5ed108c --- /dev/null +++ b/devtools/maven/src/test/resources/projects/multimodule/html/pom.xml @@ -0,0 +1,15 @@ + + + 4.0.0 + + + org.acme + quarkus-quickstart-multimodule-parent + 1.0-SNAPSHOT + ../ + + + org.acme + quarkus-quickstart-multimodule-html + 1.0-SNAPSHOT + diff --git a/devtools/maven/src/test/resources/projects/multimodule/html/src/main/resources/META-INF/resources/a.html b/devtools/maven/src/test/resources/projects/multimodule/html/src/main/resources/META-INF/resources/a.html new file mode 100644 index 0000000000000..07ab5ec510b0a --- /dev/null +++ b/devtools/maven/src/test/resources/projects/multimodule/html/src/main/resources/META-INF/resources/a.html @@ -0,0 +1 @@ +sadfasdfasdfas \ No newline at end of file diff --git a/devtools/maven/src/test/resources/projects/multimodule/html/src/main/resources/META-INF/resources/index.html b/devtools/maven/src/test/resources/projects/multimodule/html/src/main/resources/META-INF/resources/index.html new file mode 100644 index 0000000000000..346d06e401943 --- /dev/null +++ b/devtools/maven/src/test/resources/projects/multimodule/html/src/main/resources/META-INF/resources/index.html @@ -0,0 +1,152 @@ + + + + + getting-started - 1.0-SNAPSHOT + + + + + + +
+
+

Congratulations, you have created a new Quarkus application.

+ +

Why do you see this?

+ +

This page is served by Quarkus. The source is in + src/main/resources/META-INF/resources/index.html.

+ +

What can I do from here?

+ +

If not already done, run the application in dev mode using: mvn compile quarkus:dev. +

+
    +
  • Add REST resources, Servlets, functions and other services in src/main/java.
  • +
  • Your static assets are located in src/main/resources/META-INF/resources.
  • +
  • Configure your application in src/main/resources/META-INF/microprofile-config.properties. +
  • +
+ +

How do I get rid of this page?

+

Just delete the src/main/resources/META-INF/resources/index.html file.

+
+
+
+

Application

+
    +
  • GroupId: org.acme
  • +
  • ArtifactId: getting-started
  • +
  • Version: 1.0-SNAPSHOT
  • +
  • Quarkus Version: 0.11.0
  • +
+
+ +
+
+ + + + \ No newline at end of file diff --git a/devtools/maven/src/test/resources/projects/multimodule/pom.xml b/devtools/maven/src/test/resources/projects/multimodule/pom.xml new file mode 100644 index 0000000000000..b4fa3fdb264a3 --- /dev/null +++ b/devtools/maven/src/test/resources/projects/multimodule/pom.xml @@ -0,0 +1,45 @@ + + + 4.0.0 + + org.acme + quarkus-quickstart-multimodule-parent + 1.0-SNAPSHOT + pom + + + @project.version@ + 2.22.1 + UTF-8 + 1.8 + 1.8 + + + rest + html + runner + + + + + + @project.groupId@ + quarkus-maven-plugin + ${quarkus.version} + + + + + + + + @project.groupId@ + quarkus-bom + ${quarkus.version} + pom + import + + + + + diff --git a/devtools/maven/src/test/resources/projects/multimodule/rest/pom.xml b/devtools/maven/src/test/resources/projects/multimodule/rest/pom.xml new file mode 100644 index 0000000000000..6a8c9d39ebd99 --- /dev/null +++ b/devtools/maven/src/test/resources/projects/multimodule/rest/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + + org.acme + quarkus-quickstart-multimodule-parent + 1.0-SNAPSHOT + ../ + + org.acme + quarkus-quickstart-multimodule-rest + 1.0-SNAPSHOT + + + + + @project.groupId@ + quarkus-resteasy + + + diff --git a/devtools/maven/src/test/resources/projects/multimodule/rest/src/main/java/org/acme/HelloResource.java b/devtools/maven/src/test/resources/projects/multimodule/rest/src/main/java/org/acme/HelloResource.java new file mode 100644 index 0000000000000..94d1aad900334 --- /dev/null +++ b/devtools/maven/src/test/resources/projects/multimodule/rest/src/main/java/org/acme/HelloResource.java @@ -0,0 +1,42 @@ +package org.acme; + +import org.eclipse.microprofile.config.inject.ConfigProperty; + +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("/hello") +public class HelloResource { + + @Inject + @ConfigProperty(name = "greeting") + String greeting; + + @GET + @Produces(MediaType.TEXT_PLAIN) + public String hello() { + return "hello"; + } + + @GET + @Path("/greeting") + @Produces(MediaType.TEXT_PLAIN) + public String greeting() { + return greeting; + } + + @GET + @Path("/package") + @Produces(MediaType.TEXT_PLAIN) + public String pkg() { + return Blah.class.getPackage().getName(); + } + + + public static class Blah { + + } +} diff --git a/devtools/maven/src/test/resources/projects/multimodule/rest/src/main/java/org/acme/MyApplication.java b/devtools/maven/src/test/resources/projects/multimodule/rest/src/main/java/org/acme/MyApplication.java new file mode 100644 index 0000000000000..2b41cd0385afb --- /dev/null +++ b/devtools/maven/src/test/resources/projects/multimodule/rest/src/main/java/org/acme/MyApplication.java @@ -0,0 +1,9 @@ +package org.acme; + +import javax.ws.rs.ApplicationPath; +import javax.ws.rs.core.Application; + +@ApplicationPath("/app") +public class MyApplication extends Application { + +} diff --git a/devtools/maven/src/test/resources/projects/multimodule/rest/src/main/resources/META-INF/beans.xml b/devtools/maven/src/test/resources/projects/multimodule/rest/src/main/resources/META-INF/beans.xml new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/devtools/maven/src/test/resources/projects/multimodule/runner/pom.xml b/devtools/maven/src/test/resources/projects/multimodule/runner/pom.xml new file mode 100644 index 0000000000000..e80038914850d --- /dev/null +++ b/devtools/maven/src/test/resources/projects/multimodule/runner/pom.xml @@ -0,0 +1,115 @@ + + + 4.0.0 + + org.acme + quarkus-quickstart-multimodule-parent + 1.0-SNAPSHOT + ../ + + org.acme + quarkus-quickstart-multimodule-main + 1.0-SNAPSHOT + + + + + @project.groupId@ + quarkus-resteasy + + + ${project.groupId} + quarkus-quickstart-multimodule-html + ${project.version} + + + ${project.groupId} + quarkus-quickstart-multimodule-rest + ${project.version} + + + @project.groupId@ + quarkus-junit5 + test + + + io.rest-assured + rest-assured + test + + + + + + + maven-surefire-plugin + ${surefire-plugin.version} + + + org.jboss.logmanager.LogManager + + + + + io.quarkus + quarkus-maven-plugin + ${quarkus.version} + + + + build + + + + + + + + + + + native + + + native + + + + + + io.quarkus + quarkus-maven-plugin + ${quarkus.version} + + + + native-image + + + true + + + + + + maven-failsafe-plugin + ${surefire-plugin.version} + + + + integration-test + verify + + + + ${project.build.directory}/${project.build.finalName}-runner + + + + + + + + + + diff --git a/devtools/maven/src/test/resources/projects/multimodule/runner/src/main/resources/application.properties b/devtools/maven/src/test/resources/projects/multimodule/runner/src/main/resources/application.properties new file mode 100644 index 0000000000000..6926cb19f372a --- /dev/null +++ b/devtools/maven/src/test/resources/projects/multimodule/runner/src/main/resources/application.properties @@ -0,0 +1 @@ +greeting=bonjour \ No newline at end of file diff --git a/devtools/maven/src/test/resources/projects/uberjar-check/pom.xml b/devtools/maven/src/test/resources/projects/uberjar-check/pom.xml index 960664acdbb6d..ff5f846651d6e 100644 --- a/devtools/maven/src/test/resources/projects/uberjar-check/pom.xml +++ b/devtools/maven/src/test/resources/projects/uberjar-check/pom.xml @@ -16,13 +16,11 @@ @project.groupId@ quarkus-resteasy ${quarkus.version} - provided
@project.groupId@ quarkus-arc ${quarkus.version} - provided @project.groupId@ @@ -33,7 +31,7 @@ io.rest-assured rest-assured - 3.2.0 + 3.3.0 test diff --git a/devtools/pom.xml b/devtools/pom.xml index a3f2da5a97a9f..70ecf874ab216 100644 --- a/devtools/pom.xml +++ b/devtools/pom.xml @@ -41,7 +41,6 @@ common maven gradle - gradle-it aesh reflection-agent diff --git a/devtools/reflection-agent/src/main/java/io/quarkus/agent/ReflectionAgent.java b/devtools/reflection-agent/src/main/java/io/quarkus/agent/ReflectionAgent.java index 4cc50c60cb35c..9bd9c192a613a 100644 --- a/devtools/reflection-agent/src/main/java/io/quarkus/agent/ReflectionAgent.java +++ b/devtools/reflection-agent/src/main/java/io/quarkus/agent/ReflectionAgent.java @@ -6,6 +6,7 @@ import java.io.InputStreamReader; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.IllegalClassFormatException; +import java.nio.charset.StandardCharsets; import java.security.ProtectionDomain; import java.util.Collections; import java.util.HashSet; @@ -107,7 +108,7 @@ private static void handleClass(String className) { Set known = new HashSet<>(); try (InputStream in = Thread.currentThread().getContextClassLoader() .getResourceAsStream("META-INF/reflective-classes.txt")) { - BufferedReader reader = new BufferedReader(new InputStreamReader(in)); + BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)); String line; while ((line = reader.readLine()) != null) { known.add(line); diff --git a/docker/centos-graal-maven/Dockerfile b/docker/centos-graal-maven/Dockerfile deleted file mode 100644 index f285da7716641..0000000000000 --- a/docker/centos-graal-maven/Dockerfile +++ /dev/null @@ -1,13 +0,0 @@ -FROM swd847/centos-graal-rc13 - -ARG MAVEN_VERSION=3.3.9 -ARG USER_HOME_DIR="/root" -RUN mkdir -p /usr/share/maven && \ -curl -fsSL http://apache.osuosl.org/maven/maven-3/$MAVEN_VERSION/binaries/apache-maven-$MAVEN_VERSION-bin.tar.gz | tar -xzC /usr/share/maven --strip-components=1 && \ -ln -s /usr/share/maven/bin/mvn /usr/bin/mvn -ENV MAVEN_HOME /usr/share/maven -ENV MAVEN_CONFIG "$USER_HOME_DIR/.m2" -# speed up Maven JVM a bit -ENV MAVEN_OPTS="-XX:+TieredCompilation -XX:TieredStopAtLevel=1" -#ENTRYPOINT ["/usr/bin/mvn"] - diff --git a/docker/centos-graal-native-image/Dockerfile b/docker/centos-graal-native-image/Dockerfile deleted file mode 100644 index 0b418734b0d5e..0000000000000 --- a/docker/centos-graal-native-image/Dockerfile +++ /dev/null @@ -1,6 +0,0 @@ -FROM swd847/centos-graal-rc13 - -VOLUME /project -WORKDIR /project - -ENTRYPOINT ["native-image"] diff --git a/docker/centos-graal-native-image/README.md b/docker/centos-graal-native-image/README.md deleted file mode 100644 index 707eb5a7d34ca..0000000000000 --- a/docker/centos-graal-native-image/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Build - -```bash -docker build -t quarkus/graalvm-native-image:latest . -``` - -# Run - -```bash -docker run -it -v /path/to/quarkus/arc:/project --rm quarkus/graalvm-native-image -jar example/target/arc-example-shaded.jar -``` diff --git a/docker/centos-graal/Dockerfile b/docker/centos-graal/Dockerfile deleted file mode 100644 index c7d1bc03336b1..0000000000000 --- a/docker/centos-graal/Dockerfile +++ /dev/null @@ -1,21 +0,0 @@ -FROM centos:latest - -ARG GRAAL_VERSION -ENV GRAAL_VERSION=${GRAAL_VERSION:-1.0.0-rc13} -ENV GRAAL_CE_URL=https://github.com/oracle/graal/releases/download/vm-${GRAAL_VERSION}/graalvm-ce-${GRAAL_VERSION}-linux-amd64.tar.gz - -ENV JAVA_HOME=/opt/graalvm -ENV PATH=$PATH:$JAVA_HOME/bin -ENV GRAALVM_HOME=/opt/graalvm - - -RUN yum update -y && \ - yum install -y tar gzip gcc glibc-devel zlib-devel curl && \ - mkdir -p /opt/graalvm && \ - cd /opt/graalvm && \ - curl -fsSL $GRAAL_CE_URL | tar -xzC /opt/graalvm --strip-components=1 - - - - - diff --git a/docker/distroless/BUILD.bazel b/docker/distroless/BUILD.bazel deleted file mode 100755 index f0a8d33bfe66e..0000000000000 --- a/docker/distroless/BUILD.bazel +++ /dev/null @@ -1,14 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load("@io_bazel_rules_docker//container:push.bzl", "container_push") -load("@io_bazel_rules_docker//container:image.bzl", "container_image") -load("@package_bundle//file:packages.bzl", "packages") - -container_image( - name = "latest", - repository = "cescoffier/native-base", - base = "@distroless_base//image", - debs = [ - packages["zlib1g"] - ] -) \ No newline at end of file diff --git a/docker/distroless/README.md b/docker/distroless/README.md deleted file mode 100644 index 2e77d8585cd83..0000000000000 --- a/docker/distroless/README.md +++ /dev/null @@ -1,64 +0,0 @@ -# Distroless base image - -This project creates a [distroless image](https://github.com/GoogleContainerTools/distroless) suitable to run Quarkus applications. - -This image contains a minimal Linux, glibc-based system. -It contains: - -* ca-certificates -* A /etc/passwd entry for a root user -* A /tmp directory -* tzdata -* glibc -* libssl -* openssl -* zlib - -The final image is about 17Mb. - -## Using the image - -Build the native executable using: - -```bash -mvn package -Pnative -Dnative-image.docker-build=true -``` - -Then, create the following `Dockerfile`: - -```dockerfile -FROM cescoffier/native-base:latest -COPY target/*-runner /application -EXPOSE 8080 -CMD ["./application", "-Dquarkus.http.host=0.0.0.0"] -``` - -Build the docker image using (change the namespace/name): - -```bash -docker build -t quarkus-demo/demo . -``` - -You can then run your application using: - -```bash -docker run -i --rm -p 8080:8080 quarkus-demo/demo -``` - -## Build - -The build requires [bazel](https://bazel.build/). - -```bash -bazel build -bazel run :latest -``` - -It builds the `cescoffier/native-base:latest` image. - -## Updating the dependencies - -* Update the hash and versions in the `dependencies.bzl` file. - - - diff --git a/docker/distroless/WORKSPACE b/docker/distroless/WORKSPACE deleted file mode 100755 index 4e5ff2ba3fc90..0000000000000 --- a/docker/distroless/WORKSPACE +++ /dev/null @@ -1,13 +0,0 @@ -workspace(name = "io_quarkus_distroless") - - -load("//:dependencies.bzl", "distroless_bazel_repositories") -distroless_bazel_repositories() - -load("//:packages.bzl", "debian_dependencies") -debian_dependencies() - -load("//:container.bzl", "container_dependencies") -container_dependencies() - - diff --git a/docker/distroless/container.bzl b/docker/distroless/container.bzl deleted file mode 100755 index 872ac60d5ba5f..0000000000000 --- a/docker/distroless/container.bzl +++ /dev/null @@ -1,21 +0,0 @@ -load( - "@io_bazel_rules_docker//container:container.bzl", - container_repositories = "repositories", - "container_pull", -) - -load( - "@io_bazel_rules_docker//go:image.bzl", - image_repositories = "repositories", -) - -def container_dependencies(): - container_repositories() - image_repositories() - - container_pull( - name = "distroless_base", - registry = "gcr.io", - repository = "distroless/base", - ) - diff --git a/docker/distroless/dependencies.bzl b/docker/distroless/dependencies.bzl deleted file mode 100755 index 6c3ec0444fec9..0000000000000 --- a/docker/distroless/dependencies.bzl +++ /dev/null @@ -1,23 +0,0 @@ - -load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository", "new_git_repository") -load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") - -def distroless_bazel_repositories(): - - git_repository( - name = "io_bazel_rules_go", - remote = "https://github.com/bazelbuild/rules_go.git", - commit = "0.16.5", - ) - - http_archive( - name = "io_bazel_rules_docker", - url = "https://github.com/bazelbuild/rules_docker/archive/v0.5.1.zip", - strip_prefix = "rules_docker-0.5.1", - ) - - git_repository( - name = "distroless", - commit = "3585653b2b0d33c3fb369b907ef68df8344fd2ad", - remote = "https://github.com/GoogleContainerTools/distroless.git", - ) diff --git a/docker/distroless/packages.bzl b/docker/distroless/packages.bzl deleted file mode 100755 index 5dc279f91a2e3..0000000000000 --- a/docker/distroless/packages.bzl +++ /dev/null @@ -1,49 +0,0 @@ -load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") - -load( - "@distroless//package_manager:package_manager.bzl", - "package_manager_repositories", - "dpkg_src", - "dpkg_list", -) - -def debian_dependencies(): - - package_manager_repositories() - - dpkg_src( - name = "debian_stretch", - arch = "amd64", - distro = "stretch", - sha256 = "4cb2fac3e32292613b92d3162e99eb8a1ed7ce47d1b142852b0de3092b25910c", - snapshot = "20180406T095535Z", - url = "http://snapshot.debian.org/archive", - ) - - dpkg_src( - name = "debian_stretch_backports", - arch = "amd64", - distro = "stretch-backports", - sha256 = "2863af9484d2d6b478ef225a8c740dac9a14015a594241a0872024c873123cdd", - snapshot = "20180406T095535Z", - url = "http://snapshot.debian.org/archive", - ) - - dpkg_src( - name = "debian_stretch_security", - package_prefix = "http://snapshot.debian.org/archive/debian-security/20180405T165926Z/", - packages_gz_url = "http://snapshot.debian.org/archive/debian-security/20180405T165926Z/dists/stretch/updates/main/binary-amd64/Packages.gz", - sha256 = "a503fb4459eb9e862d080c7cf8135d7d395852e51cc7bfddf6c3d6cc4e11ee5f", - ) - - dpkg_list( - name = "package_bundle", - packages = [ - "zlib1g" - ], - sources = [ - "@debian_stretch_security//file:Packages.json", - "@debian_stretch_backports//file:Packages.json", - "@debian_stretch//file:Packages.json", - ], -) diff --git a/docker/integration-test-main/Dockerfile b/docker/integration-test-main/Dockerfile deleted file mode 100644 index 35e15e9822cb2..0000000000000 --- a/docker/integration-test-main/Dockerfile +++ /dev/null @@ -1,6 +0,0 @@ -FROM registry.fedoraproject.org/fedora-minimal -WORKDIR /work -COPY target/quarkus-integration-test-main-999-SNAPSHOT-runner /work/quarkus-integration-test-main-runner -RUN chmod 777 /work -EXPOSE 8080 -ENTRYPOINT ["/work/quarkus-integration-test-main-runner", "-Xmx30m", "-Xmn25m", "-Dquarkus.http.host=0.0.0.0", "-Dquarkus.datasource.url=jdbc:postgresql://postgresql-10-centos7/hibernate_orm_test" ] diff --git a/docs/pom.xml b/docs/pom.xml index c81207028c695..b6f3c72855864 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -54,6 +54,8 @@ ${project.version} ${version.surefire.plugin} ${graal-sdk.version-for-documentation} + ${axle-client.version} + ${vertx.version} ${rest-assured.version} Quarkus @@ -85,6 +87,7 @@ ${quarkus-base-url}/tree/master ${quarkus-base-url}/issues + https://github.com/quarkusio/quarkus-images/tree diff --git a/docs/src/main/asciidoc/README.adoc b/docs/src/main/asciidoc/README.adoc index b1f72ac6fee47..7443194e875c0 100644 --- a/docs/src/main/asciidoc/README.adoc +++ b/docs/src/main/asciidoc/README.adoc @@ -36,6 +36,7 @@ complete list of externalized variables for use is given in the following table: |\{quarkus-blob-url}|{quarkus-blob-url}| {project-name} URL to master blob source tree; used for referencing source files. |\{quarkus-tree-url}|{quarkus-tree-url}| {project-name} URL to master source tree root; used for referencing directories. |\{quarkus-issues-url}|{quarkus-issues-url}| {project-name} URL to the issues page. +|\{quarkus-images-url}|{quarkus-images-url}| {project-name} URL to set of container images delivered for Quarkus. |\{quarkus-chat-url}|{quarkus-chat-url} | URL of our chat. |\{quarkus-mailing-list-subscription-email}|{quarkus-mailing-list-subscription-email} | Email used to subscribe to our mailing list. diff --git a/docs/src/main/asciidoc/ap4k.adoc b/docs/src/main/asciidoc/ap4k.adoc new file mode 100644 index 0000000000000..979341736d208 --- /dev/null +++ b/docs/src/main/asciidoc/ap4k.adoc @@ -0,0 +1,121 @@ += {project-name} - Generating Kubernetes resources + +This guide covers generating Kubernetes resources based on sane defaults and user supplied configuration. + +== Prerequisites + +To complete this guide, you need: + +* roughly 10 minutes +* an IDE +* JDK 1.8+ installed with `JAVA_HOME` configured appropriately +* Apache Maven 3.5.3+ +* access to a Kubernetes or cluster (Minikube is a viable options) + +== Creating the Maven project + +First, we need a new project that contains the kubernetes extension. This can be done using the following command: + +[source, subs=attributes+] +---- +mvn io.quarkus:quarkus-maven-plugin:${quarkus-version}:create \ + -DprojectGroupId=org.acme \ + -DprojectArtifactId=test \ + -DclassName="org.acme.rest.GreetingResource" \ + -Dpath="/greeting" \ + -Dextensions="kubernetes" +---- + +== Enable Kubernetes support + +{project-name} offers the ability to automatically generate Kubernetes resources based on sane defaults and user supplied configuration. The implementation that takes care +of generating the actual Kubernetes resources is provided by https://github.com/ap4k/ap4k/[ap4k]. + +When we added the `kubernetes` extension to the command line invocation above, the following dependency was added to the `pom.xml` + +[source,xml] +---- + + io.quarkus + quarkus-kubernetes + +---- + +By adding this dependency, we now have the ability to configure the Kubernetes resource generation and application using the usual `application.properties` approach that {project-name} provides. +The configuration items that are available can be found in: `io.quarkus.kubernetes.deployment.KubernetesConfig` class. +Furthermore, the items provided by `io.quarkus.deployment.ApplicationConfig` affect the Kubernetes resources. + +By using the following configuration for example: + +[source] +---- +quarkus.kubernetes.group=yourDockerUsername # this is optional and defaults to your username if not set +quarkus.application.name=test-quarkus-app # this is also optional and defaults to the project name if not set +---- + +and following the execution of `mvn package` you will notice amongst the other files that are created, two files named +`kubernetes.json` and `kubernetes.yaml` in the `target/wiring-classes/META-INF/kubernetes/` directory. + +If you look at either file you will see that it contains both a Kubernetes `Deployment` and a `Service`. + +The full source of the `kubernetes.json` file looks something like this: + +[source,json] +---- +{ + "apiVersion" : "v1", + "kind" : "List", + "items" : [ { + "apiVersion" : "apps/v1", + "kind" : "Deployment", + "metadata" : { + "labels" : { + "app" : "test-quarkus-app", + "version" : "1.0-SNAPSHOT", + "group" : "yourDockerUsername" + }, + "name" : "test-quarkus-app" + }, + "spec" : { + "replicas" : 1, + "selector" : { + "matchLabels" : { + "app" : "test-quarkus-app", + "version" : "1.0-SNAPSHOT", + "group" : "yourDockerUsername" + } + }, + "template" : { + "metadata" : { + "labels" : { + "app" : "test-quarkus-app", + "version" : "1.0-SNAPSHOT", + "group" : "yourDockerUsername" + } + }, + "spec" : { + "containers" : [ { + "env" : [ { + "name" : "KUBERNETES_NAMESPACE", + "valueFrom" : { + "fieldRef" : { + "fieldPath" : "metadata.namespace" + } + } + } ], + "image" : "yourDockerUsername/test-quarkus-app:1.0-SNAPSHOT", + "imagePullPolicy" : "IfNotPresent", + "name" : "test-quarkus-app" + } ] + } + } + } + } ] +} +---- + +An important thing to note about the `Deployment` is that is uses `yourDockerUsername/test-quarkus-app:1.0-SNAPSHOT` as the Docker image of the `Pod`. + +Also the `Service` is configured to use container port `8080` (which is automatically picked up by the standard Quarkus configuration). + +An important thing to keep in mind is that so far, all {project-name} has done is generate the `Kubernetes` resources, it has not applied them. The next section will walk you through how this can be done. \ No newline at end of file diff --git a/docs/src/main/asciidoc/application-configuration-guide.adoc b/docs/src/main/asciidoc/application-configuration-guide.adoc index b47dbb3345b28..c24dca7297cc0 100644 --- a/docs/src/main/asciidoc/application-configuration-guide.adoc +++ b/docs/src/main/asciidoc/application-configuration-guide.adoc @@ -1,3 +1,9 @@ +//// +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 +//// + include::./attributes.adoc[] = {project-name} - Configuring Your Application @@ -14,8 +20,6 @@ To complete this guide, you need: * JDK 1.8+ installed with `JAVA_HOME` configured appropriately * Apache Maven 3.5.3+ - - == Solution We recommend that you follow the instructions in the next sections and create the application step by step. @@ -42,7 +46,7 @@ It generates: * the Maven structure * a landing page accessible on `http://localhost:8080` -* an example of `Dockerfile` +* example `Dockerfile` files for both `native` and `jvm` modes * the application configuration file * an `org.acme.config.GreetingResource` resource * an associated test @@ -50,35 +54,33 @@ It generates: == Injecting configuration value Quarkus uses https://microprofile.io/project/eclipse/microprofile-config[MicroProfile Config] to inject the configuration in the application. -The injection uses 2 annotations: `@Inject` and `@ConfigProperty`: +The injection uses the `@ConfigProperty` annotation. [source,java] ---- -@Inject @ConfigProperty(name = "greeting.message") -private String message; +String message; ---- -Edit the `org.acme.config.GreetingResource`, and introduce the 3 following configuration properties: +NOTE: When injecting a configured value, you can use `@Inject @ConfigProperty` or just `@ConfigProperty`. +The `@Inject` annotation is not necessary for members annotated with `@ConfigProperty`, a behavior which differs from https://microprofile.io/project/eclipse/microprofile-config[MicroProfile Config] + +Edit the `org.acme.config.GreetingResource`, and introduce the following configuration properties: [source,java] ---- -@Inject -@ConfigProperty(name = "greeting.message") -private String message; +@ConfigProperty(name = "greeting.message") <1> +String message; -@Inject -@ConfigProperty(name = "greeting.suffix", defaultValue="!") -private String suffix; +@ConfigProperty(name = "greeting.suffix", defaultValue="!") <2> +String suffix; -@Inject -@ConfigProperty(name = "greeting.name") -private Optional name; +@ConfigProperty(name = "greeting.name") +Optional name; <3> ---- - -If you do not provide a value for the first property, it will have a `null` value. -The second property injects the given default value if the configuration file does not provide a value. -The third property is optional. The injected `Optional` is empty as the configuration file does not provide a value. +<1> If you do not provide a value for this property, the application startup fails with `javax.enterprise.inject.spi.DeploymentException: No config value of type [class java.lang.String] exists for: greeting.message`. +<2> The default value is injected if the configuration does not provide a value for `greeting.suffix`. +<3> This property is optional - an empty `Optional` is injected if the configuration does not provide a value for `greeting.name`. Now, modify the `hello` method to use the injected properties: @@ -148,14 +150,14 @@ public class GreetingResourceTest { == Package and run the application -Run the application with: `mvn compile quarkus:dev`. +Run the application with: `./mvnw compile quarkus:dev`. Open your browser to http://localhost:8080/greeting. Changing the configuration file is immediately reflected. You can add the `greeting.suffix`, remove the other properties, change the values, etc. -As usual, the application can be packaged using `mvn clean package` and executed using the `-runner.jar` file. -You can also generate the native executable with `mvn clean package -Pnative`. +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`. == Overriding properties at runtime @@ -165,7 +167,7 @@ To change them, make sure to repackage your application. [source,shell] -- -mvn clean package +./mvnw clean package -- Extensions do define _some_ properties as overridable at runtime. @@ -183,10 +185,18 @@ You can override these runtime properties with the following mechanisms (in decr 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] +=== Custom configuration sources + +You can also introduce custom configuration sources in the standard MicroProfile Config manner. To +do this, you must provide a class which implements either `org.eclipse.microprofile.config.spi.ConfigSource` +or `org.eclipse.microprofile.config.spi.ConfigSourceProvider`. Create a +https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html[service file] for the +class and it will be detected and installed at application startup. + == More info on how to configure {project-name} relies on Eclipse MicroProfile and inherit its features. There are converters that convert your property file content from `String` to typed Java types. See the list link:https://github.com/eclipse/microprofile-config/blob/master/spec/src/main/asciidoc/converters.asciidoc[in the specification]. -// TODO: make Ken review this section and discuss SmallRye expansion. \ No newline at end of file +// TODO: make Ken review this section and discuss SmallRye expansion. diff --git a/docs/src/main/asciidoc/application-lifecycle-events-guide.adoc b/docs/src/main/asciidoc/application-lifecycle-events-guide.adoc index a067c2f3a9b90..9ede78e44f3da 100644 --- a/docs/src/main/asciidoc/application-lifecycle-events-guide.adoc +++ b/docs/src/main/asciidoc/application-lifecycle-events-guide.adoc @@ -1,3 +1,9 @@ +//// +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 +//// + include::./attributes.adoc[] = {project-name} - Application Initialization and Termination @@ -44,7 +50,7 @@ It generates: * the Maven structure * a landing page accessible on `http://localhost:8080` -* an example of `Dockerfile` +* example `Dockerfile` files for both `native` and `jvm` modes * the application configuration file * an `org.acme.events.GreetingResource` resource * an associated test @@ -90,8 +96,8 @@ NOTE: The methods can access injected beans. Check the {quickstarts-blob-url}/ap == Package and run the application -Run the application with: `mvn compile quarkus:dev`, the logged message is printed. +Run the application with: `./mvnw compile quarkus:dev`, the logged message is printed. When the application is stopped, the second log message is printed. -As usual, the application can be packaged using `mvn clean package` and executed using the `-runner.jar` file. -You can also generate the native executable using `mvn clean package -Pnative`. \ 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 generate the native executable using `./mvnw clean package -Pnative`. \ No newline at end of file diff --git a/docs/src/main/asciidoc/async-message-passing.adoc b/docs/src/main/asciidoc/async-message-passing.adoc new file mode 100644 index 0000000000000..14c28d362c496 --- /dev/null +++ b/docs/src/main/asciidoc/async-message-passing.adoc @@ -0,0 +1,306 @@ +include::./attributes.adoc[] += {project-name} - Asynchronous messages between beans + +{project-name} allows different beans to interact using asynchronous messages, enforcing loose-coupling. +The messages are sent to _virtual addresses_. +It offers 3 types of delivery mechanism: + +- point-to-point - send the message, one consumer receives it. If several consumers listen to the address, a round robin is applied; +- publish/subscribe - publish a message, all the consumers listening to the address are receiving the message; +- request/reply - send the message and expect a response. The receiver can respond to the message in an asynchronous-fashion + +All these delivery mechanism are non-blocking, and are providing one of the fundamental brick to build reactive applications. + +NOTE: The asynchronous message passing feature allows replying to messages which is not supported by Reactive Messaging. +However, it is limited to single-event behavior (no stream) and to local messages. + +== Installing + +This mechanism uses the Vert.x EventBus, so you need to enable the `vertx` extension to use this feature. +If you are creating a new project, set the `extensions` parameter are follows: + +[source, subs=attributes+] +---- +mvn io.quarkus:quarkus-maven-plugin:{quarkus-version}:create \ + -DprojectGroupId=org.acme \ + -DprojectArtifactId=vertx-quickstart \ + -Dextensions="vertx" +---- + +If you have an already created project, the `vertx` extension can be added to an existing {project-name} project with +the `add-extension` command: + +[source] +---- +./mvnw quarkus:add-extension -Dextensions="vertx" +---- + +Otherwise, you can manually add this to the dependencies section of your `pom.xml` file: + +[source,xml] +---- + + io.quarkus + quarkus-vertx + +---- + +== Consuming events + +To consume events, use the `io.quarkus.vertx.ConsumeEvent` annotation: + +[source, java] +---- +package org.acme.vertx; + +import io.quarkus.vertx.ConsumeEvent; + +import javax.enterprise.context.ApplicationScoped; + +@ApplicationScoped +public class GreetingService { + + @ConsumeEvent // <1> + public String consume(String name) { // <2> + return name.toUpperCase(); + } +} +---- +<1> If not set, the address is the fully qualified name of the bean, for instance, in this snippet it's `org.acme.vertx.GreetingService`. +<2> The method parameter is the message body. If the method returns _something_ it's the message response. + +[IMPORTANT] +==== +By default, the code consuming the event must be _non-blocking_, as it's called on the Vert.x event loop. +If your processing is blocking, use the `blocking` arttribute: + +[source, java] +---- +@ConsumeEvent(value = "blocking-consumer", blocking = true) +void consumeBlocking(String message) { + // Something blocking +} +---- +==== + +=== Configuring the address + +The `@ConsumeEvent` annotation can be configured to set the address: + +[source, java] +---- +@ConsumeEvent("greeting") // <1> +public String consume(String name) { + return name.toUpperCase(); +} +---- +<1> Receive the messages sent to the `greeting` address + +=== Replying + +The _return_ value of a method annotated with `@ConsumeEvent` is used as response to the incoming message. +For instance, in the following snippet, the returned `String` is the response. + +[source, java] +---- +@ConsumeEvent("greeting") +public String consume(String name) { + return name.toUpperCase(); +} +---- + +You can also return a `CompletionStage` to handle asynchronous reply: + +[source, java] +---- +@ConsumeEvent("greeting") +public CompletionStage consume2(String name) { + return CompletableFuture.supplyAsync(name::toUpperCase, executor); +} +---- + +=== Implementing fire and forget interactions + +You don't have to reply to received messages. +Typically for a _fire and forget_ interaction, the messages are consumed and the sender does not need to know about it. +To implement this, your consumer method just returns `void` + +[source,java] +---- +@ConsumeEvent("greeting") +public void consume(String event) { + // Do something with the event +} +---- + +=== Dealing with messages + +As said above, this mechanism is based on the Vert.x event bus. So, you can also use `Message` directly: + +[source, java] +---- +@ConsumeEvent("greeting") +public void consume(Message msg) { + System.out.println(msg.address()); + System.out.println(msg.body()); +} +---- + +== Sending messages + +Ok, we have seen how to receive messages, let's now switch to the _other side_: the sender. +Sending and publishing messages use the Vert.x event bus: + +[source, java] +---- +package org.acme; + +import io.vertx.axle.core.eventbus.EventBus; +import io.vertx.axle.core.eventbus.Message; + +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import java.util.concurrent.CompletionStage; + +@Path("/async") +public class EventResource { + + @Inject + EventBus bus; // <1> + + @GET + @Path("/{name}") + public CompletionStage hello(String name) { + return bus.send("greeting", name) // <2> + .thenApply(Message::body); + } +} +---- +<1> Inject the Event bus +<2> Send a message to the address `greeting`. Message payload is `name` + +The `EventBus` object provides methods to: + +1. `send` a message to a specific address - one single consumer receives the message. +2. `publish` a message to a specific address - all consumers receive the messages. +3. `send` a message and expect reply + +[source, java] +---- +// Case 1 +bus.send("address", "hello"); +// Case 2 +bus.publish("address", "hello"); +// Case 3 +bus.send("address", "hello, how are you?").thenAccept(message -> { + // reponse +}); +---- + +== Putting things together - bridging HTTP and messages + +Let's revisit a greeting HTTP endpoint and use asynchronous message passing to delegate the call to a separated bean. +It uses the request/reply dispatching mechanism. +Instead of implementing the business logic inside the JAX-RS endpoint, we are sending a message. +This message is consumed by another bean and the response is sent using the _reply_ mechanism. + +First create a new project using: + +[source, subs=attributes+] +---- +mvn io.quarkus:quarkus-maven-plugin:{quarkus-version}:create \ + -DprojectGroupId=org.acme \ + -DprojectArtifactId=vertx-quickstart \ + -Dextensions="vertx" +---- + +You can already start the application in _dev mode_ using `mvn compile quarkus:dev`. + +Then, creates a new JAX-RS resource with the following content: + +[source,java] +.src/main/java/org/acme/vertx/EventResource.java +---- +package org.acme.vertx; + +import io.vertx.axle.core.eventbus.EventBus; +import io.vertx.axle.core.eventbus.Message; + +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import java.util.concurrent.CompletionStage; + +@Path("/hello") +public class EventResource { + + @Inject EventBus bus; + + @GET + @Path("/async/{name}") + public CompletionStage hello(@PathParam("name") String name) { + return bus.send("greeting", name) // <1> + .thenApply(Message::body); // <2> + } +} +---- +<1> send the `name` to the `greeting` address +<2> when we get the reply, extract the body and send this as response to the user + +If you call this endpoint, you will wait and get a timeout. Indeed, no one is listening. +So, we need a consumer listening on the `greeting` address. Create a `GreetingService` bean with the following content: + +[source, java] +.src/main/java/org/acme/vertx/GreetingService.java +---- +package org.acme.vertx; + +import io.quarkus.vertx.ConsumeEvent; + +import javax.enterprise.context.ApplicationScoped; + +@ApplicationScoped +public class GreetingService { + + @ConsumeEvent("greeting") + public String greeting(String name) { + return "Hello " + name; + } + +} +---- + +This bean receives the name, and returns the greeting message. + +Now, open your browser to http://localhost:8080/async/Quarkus, and you should see: + +[source,text] +---- +Hello Quarkus +---- + +To better understand, let's detail how the HTTP request/response has been handled: + +1. The request is received by the `hello` method +2. a message containing the _name_ is sent to the event bus +3. Another bean receives this message and computes the response +4. This response is sent back using the reply mechanism +5. Once the reply is received by the sender, the content is written to the HTTP response + +This application can be packaged using: + +[source,shell] +---- +./mvnw clean package +---- + +You can also compile it as a native executable with: + +[source, shell] +---- +./mvnw clean package -Pnative +---- + diff --git a/docs/src/main/asciidoc/building-native-image-guide.adoc b/docs/src/main/asciidoc/building-native-image-guide.adoc index 83454517b5b8d..c640723b021f4 100644 --- a/docs/src/main/asciidoc/building-native-image-guide.adoc +++ b/docs/src/main/asciidoc/building-native-image-guide.adoc @@ -1,10 +1,16 @@ +//// +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 +//// + include::./attributes.adoc[] = {project-name} - Building a Native Executable This guide covers: * Compiling the application to a native executable -* The packaging of the application in a Docker container +* The packaging of the application in a container This guide takes as input the application developed in the link:getting-started-guide.html[Getting Started Guide]. @@ -20,6 +26,7 @@ Using the community edition is enough. Version {graalvm-version} is required. * The `GRAALVM_HOME` environment variable configured appropriately * Apache Maven 3.5.3+ +* A working C developer environment (see the note below for details) * A running Docker * The code of the application developed in the link:getting-started-guide.html[Getting Started Guide]. @@ -34,7 +41,13 @@ On MacOS, point the variable to the `Home` sub-directory: `export GRAALVM_HOME=$HOME/Development/graalvm/Contents/Home/` ==== +[NOTE] +==== +What does having a working C developer environment mean? + * On Linux, you will need GCC, the glibc and zlib headers (on common distributions: `sudo dnf install gcc glibc-devel zlib-devel` or `sudo apt-get install build-essential libz-dev`). + * On macOS, execute `xcode-select --install`. +==== == Solution @@ -87,10 +100,10 @@ If you have generated the application from the previous tutorial, you can find i We use a profile because, you will see very soon, packaging the native executable takes a _few_ seconds. -Create a native executable using: `mvn package -Pnative`. +Create a native executable using: `./mvnw package -Pnative`. -In addition to the regular files, the build also produces `target/quarkus-quickstart-runner`. -You can run it using: `./target/quarkus-quickstart-runner`. +In addition to the regular files, the build also produces `target/getting-started-1.0-SNAPSHOT-runner`. +You can run it using: `./target/getting-started-1.0-SNAPSHOT-runner`. == Testing the native executable @@ -103,7 +116,7 @@ In the `pom.xml` file, the `native` profile contains: org.apache.maven.plugins maven-failsafe-plugin - ${surefire.version} + ${surefire-plugin.version} @@ -142,59 +155,75 @@ public class NativeGreetingResourceIT extends GreetingResourceTest { // <2> The executable is retrieved using the `native.image.path` system property configured in the _Failsafe Maven Plugin_. <2> We extend our previous tests, but you can also implement your tests -To see the `NativeGreetingResourceIT` run against the native executable, runs `mvn verify -Pnative`: +To see the `NativeGreetingResourceIT` run against the native executable, use `./mvnw verify -Pnative`: [source,shell] ---- -mvn verify -Pnative +./mvnw verify -Pnative ... -[quarkus-quickstart-runner:50955] universe: 391.96 ms -[quarkus-quickstart-runner:50955] (parse): 904.37 ms -[quarkus-quickstart-runner:50955] (inline): 1,143.32 ms -[quarkus-quickstart-runner:50955] (compile): 6,228.44 ms -[quarkus-quickstart-runner:50955] compile: 9,130.58 ms -[quarkus-quickstart-runner:50955] image: 2,101.42 ms -[quarkus-quickstart-runner:50955] write: 803.18 ms -[quarkus-quickstart-runner:50955] [total]: 33,520.15 ms +[getting-started-1.0-SNAPSHOT-runner:18820] universe: 587.26 ms +[getting-started-1.0-SNAPSHOT-runner:18820] (parse): 2,247.59 ms +[getting-started-1.0-SNAPSHOT-runner:18820] (inline): 1,985.70 ms +[getting-started-1.0-SNAPSHOT-runner:18820] (compile): 14,922.77 ms +[getting-started-1.0-SNAPSHOT-runner:18820] compile: 20,361.28 ms +[getting-started-1.0-SNAPSHOT-runner:18820] image: 2,228.30 ms +[getting-started-1.0-SNAPSHOT-runner:18820] write: 364.35 ms +[getting-started-1.0-SNAPSHOT-runner:18820] [total]: 52,777.76 ms [INFO] -[INFO] --- maven-failsafe-plugin:2.22.0:integration-test (default) @ quarkus-quickstart-native --- +[INFO] --- maven-failsafe-plugin:2.22.1:integration-test (default) @ getting-started --- [INFO] [INFO] ------------------------------------------------------- [INFO] T E S T S [INFO] ------------------------------------------------------- -[INFO] Running org.acme.quickstart.GreetingResourceIT -Executing [.../getting-started/target/quarkus-quickstart-runner, -Dquarkus.http.port=8081, -Dtest.url=http://localhost:8081, -Dquarkus.log.file.path=target/quarkus.log] -2019-02-28 16:52:42,020 INFO [io.quarkus] (main) Quarkus started in 0.007s. Listening on: http://localhost:8080 -2019-02-28 16:52:42,021 INFO [io.quarkus] (main) Installed features: [cdi, resteasy] -[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.081 s - in org.acme.quickstart.GreetingResourceIT -[INFO] -[INFO] Results: -[INFO] -[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0 - +[INFO] Running org.acme.quickstart.NativeGreetingResourceIT +Executing [/data/home/gsmet/git/quarkus-quickstarts/getting-started/target/getting-started-1.0-SNAPSHOT-runner, -Dquarkus.http.port=8081, -Dtest.url=http://localhost:8081, -Dquarkus.log.file.path=build/quarkus.log] +2019-04-15 11:33:20,348 INFO [io.quarkus] (main) Quarkus 999-SNAPSHOT started in 0.002s. Listening on: http://[::]:8081 +2019-04-15 11:33:20,348 INFO [io.quarkus] (main) Installed features: [cdi, resteasy] +[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.387 s - in org.acme.quickstart.NativeGreetingResourceIT ... ---- -== Producing a Docker container +=== Excluding tests when running as a native executable + +When running tests this way, the only things that actually run natively are you application endpoints, which +you can only test via HTTP calls. Your test code does not actually run natively, so if you are testing code +that does not call your HTTP endpoints, it's probably not a good idea to run them as part of native tests. -IMPORTANT: Before going further, be sure to have a working Docker environment. +If you share your test class between JVM and native execusions like we advise above, you can mark certain tests +with the `@DisabledOnSubstrate` annotation in order to only run them on the JVM. -You can run the application in a Docker container using the JAR produced by the Quarkus Maven Plugin. -However, in this guide we focus on creating a Docker image using the produced native executable. +== Producing a container + +IMPORTANT: Before going further, be sure to have a working container runtime (Docker, podman) environment. + +You can run the application in a container using the JAR produced by the Quarkus Maven Plugin. +However, in this guide we focus on creating a container image using the produced native executable. image:containerization-process.png[Containerization Process] By default, the native executable is tailored for your operating system (Linux, macOS, Windows etc). -Because the Docker container may not use the same _executable_ format as the one produced by your operating system, -we will instruct the Maven build to produce an executable from inside a Docker container: +Because the container may not use the same _executable_ format as the one produced by your operating system, +we will instruct the Maven build to produce an executable from inside a container: +[source, shell] +---- +./mvnw package -Pnative -Dnative-image.docker-build=true +---- + +[TIP] +==== +You can also select the container runtime to use with: [source,shell] ---- -mvn package -Pnative -Dnative-image.docker-build=true +# Docker +./mvnw package -Pnative -Dnative-image.container-runtime=docker +# Podman +./mvnw package -Pnative -Dnative-image.container-runtime=podman ---- +==== The produced executable will be a 64 bit Linux executable, so depending on your operating system it may no longer be runnable. -However, it's not an issue as we are going to copy it to a Docker container. -The project generation has provided a `Dockerfile` in the `src/main/docker` directory with the following content: +However, it's not an issue as we are going to copy it to a container. +The project generation has provided a `Dockerfile.native` in the `src/main/docker` directory with the following content: [source,dockerfile] ---- @@ -210,17 +239,17 @@ Then, if you didn't delete the generated native executable, you can build the do [source,shell] ---- -docker build -f src/main/docker/Dockerfile -t quarkus-quickstart/quickstart . +docker build -f src/main/docker/Dockerfile.native -t quarkus-quickstart/getting-started . ---- And finally, run it with: [source,shell] ---- -docker run -i --rm -p 8080:8080 quarkus-quickstart/quickstart +docker run -i --rm -p 8080:8080 quarkus-quickstart/getting-started ---- -NOTE: Interested by tiny Docker images, check the {quarkus-tree-url}/docker/distroless[distroless] version. +NOTE: Interested by tiny Docker images, check the {quarkus-images-url}/graalvm-{graalvm-version}/distroless[distroless] version. == What's next? diff --git a/docs/src/main/asciidoc/building-substrate-howto.adoc b/docs/src/main/asciidoc/building-substrate-howto.adoc index 05e140db2d3ee..0c2b0b471c187 100644 --- a/docs/src/main/asciidoc/building-substrate-howto.adoc +++ b/docs/src/main/asciidoc/building-substrate-howto.adoc @@ -1,3 +1,9 @@ +//// +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 +//// + include::./attributes.adoc[] = {project-name} - Building a Custom SubstrateVM @@ -18,20 +24,24 @@ After obtaining the download install it and set `JAVA_HOME` [source,shell] ---- -wget -O jvmci.tgz http://download.oracle.com/otn/utilities_drivers/oracle-labs/labsjdk-8u172-jvmci-0.46-darwin-amd64.tar.gz?AuthParam=[GENERATED AUTH TOKEN HERE] +wget -O jvmci.tgz http://download.oracle.com/otn/utilities_drivers/oracle-labs/labsjdk-8u202-jvmci-0.56-darwin-amd64.tar.gz?AuthParam=[GENERATED AUTH TOKEN HERE] tar -xzvf jvmci.tgz -C /opt -# On Mac do labsjdk1.8.0_172-jvmci-0.46/Contents/Home -export JAVA_HOME=/opt/labsjdk1.8.0_172-jvmci-0.46/ +# On Mac do labsjdk1.8.0_202-jvmci-0.56/Contents/Home +export JAVA_HOME=/opt/labsjdk1.8.0_202-jvmci-0.46/ export PATH=$JAVA_HOME/bin:$PATH ---- +.NOTE +Get the latest version available as graal tends to keep updating +the minimum requirements. + === Install MX Now you need to install Graal’s special build tool, `mx`. [source,shell] ---- -git clone git@github.com:graalvm/mx.git +git clone https://github.com/graalvm/mx.git export PATH=`pwd`/mx:$PATH ---- @@ -41,7 +51,7 @@ You can now check-out and build Substrate: [source,shell] ---- -git clone git clone git@github.com:oracle/graal.git +git clone https://github.com/oracle/graal.git cd graal/substratevm mx build ---- diff --git a/docs/src/main/asciidoc/cdi-reference.adoc b/docs/src/main/asciidoc/cdi-reference.adoc index a531169854af0..a9f550564f15e 100644 --- a/docs/src/main/asciidoc/cdi-reference.adoc +++ b/docs/src/main/asciidoc/cdi-reference.adoc @@ -1,3 +1,9 @@ +//// +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 +//// + include::./attributes.adoc[] = {project-name} - Contexts and Dependency Injection @@ -71,7 +77,7 @@ public class CounterBean { } @Inject - CounterBean(CounterService service) { <2> + CounterBean(CounterService service) { // <2> this.service = service; } } @@ -95,7 +101,7 @@ public class CounterBean { ** Type-safe resolution ** Programmatic lookup via `javax.enterprise.inject.Instance` ** Client proxies -** Injection point metadata footnote:[`InjectionPoint.getMember()` is currently not supported.] +** Injection point metadata * Scopes and contexts ** `@Dependent`, `@ApplicationScoped`, `@Singleton`, `@RequestScoped` and `@SessionScoped` ** Custom scopes and contexts @@ -274,4 +280,4 @@ An unused bean: * does not declare any producer which is eligible for injection to any injection point, * is not directly eligible for injection into any `javax.enterprise.inject.Instance` or `javax.inject.Provider` injection point -The extensions can eliminate possible false positives by producing `UnremovableBeanBuildItem`. \ No newline at end of file +The extensions can eliminate possible false positives by producing `UnremovableBeanBuildItem`. diff --git a/docs/src/main/asciidoc/cli-tooling.adoc b/docs/src/main/asciidoc/cli-tooling.adoc index b9f720b62f2e4..02987d7895335 100644 --- a/docs/src/main/asciidoc/cli-tooling.adoc +++ b/docs/src/main/asciidoc/cli-tooling.adoc @@ -1,3 +1,9 @@ +//// +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 +//// + include::./attributes.adoc[] = Building {project-name} apps with {project-name} Command Line Interface (CLI) @@ -65,7 +71,7 @@ $ quarkus create-project hello-world This will create a folder called 'hello-world' in your current working directory using default groupId, artifactId and version values -(groupId='com.acme', artifactId='quarkus' and version='1.0.0-SNAPSHOT'). +(groupId='org.acme', artifactId='quarkus' and version='1.0.0-SNAPSHOT'). To specify the groupId, artifactId and version values, use the '--groupid', '--artifactid' and '--version' options: diff --git a/docs/src/main/asciidoc/datasource-guide.adoc b/docs/src/main/asciidoc/datasource-guide.adoc new file mode 100644 index 0000000000000..ee35258138e9c --- /dev/null +++ b/docs/src/main/asciidoc/datasource-guide.adoc @@ -0,0 +1,208 @@ +//// +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 +//// + +include::./attributes.adoc[] += {project-name} - Datasources + +Many projects that use data require connections to a database. +The main way of obtaining connections to a database is to use a datasource. + +In {project-name}, the out of the box datasource and connection pooling implementation is https://agroal.github.io/[Agroal]. + +This guide will explain how to: + +* configure a datasource, or multiple datasources +* how to obtain a reference to those datasources in code + +== 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+ + +== 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=agroal-datasources\ + -DclassName="org.acme.datasource.GreetingResource" \ + -Dpath="/hello" +---- + +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.datasource.GreetingResource` resource +* an associated test + +== Adding maven dependencies + +Next, you will need to add the `quarkus-agroal` dependency to your project. + + +You can add it using a simple Maven command: + +[source,shell] +---- +./mvnw quarkus:add-extension -Dextensions="agroal" +---- + +[NOTE] +==== +Agroal comes as a transitive dependency of the Hibernate ORM extension so if you are using Hibernate ORM, +you don't need to add the Agroal extension dependency explicitly. +==== + +You will also need to add the database connector library of choice. + +Quarkus provides driver extensions for: + +* H2 - jdbc-h2 +* PostgreSQL - jdbc-postgresql +* MariaDB (and MySQL) - jdbc-mariadb +* Microsoft SQL Server - jdbc-mssql + +[NOTE] +==== +In JVM mode, simply adding your driver of choice is sufficient. +Extensions are mostly useful to support GraalVM native images. +==== + +As usual, you can install the extension using `add-extension`. + +To install the PostgreSQL driver dependency for instance, just run the following command: + +[source,shell] +---- +./mvnw quarkus:add-extension -Dextensions="jdbc-postgresql" +---- + +== Configuring the datasource + +Once the dependencies are added to your pom.xml file, you'll need to configure Agroal. + +This is done in the `src/main/resources/application.properties` file. + +A viable configuration file would be: + +[source,properties] +-- +include::../../../../extensions/agroal/deployment/src/test/resources/application-default-datasource.properties[tag=basic] +-- + +There are other configuration options, detailed below. + +== Injecting a Datasource + +Because {project-name} uses CDI, injecting a datasource is very simple: + +[source,java,indent=0] +-- +include::../../../../extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/DefaultDataSourceConfigTest.java[tag=injection] +-- + +In the above example, the type is `AgroalDataSource` which is a subtype of `javax.sql.DataSource`. +Because of this, you can also use `javax.sql.DataSource`. + +== Multiple Datasources + +Agroal allows you to configure multiple datasources. +It works exactly the same way as a single datasource, with one important change: a name. + +[source,properties] +-- +include::../../../../extensions/agroal/deployment/src/test/resources/application-multiple-datasources.properties[] +-- + +Notice there's an extra bit in the key. +The syntax is as follows: `quarkus.datasource.[optional name.][datasource property]`. + +=== Named Datasource Injection + +When using multiple datasources, each `DataSource` also has the `io.quarkus.agroal.DataSource` qualifier with the name of the datasource in the property as the value. +Using the above properties to configure three different datasources, you can also inject each one as follows: + +[source,java,indent=0] +-- +include::../../../../extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/MultipleDataSourcesConfigTest.java[tag=injection] +-- + +== Agroal Configuration Reference + +|=== +|Configuration key|Java type|Example + +|quarkus.datasource.driver +|String (class name) +|org.h2.Driver, com.mysql.jdbc.Driver + +|quarkus.datasource.url +|String (JDBC Connection URL) +|jdbc:h2:tcp://localhost/mem:default, jdbc:mysql://hostname:port/dbname + +|quarkus.datasource.username +|String +|Fred, Bill, inventory_user + +|quarkus.datasource.password +|String +|correct horse battery staple, Tr0ub4dor&3 + + +|quarkus.datasource.min-size +|Integer +|5, 12, 42 + +|quarkus.datasource.max-size +|Integer +|5, 12, 42 + +|quarkus.datasource.initial-size +|Integer +|5, 12, 42 + +|quarkus.datasource.background-validation-interval +|java.time.Duration +|PT3M, PT56S, + +|quarkus.datasource.acquisition-timeout +|java.time.Duration +|PT3M, PT56S - see note below + +|quarkus.datasource.leak-detection-interval +|java.time.Duration +|PT3M, PT56S - see note below + +|quarkus.datasource.idle-removal-interval +|java.time.Duration +|PT3M, PT56S - see note below + +|quarkus.datasource.transaction-isolation-level +|io.quarkus.agroal.runtime.TransactionIsolationLevel (enum) +|none, read-committed, read-uncommitted, repeatable-read, serializable + +|quarkus.datasource.xa +|Boolean +|True or False +|=== + +[NOTE] +==== +The format for durations uses the standard `java.time` format. + +You can learn more about it in the link:https://docs.oracle.com/javase/8/docs/api/java/time/Duration.html#parse-java.lang.CharSequence-[Duration#parse() javadoc]. +==== + diff --git a/docs/src/main/asciidoc/extension-authors-guide.adoc b/docs/src/main/asciidoc/extension-authors-guide.adoc index ef92dce222e53..b4de7c3d6a653 100644 --- a/docs/src/main/asciidoc/extension-authors-guide.adoc +++ b/docs/src/main/asciidoc/extension-authors-guide.adoc @@ -1,16 +1,112 @@ -include::./attributes.adoc[] -= {project-name} - Writing Your Own Extension +//// +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 +//// += {project-name} - Writing Your Own Extension :numbered: :sectnums: :sectnumlevels: 4 +:toc: + +include::./attributes.adoc[] Quarkus extensions add a new developer focused behavior to the core offering, and consist of two distinct parts, buildtime augmentation and runtime container. The augmentation part is responsible for all metadata processing, such as reading annotations, XML descriptors etc. The output of this augmentation phase is recorded bytecode which is responsible for directly instantiating the relevant runtime services. This means that metadata is only processed once at build time, which both saves on startup time, and also on memory usage as the classes etc that are used for processing are not loaded (or even present) in the runtime JVM. -== Three Phases of Bootstrap and Quarkus Philosophy +== Extension philosophy + +This section is a work in progress and gathers the philosophy under which extensions should be designed and written. + +=== Why an extension framework + +Quarkus’s mission is to transform your entire application including the libraries it uses, into an artifact optimized for GraalVM. +To do this you need to analyze and understand the full "closed world" of the application. +Without the full and complete context, the best that can be achieved is partial and limited generic support. +By using the Quarkus extension approach, we can bring Java applications in line with memory footprint constrained environments like Kubernetes or cloud platforms. + +The Quarkus extension framework results in significantly improved resource utilization even when GraalVM is not used (e.g. in HotSpot). +Let’s list the actions an extension performs: + +* Gather build time metadata and generate code +** This part has nothing to do with GraalVM, it is how Quarkus starts frameworks “at build time” +** The extension framework facilitates reading metadata, scanning classes as well as generating classes as needed +** A small part of the extension work is executed at runtime via the generated classes, while the bulk of the work is done at build time (called deployment time) +* Enforce opinionated and sensible defaults based on the close world view of the application (e.g. an application with no `@Entity` does not need to start Hibernate ORM) +* An extension hosts Substrate VM code substitution so that libraries can run on GraalVM +** Most changes are pushed upstream to help the underlying library run on GraalVM +** Not all changes can be pushed upstream, extensions host Substrate VM substitutions - which is a form of code patching - so that libraries can run +* Host Substrate VM code substitution to help dead code elimination based on the application needs +** This is application dependant and cannot really be shared in the library itself +** For example, Quarkus optimizes the Hibernate code because it knows it only needs a specific connection pool and cache provider +* Send metadata to GraalVM for example classes in need of reflection +** This information is not static per library (e.g. Hibernate) but the framework has the semantic knowledge and knows which classes need to have reflection (for example @Entity classes) + +=== Favor build time work over runtime work + +As much as possible favor doing work at build time (deployment part of the extension) as opposed to let the framework do work at startup time (runtime). +The more is done there, the smaller Quarkus applications using that extension will be and the faster they will load. + +=== How to expose configuration + +Quarkus simplifies the most common usages. +This means that its defaults might be different than the library it integrates. + +To make the simple experience easiest, unify the configuration in `application.properties` via MicroProfile Config. +Avoid library specific configuration files, or at least make them optional: e.g. `persistence.xml` for Hibernate ORM is optional. + +Extensions should see the configuration holistically as a Quarkus application instead of focusing on the library experience. +For example `quarkus.database.url` and friends are shared between extensions as defining a database access is a shared task (instead of a `hibernate.` property for example). +The most useful configuration options should be exposed as `quarkus.[extension].` instead of the natural namespace of the library. +Less common properties can live in the library namespace. + +To fully enable the close world assumptions that Quarkus can optimize best, it is better to consider configuration options as build time settled vs overridable at runtime. +Of course properties like host, port, password should be overridable at runtime. +But many properties like enable caching or setting the JDBC driver can safely require a rebuild of the application. + +//// +=== API + +TODO: Describe where to put APIs +I wonder if that content should be in the technical aspects + +=== Substitution and template + +TODO: Describe where Substitutions and templates should live +//// + +=== Expose your components via CDI + +Since CDI is the central programming model when it comes to component composition, frameworks should expose producers that are easily consumable by user applications. + +For example, Hibernate ORM exposes `EntityManagerFactory` and `EntityManager` beans, the connection pool exposes `DataSource` beans etc. +An extension must generate these bean definitions at build time. + +=== Some types of extensions + +There exist multiple stereotypes of extension, let's list a few. + +Bare library running:: +This is the less sophisticated extension. +It consists of a set of patches to make sure a library runs on GraalVM. +If possible, contribute these patches upstream, not in extensions. +Second best is to write Substrate VM substitutions, which are patches applied during native image compilation. + +Get a framework running:: +A framework at runtime typically reads configuration, scan the classpath and classes for metadata (annotations, getters etc), build a metamodel on top of which it runs, find options via the service loader pattern, prepare invocation calls (reflection), proxy interfaces, etc. + +These operations should be done at build time and the metamodel be passed to the template DSL that will generate classes are runtime and boot the framework. + +Get a CDI portable extension running:: +The CDI portable extension model is very flexible. +Too flexible to benefit from the build time boot promoted by Quarkus. +Most extension we have seen do not make use of these extreme flexibilities capabilities. +The way to port a CDI extension to Quarkus is to rewrite it as a Quarkus extension which will define the various beans at build time (deployment time in extension parley). + +== Technical aspect + +=== Three Phases of Bootstrap and Quarkus Philosophy There are three distinct bootstrap phases of a Quarkus app: @@ -50,7 +146,7 @@ Pushing as much as possible into the `@Record(STATIC_INIT)` phase allows for two that method. If config is read at runtime, Substrate cannot reason about the contents of the config and so needs to keep all features in case they are required. -== Maven setup +=== Maven setup Your extension project should be setup as a multi-module project with two submodules: @@ -58,14 +154,13 @@ Your extension project should be setup as a multi-module project with two submod 2. A runtime submodule that contains the runtime behavior that will provide the extension behavior in the native executable or runtime JVM. -Your runtime artifact should depend on quarkus-core-runtime, and possibly the runtime artifacts of other Quarkus -modules if you want to use functionality provided by them. You will also need to include the `maven-dependency-plugin` -to write out the needed runtime dependencies, if you are using the Quarkus parent pom it will automatically -inherit the correct configuration. +Your runtime artifact should depend on `io.quarkus:quarkus-core`, and possibly the runtime artifacts of other Quarkus +modules if you want to use functionality provided by them. +You will also need to include the `io.quarkus:quarkus-bootstrap-maven-plugin` to generate the Quarkus extension descriptor included into the runtime artifact, if you are using the Quarkus parent pom it will automatically inherit the correct configuration. +Futhermore, you'll need to configure the `maven-compiler-plugin` to detect the `quarkus-extension-processor` annotation processor. -Note that at present by convention the runtime artifact will have the `-runtime` suffix, and the deployment time artifact -has no suffix (and is what the end user adds to their project). In the near future this will change, so the `-runtime` -artifact will loose its suffix, and the deployment time artifacts will have one added. +NOTE: By convention the deployment time artifact has the `-deployment` suffix, and the runtime artifact +has no suffix (and is what the end user adds to their project). [source%nowrap,xml] ---- @@ -73,42 +168,85 @@ artifact will loose its suffix, and the deployment time artifacts will have one io.quarkus - quarkus-core-runtime + quarkus-core - maven-dependency-plugin + io.quarkus + quarkus-bootstrap-maven-plugin + + + + + extension-descriptor + + + ${project.groupId}:${project.artifactId}-deployment:${project.version} + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + + + ---- +NOTE: The above `maven-compiler-plugin` configuration requires version 3.5+. + [WARNING] ==== Under no circumstances can the runtime module depend on a deployment artifact. This would result in pulling all the deployment time code into runtime scope, which defeats the purpose of having the split. ==== -Your deployment time module should depend on `quarkus-core`, your runtime artifact, -and possibly the deployment artifacts of other Quarkus modules if you want to use functionality provided by them. - +Your deployment time module should depend on `io.quarkus:quarkus-core-deployment`, your runtime artifact, +and possibly the deployment artifacts of other Quarkus modules if you want to use functionality provided by them. +You will also need to configure the `maven-compiler-plugin` to detect the `quarkus-extension-processor` annotation processor. [source%nowrap,xml] ---- - - io.quarkus - quarkus-core - ----- + + + io.quarkus + quarkus-core-deployment + + -NOTE: For historical reasons the `augment` step is still called `deployment`, this will likely remain until we do our big rename. + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + + + + + + +---- -== Build Step Processors +=== Build Step Processors -Work is done at deployment time by producing and consuming instances of `org.jboss.builder.item.BuildItem`. This is done +Work is done at deployment time by producing and consuming instances of `io.quarkus.builder.item.BuildItem`. This is done by creating a class that has method(s) annotated with `io.quarkus.deployment.annotations.BuildStep`. These classes can consume items by injection, and produce items by either returning them from the method or by injecting an instance of `io.quarkus.deployment.annotations.BuildProducer` for the produced type. These processors can also record @@ -143,7 +281,7 @@ and the resulting code does not have permission to inject private fields or invo `BuildItem` instances should be immutable, as the producer/consumer model does not allow for mutation to be correctly ordered. This is not enforced but failure to adhere to this can result in race conditions. -=== Capabilities +==== Capabilities The `@BuildStep` annotation has a `providesCapabilities` property that can be used to provide capability information to other extensions about what is present in the current application. Capabilities are simply strings that are used to @@ -155,14 +293,14 @@ To check if a capability is present you can inject the `io.quarkus.deployment.Ca Capabilities should be used when checking for the presence of an extension rather than class path based checks. -=== Application Archives +==== Application Archives The `@BuildStep` annotation can also register marker files that determine which archives on the class path are considered to be 'Application Archives', and will therefore get indexed. This is done via the `applicationArchiveMarkers`. For example the ArC extension registers `META-INF/beans.xml`, which means that all archives on the class path with a `beans.xml` file will be indexed. -== Configuration +=== Configuration Configuration in Quarkus is based on SmallRye Config, an implementation of the MicroProfile Config specification. All of the standard features of MP-Config are supported; in addition, there are several extensions which are made available @@ -172,7 +310,7 @@ The value of these properties is configured in a `application.properties` file t Configuration of Quarkus extensions is injection-based, using annotations. -=== Configuration Keys +==== Configuration Keys Leaf configuration keys are mapped to non-`private` fields via the `@io.quarkus.runtime.annotations.ConfigItem` annotation. @@ -191,7 +329,7 @@ The name can also be explicitly specified by giving a `name` attribute to the `@ NOTE: Though it is possible to override the configuration key name using the `name` attribute of `@ConfigItem`, normally this should only be done in cases where (for example) the configuration key name is the same as a Java keyword. -=== Configuration Value types +==== Configuration Value types The type of the field with the `@ConfigItem` annotation determines the conversion that is applied to it. Quarkus extensions may use the full range of configuration types made available by SmallRye Config, which includes: @@ -209,18 +347,18 @@ class. Though these implicit converters use reflection, Quarkus will automatically ensure that they are loaded at the appropriate time. -=== Configuration Groups +==== 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. -=== Configuration Maps +==== 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 map *must* be `String`, and its value may be either a configuration group class or a valid leaf type. The configuration key segment following the map's key segment will be used as the key for map values. -=== Configuration Roots +==== Configuration Roots Configuration roots are configuration groups that appear in the root of the configuration tree. A configuration property's full name is determined by joining the string `quarkus.` with the hyphenated name of the fields that form the path from the root to the @@ -235,7 +373,7 @@ Note: The current implementation is still using injection site to determine the is recommended that the injection site (field or parameter) have the same name as the configuration root class until this change is complete. -==== Configuration Root Phases +===== Configuration Root Phases 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: @@ -280,7 +418,7 @@ A configuration root dictates when its contained keys are read from configuratio For all cases other than the `BUILD_TIME` case, the configuration root class and all of the configuration groups and types contained therein must be located in, or reachable from, the extension's run time artifact. Configuration roots of phase `BUILD_TIME` may be located in or reachable from either of the extension's run time or deployment artifacts. -=== Configuration Example +==== Configuration Example [source%nowrap,java] ---- @@ -330,7 +468,7 @@ public class LogConfiguration { /** * Configuration properties for the logging file handler. */ - File file; + FileConfig file; } public class LoggingProcessor { @@ -357,7 +495,7 @@ quarkus.log.file.level=DEBUG quarkus.log.file.path=/tmp/debug.log ---- -== Bytecode Recording +=== Bytecode Recording One of the main outputs of the build process is recorded bytecode. This bytecode actually sets up the runtime environment. For example, in order to start Undertow, the resulting application will have some bytecode that directly registers all Servlet instances and then starts Undertow. @@ -387,7 +525,7 @@ control the order that generated bytecode is run. In the example above we know t `ServletExtensionBuildItem` will be run before the bytecode that consumes it. -=== RecorderContext +==== RecorderContext `io.quarkus.deployment.recording.RecorderContext` provides some convenience methods to enhance bytecode recording, this includes the ability to register creation functions for classes without no-arg constructors, to register an object @@ -401,8 +539,10 @@ convenience to avoid the need to explicitly load classes in the templates. TODO: config integration +=== CDI Extension Points +As a CDI based runtime, {project-name} extensions often make CDI beans available as part of the extension behavior. The link:cdi-reference.html[Build Time Extension Points] describes the `BuildItems` you can make use of in your extension build steps to achieve this. -== Testing Extensions +=== Testing Extensions Testing of extensions should be done with the `io.quarkus.test.QuarkusUnitTest` runner. This runner allows for Arquillian-style tests that test specific functionalities. It is not intended for testing user applications, as this @@ -517,7 +657,7 @@ public class PersistenceAndQuarkusConfigTest { <1> This tells JUnit that the {project-name} deployment should fail with a specific exception -== Native Executable Support +=== Native Executable Support There Quarkus provides a lot of build items that control aspects of the native executable build. This allows for extensions to programmatically perform tasks such as registering classes for reflection or adding static resources to the native @@ -544,19 +684,20 @@ A class that will be initialized at runtime rather than build time. This will ca `io.quarkus.deployment.builditem.substrate.SubstrateConfigBuildItem`:: A convenience feature that allows you to control most of the above features from a single build item. -== IDE support tips +=== IDE support tips -=== Writing {project-name} extensions in Eclipse +==== Writing {project-name} extensions in Eclipse The only particular aspect of writing {project-name} extensions in Eclipse is that APT (Annotation Processing Tool) is required as part of extension builds, which means you need to: - Install `m2e-apt` from https://marketplace.eclipse.org/content/m2e-apt - Define this property in your `pom.xml`: `jdt_apt`, although if you rely on `io.quarkus:quarkus-build-parent` you will get it for free. -- If you have the `io.quarkus:quarkus-extension-processor` open at the same time in your IDE (for example, if you have the {project-name} sources checked out and open in your IDE) you will need to close that project. Otherwise, Eclipse will not invoke the APT plugin that it contains. +- If you have the `io.quarkus:quarkus-extension-processor` project open at the same time in your IDE (for example, if you have the {project-name} sources checked out and open in your IDE) you will need to close that project. Otherwise, Eclipse will not invoke the APT plugin that it contains. +- If you just closed the extension processor project, be sure to do `Maven > Update Project` on the other projects in order for Eclipse to pick up the extension processor from the Maven repository. -== Troubleshooting / Debugging Tips +=== Troubleshooting / Debugging Tips -=== Saving Application Generated Classes to Disk +==== Saving Application Generated Classes to Disk The class augmentation step of {project-name} generates classes for various purposes. Sometimes you need to view these classes/bytecode to debug or understand an issue. Classes that are related to application augmentation are currently held in @@ -565,5 +706,550 @@ memory in a runtime class loader. To have these classes written out to disk for [source,shell] ---- -mvn clean install -Dquarkus.debug.generated-classes-dir=./target/app-generated-classes +./mvnw clean install -Dquarkus.debug.generated-classes-dir=./target/app-generated-classes +---- + + +=== Sample Test Extension +We have an extension that is used to test for regressions in the extension processing. It is located in {quarkus-tree-url}/core/test-extension directory. In this section we touch on some of the tasks an extension +author will typically need to perform using the test-extension code to illustrate how the task could be done. + +==== Features and Capabilities +When you start a {project-name} instance, you will see a line like the one highlighted in this example: + +.Example Startup Lines +[source, bash] +---- +2019-03-22 14:02:37,884 INFO [io.quarkus] (main) Quarkus 999-SNAPSHOT started in 0.061s. +2019-03-22 14:02:37,884 INFO [io.quarkus] (main) Installed features: [cdi, test-extension] <1> +---- +<1> A list of features installed in the runtime image + +The features listed reflect the types of extensions that are installed. An extension declares its display name + using a <> method that produces a `FeatureBuildItem` like this TestProcessor#featureBuildItem() method example: + + +.TestProcessor#featureBuildItem() +[source,java] +---- + /** + * Register a extension capability and feature + * + * @return test-extension feature build item + */ + @BuildStep(providesCapabilities = "io.quarkus.test-extension") + FeatureBuildItem featureBuildItem() { + return new FeatureBuildItem("test-extension"); + } +---- + +The feature name should map to a label in the extension's devtools/common/src/main/filtered/extensions.json entry so that +the feature name displayed by the startup line matches a label that one can used to select the extension when creating a project +using the {project-name} maven plugin as shown in this example taken from the Writing JSON REST Services guide where the "resteasy-jsonb" feature is referenced: + +[source,shell,subs=attributes+] +---- +mvn io.quarkus:quarkus-maven-plugin:{quarkus-version}:create \ + -DprojectGroupId=org.acme \ + -DprojectArtifactId=rest-json \ + -DclassName="org.acme.rest.json.FruitResource" \ + -Dpath="/fruits" \ + -Dextensions="resteasy-jsonb" +---- + +==== Bean Defining Annotations +The CDI layer processes CDI beans that are either explicitly registered or that it discovers based on bean defining annotations as defined in http://docs.jboss.org/cdi/spec/2.0/cdi-spec.html#bean_defining_annotations">[2.5.1. Bean defining annotations]. You can expand this set of annotations to include annotations your extension processes using a `BeanDefiningAnnotationBuildItem` as shown in this `TestProcessor#registerBeanDefinningAnnotations` example: + +.Register a Bean Definining Annotation +[source,java] +---- +import javax.enterprise.context.ApplicationScoped; +import org.jboss.jandex.DotName; +import io.quarkus.extest.runtime.TestAnnotation; + +public final class TestProcessor { + static DotName TEST_ANNOTATION = DotName.createSimple(TestAnnotation.class.getName()); + static DotName TEST_ANNOTATION_SCOPE = DotName.createSimple(ApplicationScoped.class.getName()); + +... + + @BuildStep + BeanDefiningAnnotationBuildItem registerX() { + <1> + return new BeanDefiningAnnotationBuildItem(TEST_ANNOTATION, TEST_ANNOTATION_SCOPE); + } +... +} + +/** + * Marker annotation for test configuration target beans + */ +@Target({ TYPE }) +@Retention(RUNTIME) +@Documented +@Inherited +public @interface TestAnnotation { +} + +/** + * A sample bean + */ +@TestAnnotation <2> +public class ConfiguredBean implements IConfigConsumer { + +... +---- +<1> Register the annotation class and CDI default scope using the Jandex `DotName` class. +<2> `ConfiguredBean` will be processed by the CDI layer the same as a bean annotated with the CDI standard @ApplicationScoped. + +==== Parsing Config to Objects +One of the main things an extension is likely to do is completely separate the configuration phase of behavior from the runtime phase. Frameworks often do parsing/load of configuration on startup that can be done during build time to both reduce the runtime dependencies on frameworks like xml parsers as well as reducing the startup time the parsing incurs. + +An example of parsing a XML config file using JAXB is shown in the `TestProcessor#parseServiceXmlConfig` method: +.Parsing an XML Configuration into Runtime XmlConfig Instance +[source,java] +---- + @BuildStep + @Record(STATIC_INIT) + RuntimeServiceBuildItem parseServiceXmlConfig(TestTemplate template) throws JAXBException { + RuntimeServiceBuildItem serviceBuildItem = null; + JAXBContext context = JAXBContext.newInstance(XmlConfig.class); + Unmarshaller unmarshaller = context.createUnmarshaller(); + InputStream is = getClass().getResourceAsStream("/config.xml"); <1> + if (is != null) { + log.infof("Have XmlConfig, loading"); + XmlConfig config = (XmlConfig) unmarshaller.unmarshal(is); <2> +... + } + return serviceBuildItem; + } + +---- +<1> Look for a config.xml classpath resource +<2> If found, parse using JAXB context for `XmlConfig.class` + +[NOTE] +==== +If there was no /config.xml resource available in the build environment, then a null `RuntimeServiceBuildItem` would be returned and no subsequent logic based on a `RuntimeServiceBuildItem` being produced would execute. +==== + +Typically one is loading a configuration to create some runtime component/service as `parseServiceXmlConfig` is doing. We will come back to the rest of the behavior in `parseServiceXmlConfig` in the following <> section. + +If for some reason you need to parse the config and use it in other build steps in an extension processor, you would need to create an `XmlConfigBuildItem` to pass the parsed XmlConfig instance around. + +[TIP] +==== +If you look at the XmlConfig code you will see that it does carry around the JAXB annotations. If you don't want these in the runtime image, you could clone the XmlConfig instance into some POJO object graph and then replace XmlConfig with the POJO class. We will do this in <>. +==== + +==== Scanning Deployments Using Jandex +If your extension defines annotations or interfaces that mark beans needing to be processed, you can locate these beans using the Jandex API, a Java annotation indexer and offline reflection library. The following `TestProcessor#scanForBeans` method shows how to find the beans annotated with our `@TestAnnotation` that also implement the `IConfigConsumer` interface: + +.Example Jandex Usage +[source,java] +---- + static DotName TEST_ANNOTATION = DotName.createSimple(TestAnnotation.class.getName()); +... + + @BuildStep + @Record(STATIC_INIT) + void scanForBeans(TestTemplate template, BeanArchiveIndexBuildItem beanArchiveIndex, <1> + BuildProducer testBeanProducer) { + IndexView indexView = beanArchiveIndex.getIndex(); <2> + Collection testBeans = indexView.getAnnotations(TEST_ANNOTATION); <3> + for (AnnotationInstance ann : testBeans) { + ClassInfo beanClassInfo = ann.target().asClass(); + try { + boolean isConfigConsumer = beanClassInfo.interfaceNames() + .stream() + .anyMatch(dotName -> dotName.equals(DotName.createSimple(IConfigConsumer.class.getName()))); <4> + if (isConfigConsumer) { + Class beanClass = (Class) Class.forName(beanClassInfo.name().toString()); + testBeanProducer.produce(new TestBeanBuildItem(beanClass)); <5> + log.infof("Configured bean: %s", beanClass); + } + } catch (ClassNotFoundException e) { + log.warn("Failed to load bean class", e); + } + } + } +---- +<1> Depend on a `BeanArchiveIndexBuildItem` to have the build step be run after the deployment has been indexed. +<2> Retrieve the index. +<3> Find all beans annotated with `@TestAnnotation`. +<4> Determine which of these beans also has the `IConfigConsumer` interface. +<5> Save the bean class in a `TestBeanBuildItem` for use in a latter RUNTIME_INIT build step that will interact with the bean instances. + +==== Interacting With Extension Beans +You can use the `io.quarkus.arc.runtime.BeanContainer` interface to interact with your extension beans. The following `configureBeans` methods illustrate interacting with the beans scanned for in the previous section: + +.Using CDI BeanContainer Interface +[source,java] +---- +// TestProcessor#configureBeans + @BuildStep + @Record(RUNTIME_INIT) + void configureBeans(TestTemplate template, List testBeans, <1> + BeanContainerBuildItem beanContainer, <2> + TestRunTimeConfig runTimeConfig) { + + for (TestBeanBuildItem testBeanBuildItem : testBeans) { + Class beanClass = testBeanBuildItem.getConfigConsumer(); + template.configureBeans(beanContainer.getValue(), beanClass, buildAndRunTimeConfig, runTimeConfig); <3> + } + } + +// TestTemplate#configureBeans + public void configureBeans(BeanContainer beanContainer, Class beanClass, + TestBuildAndRunTimeConfig buildTimeConfig, + TestRunTimeConfig runTimeConfig) { + log.infof("Begin BeanContainerListener callback\n"); + IConfigConsumer instance = beanContainer.instance(beanClass); <4> + instance.loadConfig(buildTimeConfig, runTimeConfig); <5> + log.infof("configureBeans, instance=%s\n", instance); + } +---- +<1> Consume the `TestBeanBuildItem`s produced from the scanning build step. +<2> Consume the `BeanContainerBuildItem` to order this build step to run after the CDI bean container has been created. +<3> Call the runtime template to record the bean interactions. +<4> Runtime template retrieves the bean using its type. +<5> Runtime template invokes the `IConfigConsumer#loadConfig(...)` method passing in the configuration objects with runtime information. + +==== Manage Non-CDI Service +A common purpose for an extension is to integrate a non-CDI aware service into the CDI based {project-name} runtime. Step 1 of this task is to load any configuration needed in a STATIC_INIT build step as we did in <>. Now we need to create an instance of the service using the configuration. Let's return to the `TestProcessor#parseServiceXmlConfig` method to see how this can be done. + +.Creating a Non-CDI Service +[source,java] +---- +// TestProcessor#parseServiceXmlConfig + @BuildStep + @Record(STATIC_INIT) + RuntimeServiceBuildItem parseServiceXmlConfig(TestTemplate template) throws JAXBException { + RuntimeServiceBuildItem serviceBuildItem = null; + JAXBContext context = JAXBContext.newInstance(XmlConfig.class); + Unmarshaller unmarshaller = context.createUnmarshaller(); + InputStream is = getClass().getResourceAsStream("/config.xml"); + if (is != null) { + log.infof("Have XmlConfig, loading"); + XmlConfig config = (XmlConfig) unmarshaller.unmarshal(is); + log.infof("Loaded XmlConfig, creating service"); + RuntimeValue service = template.initRuntimeService(config); //<1> + serviceBuildItem = new RuntimeServiceBuildItem(service); //<3> + } + return serviceBuildItem; + } + +// TestTemplate#initRuntimeService + public RuntimeValue initRuntimeService(XmlConfig config) { + RuntimeXmlConfigService service = new RuntimeXmlConfigService(config); //<2> + return new RuntimeValue<>(service); + } + +// RuntimeServiceBuildItem + final public class RuntimeServiceBuildItem extends SimpleBuildItem { + private RuntimeValue service; + + public RuntimeServiceBuildItem(RuntimeValue service) { + this.service = service; + } + + public RuntimeValue getService() { + return service; + } +} +---- +<1> Call into the runtime template to record the creation of the service. +<2> Using the parsed `XmlConfig` instance, create an instance of `RuntimeXmlConfigService` and wrap it in a `RuntimeValue`. Use a `RuntimeValue` wrapper for non-interface objects that are non-proxiable. +<3> Wrap the return service value in a `RuntimeServiceBuildItem` for use in a RUNTIME_INIT build step that will start the service. + +===== Starting a Service +Now that you have recorded the creation of a service during the build phase, you need to record how to start the service at runtime during booting. You do this with a RUNTIME_INIT build step as shown in the `TestProcessor#startRuntimeService` method. + +.Starting/Stopping a Non-CDI Service +[source,java] +---- +// TestProcessor#startRuntimeService + @BuildStep + @Record(RUNTIME_INIT) + ServiceStartBuildItem startRuntimeService(TestTemplate template, ShutdownContextBuildItem shutdownContextBuildItem , // <1> + RuntimeServiceBuildItem serviceBuildItem) throws IOException { // <2> + if (serviceBuildItem != null) { + log.info("Registering service start"); + template.startRuntimeService(shutdownContextBuildItem, serviceBuildItem.getService()); // <3> + } else { + log.info("No RuntimeServiceBuildItem seen, check config.xml"); + } + return new ServiceStartBuildItem("RuntimeXmlConfigService"); //<4> + } + +// TestTemplate#startRuntimeService + public void startRuntimeService(ShutdownContext shutdownContext, RuntimeValue runtimeValue) + throws IOException { + RuntimeXmlConfigService service = runtimeValue.getValue(); + service.startService(); //<5> + shutdownContext.addShutdownTask(service::stopService); //<6> + } +---- +<1> We consume a ShutdownContextBuildItem to register the service shutdown. +<2> We consume the previously initialized service captured in `RuntimeServiceBuildItem`. +<3> Call the runtime template to record the service start invocation. +<4> Produce a `ServiceStartBuildItem` to indicate the startup of a service. See <> for details. +<5> Runtime template retrieves the service instance reference and calls its `startService` method. +<6> Runtime template registers an invocation of the service instance `stopService` method with the {project-name} `ShutdownContext`. + +The code for the `RuntimeXmlConfigService` can be viewed here: +{quarkus-blob-url}/core/test-extension/runtime/src/main/java/io/quarkus/extest/runtime/RuntimeXmlConfigService.java[RuntimeXmlConfigService.java] + +The testcase for validating that the `RuntimeXmlConfigService` has started can be found in the `testRuntimeXmlConfigService` test of `ConfiguredBeanTest` and `NativeImageIT`. + +==== Startup and Shutdown Events +The {project-name} container supports startup and shutdown lifecycle events to notify components of the container startup +and shutdown. There are CDI events fired that components can observe are illustrated in this example: + +.Observing Container Startup +[source,java] +---- +import io.quarkus.runtime.ShutdownEvent; +import io.quarkus.runtime.StartupEvent; + +puclic class SomeBean { + /** + * Called when the runtime has started + * @param event + */ + void onStart(@Observes StartupEvent event) { // <1> + System.out.printf("onStart, event=%s%n", event); + } + + /** + * Called when the runtime is shutting down + * @param event + */ + void onStop(@Observes ShutdownEvent event) { // <2> + System.out.printf("onStop, event=%s%n", event); + } +} +---- +<1> Observe a `StartupEvent` to be notified the runtime has started. +<2> Observe a 'ShutdownEvent` to be notified when the runtime is going to shutdown. + +What is the relevance of startup and shutdown events for extension authors? We have already seen the use of a `ShutdownContext` +to register a callback to perform shutdown tasks in the <> section. These shutdown tasks would be called +after a `ShutdownEvent` had been sent. + +A `StartupEvent` is fired after all `io.quarkus.deployment.builditem.ServiceStartBuildItem` producers have been consumed. +The implication of this is that if an extension has services that application components would expect to have been +started when they observe a `StartupEvent`, the build steps that invoke the runtime code to start those services needs +to produce a `ServiceStartBuildItem` to ensure that the runtime code is run before the `StartupEvent` is sent. Recall that +we saw the production of a `ServiceStartBuildItem` in the previous section, and it is repeated here for clarity: + +.Example of Producing a ServiceStartBuildItem +[source,java] +---- +// TestProcessor#startRuntimeService + @BuildStep + @Record(RUNTIME_INIT) + ServiceStartBuildItem startRuntimeService(TestTemplate template, ShutdownContextBuildItem shutdownContextBuildItem, + RuntimeServiceBuildItem serviceBuildItem) throws IOException { +... + return new ServiceStartBuildItem("RuntimeXmlConfigService"); //<1> + } +---- +<1> Produce a `ServiceStartBuildItem` to indicate that this is a service starting step that needs to run before the `StartupEvent` is sent. + +==== Register Resources for Use in Native Image +Not all configuration or resources can be consumed at build time. If you have classpath resources that the runtime needs to access, you need to inform the build phase that these resources need to be copied into the native image. This is done by producing one or more `SubstrateResourceBuildItem` or `SubstrateResourceBundleBuildItem` in the case of resource bundles. Examples of this are shown in this sample `registerNativeImageReources` build step: + +.Registering Resources and ResourceBundles +[source,java] +---- +public final class MyExtProcessor { + @Inject + BuildProducer resource; + @Inject + BuildProducer resourceBundle; + + @BuildStep + void registerNativeImageReources() { + resource.produce(new SubstrateResourceBuildItem("/security/runtime.keys")); //<1> + + resource.produce(new SubstrateResourceBuildItem( + "META-INF/services/" + io.quarkus.SomeService.class.getName())); //<2> + + resourceBundle.produce(new SubstrateResourceBuildItem("javax.xml.bind.Messages")); //<3> + } +} +---- +<1> Indicate that the /security/runtime.keys classpath resource should be copied into native image. +<2> Indicate that the `io.quarkus.SomeService` ServiceLoader resource should be copied into native image. +<3> Indicate that the "javax.xml.bind.Messages" resource bundle should be copied into native image. + +==== Object Substitution +Objects created during the build phase that are passed into the runtime need to have a default constructor in order for them to be created and configured at startup of the runtime from the build time state. If an object does not have a default constructor you will see an error similar to the following during generation of the augmented artifacts: + +.DSAPublicKey Serialization Error +[source,bash] +---- + [error]: Build step io.quarkus.deployment.steps.MainClassBuildStep#build threw an exception: java.lang.RuntimeException: Unable to serialize objects of type class sun.security.provider.DSAPublicKeyImpl to bytecode as it has no default constructor + at io.quarkus.builder.Execution.run(Execution.java:123) + at io.quarkus.builder.BuildExecutionBuilder.execute(BuildExecutionBuilder.java:136) + at io.quarkus.deployment.QuarkusAugmentor.run(QuarkusAugmentor.java:110) + at io.quarkus.runner.RuntimeRunner.run(RuntimeRunner.java:99) + ... 36 more +---- + +There is a `io.quarkus.runtime.ObjectSubstitution` interface that can be implemented to tell {project-name} how to handle such classes. An example implementation for the `DSAPublicKey` is shown here: + +.DSAPublicKeyObjectSubstitution Example +[source,bash] +---- +package io.quarkus.extest.runtime.subst; + +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.interfaces.DSAPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; +import java.util.logging.Logger; + +import io.quarkus.runtime.ObjectSubstitution; + +public class DSAPublicKeyObjectSubstitution implements ObjectSubstitution { + private static final Logger log = Logger.getLogger("DSAPublicKeyObjectSubstitution"); + @Override + public KeyProxy serialize(DSAPublicKey obj) { //<1> + log.info("DSAPublicKeyObjectSubstitution.serialize"); + byte[] encoded = obj.getEncoded(); + KeyProxy proxy = new KeyProxy(); + proxy.setContent(encoded); + return proxy; + } + + @Override + public DSAPublicKey deserialize(KeyProxy obj) { //<2> + log.info("DSAPublicKeyObjectSubstitution.deserialize"); + byte[] encoded = obj.getContent(); + X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(encoded); + DSAPublicKey dsaPublicKey = null; + try { + KeyFactory kf = KeyFactory.getInstance("DSA"); + dsaPublicKey = (DSAPublicKey) kf.generatePublic(publicKeySpec); + + } catch (NoSuchAlgorithmException | InvalidKeySpecException e) { + e.printStackTrace(); + } + return dsaPublicKey; + } +} +---- +<1> The serialize method takes the object without a default constructor and creates a `KeyProxy` that contains the information necessary to recreate the `DSAPublicKey`. +<2> The deserialize method uses the `KeyProxy` to recreate the `DSAPublicKey` from its encoded form using the key factory. + +An extension registers this substitution by producing an `ObjectSubstitutionBuildItem` as shown in this `TestProcessor#loadDSAPublicKey` fragment: + +.Registering an Object Subtitution +[source,java] +---- + @BuildStep + @Record(STATIC_INIT) + PublicKeyBuildItem loadDSAPublicKey(TestTemplate template, + BuildProducer substitutions) throws IOException, GeneralSecurityException { +... + // Register how to serialize DSAPublicKey + ObjectSubstitutionBuildItem.Holder holder = new ObjectSubstitutionBuildItem.Holder( + DSAPublicKey.class, KeyProxy.class, DSAPublicKeyObjectSubstitution.class); + ObjectSubstitutionBuildItem keysub = new ObjectSubstitutionBuildItem(holder); + substitutions.produce(keysub); + + log.infof("loadDSAPublicKey run"); + return new PublicKeyBuildItem(publicKey); + } +---- + +==== Replacing Classes in the Native Image +The Graal SDK supports substitutions of classes in the native image. An example of how one could replace the `XmlConfig/XmlData` classes with versions that have no JAXB annotation dependencies is shown in these example classes: + +.Substitution of XmlConfig/XmlData Classes Example +[source,bash] +---- +package io.quarkus.extest.runtime.graal; + +import java.util.Date; + +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; + +import io.quarkus.extest.runtime.config.XmlData; +@TargetClass(XmlConfig.class) +@Substitute +final public class Target_XmlConfig { + @Substitute + private String address; + @Substitute + private int port; + @Substitute + private ArrayList dataList; + + @Substitute + public String getAddress() { + return address; + } + + @Substitute + public int getPort() { + return port; + } + + @Substitute + public ArrayList getDataList() { + return dataList; + } + + @Substitute + @Override + public String toString() { + return "Target_XmlConfig{" + + "address='" + address + '\'' + + ", port=" + port + + ", dataList=" + dataList + + '}'; + } +} + +@TargetClass(XmlData.class) +@Substitute +final public class Target_XmlData { + @Substitute + private String name; + @Substitute + private String model; + @Substitute + private Date date; + + @Substitute + public String getName() { + return name; + } + + @Substitute + public String getModel() { + return model; + } + + @Substitute + public Date getDate() { + return date; + } + + @Substitute + @Override + public String toString() { + return "Target_XmlData{" + + "name='" + name + '\'' + + ", model='" + model + '\'' + + ", date='" + date + '\'' + + '}'; + } + +} ---- diff --git a/docs/src/main/asciidoc/fault-tolerance-guide.adoc b/docs/src/main/asciidoc/fault-tolerance-guide.adoc index a2e7ec03d2450..2c9326cdc480a 100644 --- a/docs/src/main/asciidoc/fault-tolerance-guide.adoc +++ b/docs/src/main/asciidoc/fault-tolerance-guide.adoc @@ -1,3 +1,9 @@ +//// +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 +//// + include::./attributes.adoc[] = {project-name} - Fault Tolerance @@ -113,7 +119,7 @@ Let's continue with a simple CDI bean, that would work as a repository of our co [source,java] ---- -package com.acme.faulttolerance; +package org.acme.faulttolerance; import java.util.ArrayList; import java.util.Collections; @@ -207,7 +213,10 @@ in the `CoffeeResource#coffees()` endpoint method in about 50 % of requests. Why not check that our application works? Run the Quarkus development server with: -`mvn compile quarkus:dev` +[source,shell] +---- +./mvnw compile quarkus:dev +---- and open `http://localhost:8080/coffee` in your browser. Make couple of requests (remember, some of them we expect to fail). At least some of the requests should show us the list of our coffee samples in JSON, the rest will fail @@ -246,14 +255,14 @@ the call! To see that that the failures still happen, check the output of the development server. The log messages should be similar to these: -``` +[source] +---- 2019-03-06 12:17:41,725 INFO [org.acm.fau.CoffeeResource] (XNIO-1 task-1) CoffeeResource#coffees() invocation #5 returning successfully 2019-03-06 12:17:44,187 INFO [org.acm.fau.CoffeeResource] (XNIO-1 task-1) CoffeeResource#coffees() invocation #6 returning successfully 2019-03-06 12:17:45,166 ERROR [org.acm.fau.CoffeeResource] (XNIO-1 task-1) CoffeeResource#coffees() invocation #7 failed 2019-03-06 12:17:45,172 ERROR [org.acm.fau.CoffeeResource] (XNIO-1 task-1) CoffeeResource#coffees() invocation #8 failed 2019-03-06 12:17:45,176 INFO [org.acm.fau.CoffeeResource] (XNIO-1 task-1) CoffeeResource#coffees() invocation #9 returning successfully - -``` +---- You can see that every time an invocation fails, it's immediately followed by another invocation, until one succeeds. Since we allowed 4 retries, it would require 5 invocations to fail in a row, in order for the user to be actually exposed @@ -346,11 +355,12 @@ two recommendations returned by the original method. Check the server output to see that fallback is really happening: -``` +[source] +---- 2019-03-06 13:21:54,170 INFO [org.acm.fau.CoffeeResource] (pool-15-thread-3) CoffeeResource#recommendations() invocation #2 returning successfully 2019-03-06 13:21:55,159 ERROR [org.acm.fau.CoffeeResource] (pool-15-thread-4) CoffeeResource#recommendations() invocation #3 timed out after 248 ms 2019-03-06 13:21:55,161 INFO [org.acm.fau.CoffeeResource] (HystrixTimer-1) Falling back to RecommendationResource#fallbackRecommendations() -``` +---- NOTE: The fallback method is required to have the same parameters as the original method. @@ -454,15 +464,17 @@ of our business logic. All that is needed to enable the fault tolerance features in Quarkus is: * adding the `smallrye-fault-tolerance` Quarkus extension to your project using the `quarkus-maven-plugin`: - - `mvn quarkus:add-extension -Dextensions="smallrye-fault-tolerance"` - ++ +[source,shell] +---- +./mvnw quarkus:add-extension -Dextensions="smallrye-fault-tolerance" +---- * or simply adding the following Maven dependency: - - [source,xml] - ---- - - io.quarkus - quarkus-smallrye-fault-tolerance - - ---- ++ +[source,xml] +---- + + io.quarkus + quarkus-smallrye-fault-tolerance + +---- diff --git a/docs/src/main/asciidoc/flyway-guide.adoc b/docs/src/main/asciidoc/flyway-guide.adoc new file mode 100644 index 0000000000000..f2147d6d20a26 --- /dev/null +++ b/docs/src/main/asciidoc/flyway-guide.adoc @@ -0,0 +1,175 @@ +//// +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 +//// + +include::./attributes.adoc[] += {project-name} - Using Flyway +:migrations-path: src/main/resources/db/migration +:config-file: application.properties + +https://flywaydb.org/[Flyway] is a popular database migration tool that is commonly used in JVM environments. + +{project-name} provides first class support for using Flyway as will be explained in this guide. + +== Setting up support for Flyway + +To start using Flyway with your project, you just need to: + +* add your migrations to the `{migrations-path}` folder as you usually do with Flyway +* activate the `migrate-at-start` option to migrate the schema automatically or inject the `Flyway` object and run +your migration as you normally do + +In your `pom.xml`, add the following dependencies: + +* the Flyway extension +* your JDBC driver extension (`quarkus-jdbc-postgresql`, `quarkus-jdbc-h2`, `quarkus-jdbc-mariadb`, ...) + +[source,xml] +-- + + + + io.quarkus + quarkus-flyway + + + + + io.quarkus + quarkus-jdbc-postgresql + + +-- + +Flyway support relies on the {project-name} default datasource config, you must add the default datasource properties +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`:: +**true** to execute Flyway 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 +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. + +**default:** classpath:db/migration + +`quarkus.flyway.connect-retries`:: +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 connect-retries. + +**default:** 0 + +`quarkus.flyway.schemas`:: +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. + +**default:** + +`quarkus.flyway.table`:: +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 quarkus.flyway.schemas property is set (multi-schema mode), the schema history table is placed in the first schema of +the list. + +**default:** flyway_schema_history + +`quarkus.flyway.sql-migration-prefix`:: +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 + +**default:** V + +`quarkus.flyway.repeatable-sql-migration-prefix`:: +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 + +**default:** R + + +The following is an example for the `{config-file}` file: + +[source,properties] +-- +# configure your datasource +quarkus.datasource.url: jdbc:postgresql://localhost:5432/mydatabase +quarkus.datasource.driver: org.postgresql.Driver +quarkus.datasource.username: sarah +quarkus.datasource.password: connor + +# Flyway minimal config properties +quarkus.flyway.migrate-at-start=true + +# Flyway optional config properties +# 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 +-- + +Add a SQL migration to the default folder following the Flyway naming conventions: `{migrations-path}/V1.0.0__Quarkus.sql` + +[source,sql] +-- +CREATE TABLE quarkus +( + id INT, + name VARCHAR(20) +); +INSERT INTO quarkus(id, name) +VALUES (1, 'QUARKED'); +-- + +Now you can start your application and {project-name} will run the Flyway's migrate method according to your config: + +[source,java] +-- +@ApplicationScoped +public class MigrationService { + // You can Inject the object if you want to use it manually + @Inject + Flyway flyway; <1> + + public void checkMigration() { + // This will print 1.0.0 + System.out.println(flyway.info().current().getVersion().toString()); + } +} +-- + +<1> Inject the Flyway object if you want to use it directly + +== Using the Flyway object + +In case you are interested in using the `Flyway` object directly, you can inject it as follows: + +NOTE: If you enabled the `quarkus.flyway.migrate-at-start` property, by the time you use the Flyway instance, +{project-name} will already have run the migrate operation + +[source,java] +-- +@ApplicationScoped +public class MigrationService { + // You can Inject the object if you want to use it manually + @Inject + Flyway flyway; <1> + + public void checkMigration() { + // Use the flyway instance manually + flyway.clean(); <2> + flyway.migrate(); + // This will print 1.0.0 + System.out.println(flyway.info().current().getVersion().toString()); + } +} +-- + +<1> Inject the Flyway object if you want to use it directly +<2> Use the Flyway instance directly + diff --git a/docs/src/main/asciidoc/getting-started-guide.adoc b/docs/src/main/asciidoc/getting-started-guide.adoc index 40510f577ee5f..1852f21f091cc 100644 --- a/docs/src/main/asciidoc/getting-started-guide.adoc +++ b/docs/src/main/asciidoc/getting-started-guide.adoc @@ -1,3 +1,9 @@ +//// +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 +//// + include::./attributes.adoc[] = {project-name} - Creating Your First Application @@ -33,7 +39,7 @@ To complete this guide, you need: == Architecture In this guide, we create a straightforward application serving a `hello` endpoint. To demonstrate -dependency injection this endpoint uses a `greeting` bean. +dependency injection, this endpoint uses a `greeting` bean. image::getting-started-architecture.png[alt=Architecture] @@ -67,11 +73,11 @@ It generates: * an `org.acme.quickstart.GreetingResource` resource exposed on `/hello` * an associated unit test * a landing page that is accessible on `http://localhost:8080` after starting the application -* an example of `Dockerfile` +* example `Dockerfile` files for both `native` and `jvm` modes * the application configuration file Once generated, look at the `pom.xml`. -You will find the import of the Quarkus BOM, allowing to omit the version on the different {project-name} dependencies. +You will find the import of the Quarkus BOM, allowing you to omit the version on the different {project-name} dependencies. In addition, you can see the `quarkus-maven-plugin` responsible of the packaging of the application and also providing the development mode. [source,xml,subs=attributes+] @@ -106,7 +112,7 @@ In addition, you can see the `quarkus-maven-plugin` responsible of the packaging ---- -If we focus on the dependencies section, you can see 2 extensions allowing the development of REST applications: +If we focus on the dependencies section, you can see the extension allowing the development of REST applications: [source,xml] ---- @@ -114,18 +120,8 @@ If we focus on the dependencies section, you can see 2 extensions allowing the d io.quarkus quarkus-resteasy - - io.quarkus - quarkus-arc - ---- -[NOTE] -.What's ArC? -==== -ArC is a CDI-based dependency injection solution tailored for Quarkus architecture - see also link:cdi-reference.html[Contexts and Dependency Injection]. -==== - === The JAX-RS resources During the project creation, the `src/main/java/org/acme/quickstart/GreetingResource.java` file has been created with the following content: @@ -145,7 +141,7 @@ public class GreetingResource { @GET @Produces(MediaType.TEXT_PLAIN) public String hello() { - return "hello"; + return "hello\n"; } } ---- @@ -153,32 +149,32 @@ public class GreetingResource { It's a very simple REST endpoint, returning "hello" to requests on "/hello". [TIP] -.Differences with vanilla Jax-RS +.Differences with vanilla JAX-RS ==== -With Quarkus there is no need to create an `Application` class. It's supported but not required. In addition, only one instance +With Quarkus, there is no need to create an `Application` class. It's supported, but not required. In addition, only one instance of the resource is created and not one per request. You can configure this using the different `*Scoped` annotations (`ApplicationScoped`, `RequestScoped`, etc). ==== == Running the application Now we are ready to run our application. -Use: `mvn compile quarkus:dev`: +Use: `./mvnw compile quarkus:dev`: [source,shell] ---- -[INFO] --------------------< org.acme:quarkus-quickstart >--------------------- -[INFO] Building quarkus-quickstart 1.0-SNAPSHOT +[INFO] --------------------< org.acme:getting-started >--------------------- +[INFO] Building getting-started 1.0-SNAPSHOT [INFO] --------------------------------[ jar ]--------------------------------- [INFO] -[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ quarkus-quickstart --- +[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ getting-started --- [INFO] Using 'UTF-8' encoding to copy filtered resources. [INFO] skip non existing resourceDirectory /Users/starksm/Dev/JBoss/Quarkus/starksm64-quarkus-quickstarts/getting-started/src/main/resources [INFO] -[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ quarkus-quickstart --- +[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ getting-started --- [INFO] Changes detected - recompiling the module! [INFO] Compiling 2 source files to /Users/starksm/Dev/JBoss/Quarkus/starksm64-quarkus-quickstarts/getting-started/target/classes [INFO] -[INFO] --- quarkus-maven-plugin::dev (default-cli) @ quarkus-quickstart --- +[INFO] --- quarkus-maven-plugin::dev (default-cli) @ getting-started --- Listening for transport dt_socket at address: 5005 2019-02-28 17:05:22,347 INFO [io.qua.dep.QuarkusAugmentor] (main) Beginning quarkus augmentation 2019-02-28 17:05:22,635 INFO [io.qua.dep.QuarkusAugmentor] (main) Quarkus augmentation completed in 288ms @@ -197,6 +193,11 @@ Hit `CTRL+C` to stop the application, but you can also keep it running and enjoy == Using injection +Dependency injection in Quarkus is based on ArC which is a CDI-based dependency injection solution tailored for Quarkus' architecture. +You can learn more about it in the link:cdi-reference.html[Contexts and Dependency Injection guide]. + +ArC comes as a dependency of `quarkus-resteasy` so you already have it handy. + Let's modify the application and add a companion bean. Create the `src/main/java/org/acme/quickstart/GreetingService.java` file with the following content: @@ -210,7 +211,7 @@ import javax.enterprise.context.ApplicationScoped; public class GreetingService { public String greeting(String name) { - return "hello " + name; + return "hello " + name + "\n"; } } @@ -245,20 +246,20 @@ public class GreetingResource { @GET @Produces(MediaType.TEXT_PLAIN) public String hello() { - return "hello"; + return "hello\n"; } } ---- -If you stopped the application, restart the application with `mvn compile quarkus:dev`. +If you stopped the application, restart the application with `./mvnw compile quarkus:dev`. Then check that http://localhost:8080/hello/greeting/quarkus returns `hello quarkus`. == Development Mode `quarkus:dev` runs Quarkus in development mode. This enables hot deployment with background compilation, which means -that when you modify your Java files your resource files and refresh your browser these changes will automatically take effect. +that when you modify your Java files and/or your resource files and refresh your browser, these changes will automatically take effect. This works too for resource files like the configuration property file. -Refreshing the browser triggers a scan of the workspace, and if any changes are detected the Java files are recompiled +Refreshing the browser triggers a scan of the workspace, and if any changes are detected, the Java files are recompiled and the application is redeployed; your request is then serviced by the redeployed application. If there are any issues with compilation or deployment an error page will let you know. @@ -276,13 +277,11 @@ In the generated `pom.xml` file, you can see 2 test dependencies: io.quarkus quarkus-junit5 - ${quarkus.version} test io.rest-assured rest-assured - {restassured-version} test ---- @@ -296,7 +295,7 @@ be set, as the default version does not support Junit 5: ---- maven-surefire-plugin - ${surefire.version} + ${surefire-plugin.version} org.jboss.logmanager.LogManager @@ -331,7 +330,7 @@ public class GreetingResourceTest { .when().get("/hello") .then() .statusCode(200) // <2> - .body(is("hello")); + .body(is("hello\n")); } @Test @@ -342,7 +341,7 @@ public class GreetingResourceTest { .when().get("/hello/greeting/{name}") .then() .statusCode(200) - .body(is("hello " + uuid)); + .body(is("hello " + uuid + "\n")); } } @@ -352,12 +351,12 @@ public class GreetingResourceTest { These tests use http://rest-assured.io/[RestAssured], but feel free to use your favorite library. -You can run the test from your IDE directly (be sure you stopped the application first), or from Maven using: `mvn test`. +You can run the test from your IDE directly (be sure you stopped the application first), or from Maven using: `./mvnw test`. -By default tests will run on port `8081` so as not to conflict with the running application. We automatically +By default, tests will run on port `8081` so as not to conflict with the running application. We automatically configure RestAssured to use this port. If you want to use a different client you should use the `@TestHTTPResource` annotation to directly inject the URL of the test into a field on the test class. This field can be of the type -`String`, `URL` or `URI`. This annotation can also be given a value for the test path. For example if I want to test +`String`, `URL` or `URI`. This annotation can also be given a value for the test path. For example, if I want to test a Servlet mapped to `/myservlet` I would just add the following to my test: @@ -373,7 +372,7 @@ property called `test.url` that is set to the base test URL for situations where == Packaging and run the application -The application is packaged using `mvn package`. +The application is packaged using `./mvnw package`. It produces 2 jar files: * `getting-started-1.0-SNAPSHOT.jar` - containing just the classes and resources of the projects, it's the regular @@ -398,7 +397,7 @@ The resource can also use `CompletionStage` as return type to handle asynchronou @Produces(MediaType.TEXT_PLAIN) public CompletionStage hello() { return CompletableFuture.supplyAsync(() -> { - return "hello"; + return "hello\n"; }); } ---- @@ -413,8 +412,8 @@ We recommend continuing the journey with the link:building-native-image-guide.ht In addition, the link:tooling.html[tooling guide] document explains how to: -* scaffold a project in a single command line, -* enable the _development model_ (hot reload) +* scaffold a project in a single command line +* enable the _development mode_ (hot reload) * import the project in your favorite IDE * and more diff --git a/docs/src/main/asciidoc/getting-started-knative-guide.adoc b/docs/src/main/asciidoc/getting-started-knative-guide.adoc index 33b4051123773..694771c809477 100644 --- a/docs/src/main/asciidoc/getting-started-knative-guide.adoc +++ b/docs/src/main/asciidoc/getting-started-knative-guide.adoc @@ -1,3 +1,9 @@ +//// +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 +//// + include::./attributes.adoc[] :experimental: @@ -37,7 +43,7 @@ The solution is located in the `getting-started-knative` directory. Before we deploy the application to Knative in Minikube or Minishift we need to create the following Kubernetes objects: -- https://github.com/knative/docs/tree/master/serving/samples/build-private-repo-go#creating-a-dockerhub-push-credential[Container registry secrets] :- +- https://github.com/knative/docs/blob/master/docs/serving/deploying-with-private-registry.md#provide-container-registry-credentials-to-knative[Container registry secrets] :- This is required to for the built container image to be pushed to the container registry of your choice - https://developer.github.com/v3/guides/managing-deploy-keys/#deploy-keys[Deploy Key] :- This is required only if you are going to pull the sources from private repository. diff --git a/docs/src/main/asciidoc/getting-started-testing.adoc b/docs/src/main/asciidoc/getting-started-testing.adoc index 415e2bb6b1e89..4580396f85cf6 100644 --- a/docs/src/main/asciidoc/getting-started-testing.adoc +++ b/docs/src/main/asciidoc/getting-started-testing.adoc @@ -1,3 +1,9 @@ +//// +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 +//// + include::./attributes.adoc[] = {project-name} - Testing Your Application @@ -79,7 +85,7 @@ must be set, as the default version does not support Junit 5: ---- maven-surefire-plugin - ${surefire.version} + ${surefire-plugin.version} org.jboss.logmanager.LogManager @@ -135,9 +141,18 @@ the test is run. === Controlling the test port -While Quarkus will listen on port `8080` by default when running tests it defaults to `8081`. This allows you to run -tests while having the application running in parallel. This can be configured via the `quarkus.http.test-port` -config property in `application.properties`. +While Quarkus will listen on port `8080` by default, when running tests it defaults to `8081`. This allows you to run +tests while having the application running in parallel. + +[TIP] +.Changing the test port +==== +You can configure the port used by tests by configuring `quarkus.http.test-port` in your `application.properties`: +[source] +---- +quarkus.http.test-port=8083 +---- +==== Quarkus also provides Restassured integration that updates the default port used by Restassured before the tests are run, so no additional configuration should be required. diff --git a/docs/src/main/asciidoc/gradle-config.adoc b/docs/src/main/asciidoc/gradle-config.adoc index 083da113ba0ba..d338f9daca6af 100644 --- a/docs/src/main/asciidoc/gradle-config.adoc +++ b/docs/src/main/asciidoc/gradle-config.adoc @@ -1,3 +1,9 @@ +//// +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 +//// + include::./attributes.adoc[] = {project-name} - Gradle Plugin Repositories @@ -13,7 +19,7 @@ pluginManagement { } resolutionStrategy { eachPlugin { - if (requested.id.id == 'io.quarkus.gradle.plugin') { + if (requested.id.id == 'io.quarkus') { useModule("io.quarkus:quarkus-gradle-plugin:${requested.version}") } } @@ -31,7 +37,7 @@ pluginManagement { } resolutionStrategy { eachPlugin { - if (requested.id.id == "io.quarkus.gradle.plugin") { + if (requested.id.id == "io.quarkus") { useModule("io.quarkus:quarkus-gradle-plugin:${requested.version}") } } diff --git a/docs/src/main/asciidoc/gradle-tooling.adoc b/docs/src/main/asciidoc/gradle-tooling.adoc index a2f2fd48506f6..74e8eb0baa69b 100644 --- a/docs/src/main/asciidoc/gradle-tooling.adoc +++ b/docs/src/main/asciidoc/gradle-tooling.adoc @@ -1,3 +1,9 @@ +//// +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 +//// + include::./attributes.adoc[] = Building {project-name} apps with Gradle @@ -12,7 +18,7 @@ luckily setting up a {project-name} project with Gradle is very simple. You only ---- plugins { id 'java' - id 'io.quarkus.gradle.plugin' version '{quarkus-version}' + id 'io.quarkus' version '{quarkus-version}' } ---- @@ -22,7 +28,7 @@ or, if you use the Gradle Kotlin DSL: ---- plugins { java - id("io.quarkus.gradle.plugin") version "{quarkus-version}" + id("io.quarkus") version "{quarkus-version}" } ---- @@ -36,25 +42,21 @@ Here is a complete sample file for a simple REST project: ---- plugins { id 'java' - id 'io.quarkus.gradle.plugin' version '{quarkus-version}' <1> + id 'io.quarkus.' version '{quarkus-version}' <1> } repositories { mavenCentral() - - // this is temporary, all dependencies should be in central soon - maven { - url = 'http://repository.jboss.org/nexus/content/groups/public' - } } dependencies { <2> - compileOnly 'io.quarkus:quarkus-resteasy:{quarkus-version}' + implementation 'io.quarkus:quarkus-resteasy:{quarkus-version}' } ---- <1> The {project-name} plugin needs to be applied. <2> This dependency is needed for a REST application similar to the getting started example. +{project-name} also need this dependency for running tests, to provide this we use the `implementation` configuration. Here's the same build script, using the Gradle Kotlin DSL: @@ -62,28 +64,50 @@ Here's the same build script, using the Gradle Kotlin DSL: ---- plugins { java - id("io.quarkus.gradle.plugin") version "{quarkus-version}" + id("io.quarkus") version "{quarkus-version}" } repositories { mavenCentral() - - // this is temporary, all dependencies should be in central soon - maven(url = uri("http://repository.jboss.org/nexus/content/groups/public")) } dependencies { - compileOnly("io.quarkus:quarkus-resteasy:{quarkus-version}") + implementation("io.quarkus:quarkus-resteasy:{quarkus-version}") } ---- +== Enable Tests + +{project-name} uses Junit5 and to enable it in Gradle we need to add a section to our build file: + +[source,groovy,subs=attributes+] +---- +test { + useJUnitPlatform() +} +---- + +To follow up our Rest example from above, we would also need to add two test dependencies: + +[source,groovy,subs=attributes+] +---- +testCompile group: 'io.quarkus', name: 'quarkus-junit5', version: '{quarkus-version}' +testCompile group: 'io.rest-assured', name: 'rest-assured', version: '{restassured-version}' +---- + +Note: {project-name} do not allow both link:getting-started-testing.html[QuarkusTests] and link:getting-started-testing.html#native-executable-testing[SubstrateTests] to run in the same test run. +SubstrateTests should be seen as integration tests and moved to a different folder +as recommended here: +https://docs.gradle.org/current/userguide/java_testing.html#sec:configuring_java_integration_tests[Configuring integration tests]. +{project-name} supports running Substrate tests with Gradle, but the `buildNative` task is required to be completed first. + == Dealing with extensions From inside a {project-name} project, you can obtain a list of the available extensions with: [source,shell] ---- -./gradlew list-extensions +./gradlew listExtensions ---- Functionality to automatically add extensions to your Gradle project is not implemented yet (coming soon). @@ -95,14 +119,14 @@ Run your application with: [source,shell] ---- -./gradlew quarkus-dev +./gradlew quarkusDev ---- You can then update the application sources, resources and configurations. The changes are automatically reflected in your running application. This is great to do development spanning UI and database as you see changes reflected immediately. -`quarkus-dev` enables hot deployment with background compilation, which means that when you modify +`quarkusDev` enables hot deployment with background compilation, which means that when you modify your Java files or your resource files and refresh your browser these changes will automatically take effect. This works too for resource files like the configuration property file. The act of refreshing the browser triggers a scan of the workspace, and if any changes are detected the @@ -117,7 +141,7 @@ You can run a {project-name} application in debug mode using: [source,shell] ---- -./gradlew quarkus-dev --debug=true +./gradlew quarkusDev --debug=true ---- Then, attach your debugger to `localhost:5005`. @@ -134,7 +158,7 @@ In the wizard, select: `Gradle -> Existing Gradle Project`. On the next screen, select the root location of the project. The next screen list the found modules; select the generated project and click on `Finish`. Done! -In a separated terminal, run `./gradlew quarkus-dev`, and enjoy a highly productive environment. +In a separated terminal, run `./gradlew quarkusDev`, and enjoy a highly productive environment. **IntelliJ** @@ -146,7 +170,7 @@ In IntelliJ: 4. Next a few times (review the different options if needed) 5. On the last screen click on Finish -In a separated terminal or in the embedded terminal, run `./gradlew quarkus-dev`. Enjoy! +In a separated terminal or in the embedded terminal, run `./gradlew quarkusDev`. Enjoy! **Apache Netbeans** @@ -156,7 +180,7 @@ In Netbeans: 2. Select the project root 3. Click on `Open Project` -In a separated terminal or the embedded terminal, go to the project root and run `./gradlew quarkus-dev`. Enjoy! +In a separated terminal or the embedded terminal, go to the project root and run `./gradlew quarkusDev`. Enjoy! **Visual Studio Code** @@ -168,7 +192,7 @@ Native executables make {project-name} applications ideal for containers and ser Make sure to have `GRAALVM_HOME` configured and pointing to GraalVM version {graalvm-version}. -Create a native executable using: `./gradlew quarkus-native`. +Create a native executable using: `./gradlew buildNative`. A native executable will be present in `build/`. === Build a container friendly executable @@ -178,10 +202,29 @@ To create an executable that will run in a container, use the following: [source,shell] ---- -./gradlew quarkus-native --docker-build=true +./gradlew buildNative --docker-build=true ---- The produced executable will be a 64 bit Linux executable, so depending on your operating system it may no longer be runnable. However, it's not an issue as we are going to copy it to a Docker container. +== Building Uber-Jars + +Quarkus Gradle plugin supports the generation of Uber-Jars by specifying an `--uber-jar` argument as follows: + +[source,shell] +---- +./gradlew quarkusBuild --uber-jar +---- + +When building an Uber-Jar you can specify entries that you want to exclude from the generated jar by using the `--ignored-entry` argument: + +[source,shell] +---- +./gradlew quarkusBuild --uber-jar --ignored-entry=META-INF/file1.txt +---- + +The entries are relative to the root of the generated Uber-Jar. You can specify multiple entries by adding extra `--ignored-entry` arguments. + + diff --git a/docs/src/main/asciidoc/health-guide.adoc b/docs/src/main/asciidoc/health-guide.adoc new file mode 100644 index 0000000000000..97979dcad2865 --- /dev/null +++ b/docs/src/main/asciidoc/health-guide.adoc @@ -0,0 +1,232 @@ +//// +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 +//// + +include::./attributes.adoc[] += {project-name} - MicroProfile Health + +This guide demonstrates how your {project-name} application can utilize the MicroProfile +Health specification through the SmallRye Health extension. + +MicroProfile Health allows applications to provide information about their state +to external viewers which is typically useful in cloud environments where automated +processes must be able to determine whether the application should be discarded +or restarted. + +== 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+ + +== Architecture + +In this guide, we build a simple REST application that exposes MicroProfile Health functionalities at +the `/health` endpoint according to the specification. It will also provide several other REST +endpoints to allow us to dynamically change the healthness of our {project-name} application. + +== 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 `microprofile-health` {quickstarts-tree-url}/microprofile-health[directory]. + +== Creating the Maven Project + +First, we need a new project. Create a new project with the following command: + +[source, subs=attributes+] +---- +mvn io.quarkus:quarkus-maven-plugin:{quarkus-version}:create \ + -DprojectGroupId=org.acme \ + -DprojectArtifactId=microprofile-health \ + -Dextensions="smallrye-health" +---- + +This command generates a Maven project, importing the `smallrye-health` extension +which is an implementation of the MicroProfile Health specification used in {project-name}. + +== Running the health check + +Importing the `smallrye-health` extension directly exposes a single REST endpoint at +the `/health` endpoint that can be used to run the health check procedures: + +* start your {project-name} application with `mvn compile quarkus:dev` +* access the `http://localhost:8080/health` endpoint using your browser or +`curl http://localhost:8080/health` + +The health REST enpoint returns a simple JSON object with two fields: + +* `outcome` -- the overall result of all the health check procedures +* `checks` -- an array of individual checks + +The general `outcome` of the health check is computed as a logical AND of all the declared +health check procedures. The `checks` array is empty as we have not specified any health +check procedure yet so let's define some. + +== Creating your first health check + +In this section we create our first simple health check procedure. + +Create the `org.acme.health.SimpleHealthCheck` class: + +[source,java] +---- +package org.acme.health; + +import org.eclipse.microprofile.health.Health; +import org.eclipse.microprofile.health.HealthCheck; +import org.eclipse.microprofile.health.HealthCheckResponse; + +import javax.enterprise.context.ApplicationScoped; + +@Health +@ApplicationScoped +public class SimpleHealthCheck implements HealthCheck { + + @Override + public HealthCheckResponse call() { + return HealthCheckResponse.named("Simple health check").up().build(); + } +} +---- + +As you can see health check procedures are defined as implementations of the `HealthCheck` +interface which are defined as CDI beans with the `@Health` qualifier. `HealthCheck` is +a functional interface whose single method `call` returns a `HealthCheckResponse` object +which can be easily constructed by the fluent builder API shown in the example. + +As we have started our {project-name} application in dev mode simply repeat the request +to `http://localhost:8080/health` by refreshing your browser window or by using `curl http://localhost:8080/health`. +The new health check procedure is now present in the `checks` array. + +Congratulations! You've created your first {project-name} health check procedure. Let's +continue by exploring what else can be done with the MicroProfile Health specification. + +== Adding user specific data to the health check response + +In previous section we saw how to create a simple health check with only the minimal +attributes, namely, the health check name and its state (UP or DOWN). However, the +MicroProfile specification also provides a way for the applications to supply +arbitrary data in the form of key value pairs sent to the consuming end. This can be done by using the +`withData(key, value)` method of the health check response builder API. + +Let's create our second health check procedure `org.acme.health.DataHealthCheck`: + +[source,java] +---- +package org.acme.health; + +import org.eclipse.microprofile.health.Health; +import org.eclipse.microprofile.health.HealthCheck; +import org.eclipse.microprofile.health.HealthCheckResponse; + +import javax.enterprise.context.ApplicationScoped; + +@Health +@ApplicationScoped +public class DataHealthCheck implements HealthCheck { + + @Override + public HealthCheckResponse call() { + return HealthCheckResponse.named("Health check with data") + .up() + .withData("foo", "fooValue") + .withData("bar", "barValue") + .build(); + } +} +---- + +If you rerun the health check procedure again by accessing the `/health` endpoint you can +see that the new health check `Health check with data` is present in the `checks` array. +This check contains a new attribute called `data` which is a JSON object consisting of +the properties we have defined in our health check procedure. + +== Negative health check procedure + +In this section we create another health check procedure which simulates a connection to +an external service provider such as a database. For simplicity reasons, we only determine +whether the database is accessible or not by a configuration property. + +Create `org.acme.health.DatabaseConnectionHealthCheck` class: + +[source,java] +---- +package org.acme.health; + +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.eclipse.microprofile.health.Health; +import org.eclipse.microprofile.health.HealthCheck; +import org.eclipse.microprofile.health.HealthCheckResponse; +import org.eclipse.microprofile.health.HealthCheckResponseBuilder; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; + +@Health +@ApplicationScoped +public class DatabaseConnectionHealthCheck implements HealthCheck { + + @ConfigProperty(name = "database.up", defaultValue = "false") + private boolean databaseUp; + + @Override + public HealthCheckResponse call() { + + HealthCheckResponseBuilder responseBuilder = HealthCheckResponse.named("Database connection health check"); + + try { + simulateDatabaseConnectionVerification(); + responseBuilder.up(); + } catch (IllegalStateException e) { + // cannot access the database + responseBuilder.down() + .withData("error", e.getMessage()); + } + + return responseBuilder.build(); + } + + private void simulateDatabaseConnectionVerification() { + if (!databaseUp) { + throw new IllegalStateException("Cannot contact database"); + } + } +} +---- + +If you now rerun the health check the overall `outcome` should be DOWN and you should +see in the `checks` array the newly added `Database connection health check` which is +down and the error message explaining why it failed. + +As we shouldn't leave this application with a health check in DOWN state and because we +are running {project-name} dev mode you can add `database.up=true` in +`src/main/resources/application.properties` and rerun the health check again -- +it should be up again. + +== Conclusion + +MicroProfile Health provides a way for your application to distribute information +about its healthness state to state whether or not it is able to function properly. + +All that is needed to enable the MicroProfile Health features in {project-name} is: + +* adding the `smallrye-health` {project-name} extension to your project using the `quarkus-maven-plugin`: + + mvn quarkus:add-extension -Dextensions="smallrye-health" + +* or simply adding the following Maven dependency: + + + io.quarkus + quarkus-smallrye-health + diff --git a/docs/src/main/asciidoc/hibernate-orm-guide.adoc b/docs/src/main/asciidoc/hibernate-orm-guide.adoc index 74fd6e7694f6c..4388f98bc4b0e 100644 --- a/docs/src/main/asciidoc/hibernate-orm-guide.adoc +++ b/docs/src/main/asciidoc/hibernate-orm-guide.adoc @@ -1,3 +1,9 @@ +//// +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 +//// + include::./attributes.adoc[] = {project-name} - Using Hibernate ORM and JPA :config-file: application.properties @@ -27,14 +33,12 @@ In your `pom.xml`, add the following dependencies: io.quarkus quarkus-hibernate-orm - provided io.quarkus quarkus-jdbc-postgresql - provided -- @@ -45,16 +49,17 @@ then add the relevant configuration properties in `{config-file}`. [source,properties] -- # configure your datasource -quarkus.datasource.url: jdbc:postgresql://localhost:5432/mydatabase -quarkus.datasource.driver: org.postgresql.Driver -quarkus.datasource.username: sarah -quarkus.datasource.password: connor +quarkus.datasource.url = jdbc:postgresql://localhost:5432/mydatabase +quarkus.datasource.driver = org.postgresql.Driver +quarkus.datasource.username = sarah +quarkus.datasource.password = connor # drop and create the database at startup (use `update` to only update the schema) quarkus.hibernate-orm.database.generation=drop-and-create -- -Note that these configuration properties are not the same ones as made available in `{config-file}`: they might differ in names, casing and don't necessarily map 1:1 to each other. +Note that these configuration properties are not the same ones as in your typical Hibernate ORM configuration file: they might differ in names, casing and don't necessarily map 1:1 to each other. +Please see below for the list of properties you can define. An `EntityManagerFactory` will be created based on {project-name} `datasource` configuration as long as the Hibernate ORM extension is declared in your `pom.xml`. The dialect will be selected based on the JDBC driver. @@ -115,8 +120,11 @@ There are optional properties useful to refine your `EntityManagerFactory` or gu ==== Dialect `quarkus.hibernate-orm.dialect`:: (e.g. `org.hibernate.dialect.PostgreSQL95Dialect`). -Class name of the Hibernate ORM dialect. - +Class name of the Hibernate ORM dialect. The complete list of bundled dialects is available in the https://docs.jboss.org/hibernate/stable/orm/javadocs/org/hibernate/dialect/package-summary.html[Hibernate ORM JavaDoc]. +[NOTE] +==== +Not all the dialects are supported in GraalVM native executables: we currently provide driver extensions for PostgreSQL, MariaDB, Microsoft SQL Server and H2. +==== `quarkus.hibernate-orm.dialect.storage-engine`:: (e.g. `MyISAM` or `InnoDB`). The storage engine to use when the dialect supports multiple storage engines. @@ -142,7 +150,7 @@ Options are `none`, `first`, `last`. `quarkus.hibernate-orm.database.generation`:: (e.g. `drop-and-create` which is awesome in development mode). Select whether the database schema is generated or not. -Options are `none`, `create`, `drop-and-create`, `drop` +Options are `none`, `create`, `drop-and-create`, `drop`, `update`. The default is `none`. `quarkus.hibernate-orm.database.generation.halt-on-error`:: (defaults to `false`) Whether we should stop on the first error when applying the schema. @@ -276,7 +284,7 @@ Collections and relations need to be individually annotated to be cached; in thi [source,java] -- -package com.acme; +package org.acme; @Entity @Cacheable @@ -323,9 +331,9 @@ That's all! Caching technology is already integrated and enabled by default in { Caches store the data in separate regions to isolate different portions of data; such regions are assigned a name, which is useful for configuring each region independently, or to monitor their statistics. -By default entities are cached in regions named after their fully qualified name, e.g. `com.acme.Country`. +By default entities are cached in regions named after their fully qualified name, e.g. `org.acme.Country`. -Collections are cached in regions named after the fully qualified name of their owner entity and collection field name, separated by `#` character, e.g. `com.acme.Country#cities`. +Collections are cached in regions named after the fully qualified name of their owner entity and collection field name, separated by `#` character, e.g. `org.acme.Country#cities`. All cached queries are by default kept in a single region dedicated to them called `default-query-results-region`. @@ -339,7 +347,7 @@ To set the maximum idle time, provide the number of seconds via the `quarkus.hib ==== The double quotes are mandatory if your region name contains a dot. For instance: ``` -quarkus.hibernate-orm.cache."com.acme.MyEntity".memory.object-count=1000 +quarkus.hibernate-orm.cache."org.acme.MyEntity".memory.object-count=1000 ``` ==== diff --git a/docs/src/main/asciidoc/hibernate-orm-panache-guide.adoc b/docs/src/main/asciidoc/hibernate-orm-panache-guide.adoc index c0a471df8dace..8b0aaf9af1df2 100644 --- a/docs/src/main/asciidoc/hibernate-orm-panache-guide.adoc +++ b/docs/src/main/asciidoc/hibernate-orm-panache-guide.adoc @@ -1,3 +1,9 @@ +//// +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 +//// + include::./attributes.adoc[] = {project-name} - Simplified Hibernate ORM with Panache :config-file: application.properties @@ -16,7 +22,7 @@ What we're doing in Panache is allow you to write your Hibernate ORM entities li public class Person extends PanacheEntity { public String name; public LocalDate birth; - public PersonStatus status; + public Status status; public static Person findByName(String name){ return find("name", name).firstResult(); @@ -59,14 +65,12 @@ In your `pom.xml`, add the following dependencies: io.quarkus quarkus-hibernate-orm-panache - provided io.quarkus quarkus-jdbc-postgresql - provided -- @@ -76,13 +80,13 @@ Then add the relevant configuration properties in `{config-file}`. [source,properties] -- # configure your datasource -quarkus.datasource.url: jdbc:postgresql://localhost:5432/mydatabase -quarkus.datasource.driver: org.postgresql.Driver -quarkus.datasource.username: sarah -quarkus.datasource.password: connor +quarkus.datasource.url = jdbc:postgresql://localhost:5432/mydatabase +quarkus.datasource.driver = org.postgresql.Driver +quarkus.datasource.username = sarah +quarkus.datasource.password = connor # drop and create the database at startup (use `update` to only update the schema) -quarkus.hibernate-orm.database.generation=drop-and-create +quarkus.hibernate-orm.database.generation = drop-and-create -- == Defining your entity @@ -96,7 +100,7 @@ columns as public fields: public class Person extends PanacheEntity { public String name; public LocalDate birth; - public PersonStatus status; + public Status status; } -- @@ -109,7 +113,7 @@ You can put all your JPA column annotations on the public fields. If you need a public class Person extends PanacheEntity { public String name; public LocalDate birth; - public PersonStatus status; + public Status status; // this will store all names in lowercase in the DB, // and make them uppercase in the model @@ -161,10 +165,10 @@ person = Person.findById(personId); List livingPersons = Person.list("status", Status.Alive); // counting all persons -int countAll = Person.count(); +long countAll = Person.count(); // counting all living persons -int countAlive = Person.count("status", Status.Alive); +long countAlive = Person.count("status", Status.Alive); // delete all living persons Person.delete("status", Status.Alive); @@ -177,7 +181,8 @@ All `list` methods have equivalent `stream` versions. [source,java] -- -List namesButEmmanuels = Person.streamAll() +Stream persons = Person.streamAll(); +List namesButEmmanuels = persons .map(p -> p.name.toLowerCase() ) .filter( n -> ! "emmanuel".equals(n) ) .collect(Collectors.toList()); @@ -253,7 +258,7 @@ in your entity class: public class Person extends PanacheEntity { public String name; public LocalDate birth; - public PersonStatus status; + public Status status; public static Person findByName(String name){ return find("name", name).firstResult(); diff --git a/docs/src/main/asciidoc/images/kafka-guide-architecture.png b/docs/src/main/asciidoc/images/kafka-guide-architecture.png new file mode 100644 index 0000000000000..2fc98580e1357 Binary files /dev/null and b/docs/src/main/asciidoc/images/kafka-guide-architecture.png differ diff --git a/docs/src/main/asciidoc/images/openapi-swaggerui-guide-screenshot01.png b/docs/src/main/asciidoc/images/openapi-swaggerui-guide-screenshot01.png new file mode 100644 index 0000000000000..b80e63fddbb34 Binary files /dev/null and b/docs/src/main/asciidoc/images/openapi-swaggerui-guide-screenshot01.png differ diff --git a/docs/src/main/asciidoc/images/openapi-swaggerui-guide-screenshot02.png b/docs/src/main/asciidoc/images/openapi-swaggerui-guide-screenshot02.png new file mode 100644 index 0000000000000..821da88efadb8 Binary files /dev/null and b/docs/src/main/asciidoc/images/openapi-swaggerui-guide-screenshot02.png differ diff --git a/docs/src/main/asciidoc/index.adoc b/docs/src/main/asciidoc/index.adoc index 5a47323ccb77b..37616945aeb9b 100644 --- a/docs/src/main/asciidoc/index.adoc +++ b/docs/src/main/asciidoc/index.adoc @@ -32,10 +32,13 @@ include::quarkus-intro.adoc[tag=intro] * link:infinispan-client-guide.html[Using Infinispan Client] * link:spring-di-guide.html[Using our Spring Dependency Injection compatibility layer] * link:kotlin.html[Using Kotlin] +* link:health-guide.html[Using MicroProfile Health] * link:fault-tolerance-guide.html[Using Fault Tolerance] * link:extension-authors-guide.html[Write Your Own Extension] _(advanced)_ +* link:writing-native-applications-tips.html[Tips for writing native applications] _(advanced)_ * link:performance-measure.html[Measuring Performance] _(advanced)_ * link:cdi-reference.html[Contexts and Dependency Injection] _(advanced)_ +* link:keycloak-guide.html[Using Keycloak] * link:faq.html[FAQs] @@ -56,7 +59,7 @@ _Recommended:_ You can contact the project team using: -* email using the http://post-office.corp.redhat.com/mailman/listinfo/quarkus-dev[quarkus-dev@redhat.com] mailing list +* the https://groups.google.com/forum/#!forum/quarkus-dev[quarkus-dev Google Groups] * chat using https://quarkus.zulipchat.com[Zulip] Your help is more than welcome! Don't hesitate to join the crowd. diff --git a/docs/src/main/asciidoc/infinispan-client-guide.adoc b/docs/src/main/asciidoc/infinispan-client-guide.adoc index 8e64809d53284..42324f093a5ab 100644 --- a/docs/src/main/asciidoc/infinispan-client-guide.adoc +++ b/docs/src/main/asciidoc/infinispan-client-guide.adoc @@ -1,3 +1,9 @@ +//// +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 +//// + include::./attributes.adoc[] = {project-name} - Infinispan Client diff --git a/docs/src/main/asciidoc/jwt-guide.adoc b/docs/src/main/asciidoc/jwt-guide.adoc index 9750a72581958..45302841ee596 100644 --- a/docs/src/main/asciidoc/jwt-guide.adoc +++ b/docs/src/main/asciidoc/jwt-guide.adoc @@ -1,3 +1,9 @@ +//// +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 +//// + include::./attributes.adoc[] = {project-name} - Using JWT RBAC :extension-name: Smallrye JWT @@ -11,9 +17,9 @@ secured access to the JAX-RS endpoints. [cols=">. |mp.jwt.verify.publickey.location|none|Config property allows for an external or internal location of Public Key to be specified. The value may be a relative path or a URL. |mp.jwt.verify.issuer|none|Config property specifies the value of the `iss` (issuer) @@ -109,29 +115,32 @@ import org.eclipse.microprofile.jwt.JsonWebToken; * Version 1 of the TokenSecuredResource */ @Path("/secured") +@RequestScoped // <1> public class TokenSecuredResource { @Inject - JsonWebToken jwt; // <1> + JsonWebToken jwt; // <2> @GET() @Path("permit-all") - @PermitAll // <2> + @PermitAll // <3> @Produces(MediaType.TEXT_PLAIN) - public String hello(@Context SecurityContext ctx) { // <3> - Principal caller = ctx.getUserPrincipal(); <4> + public String hello(@Context SecurityContext ctx) { // <4> + Principal caller = ctx.getUserPrincipal(); <5> String name = caller == null ? "anonymous" : caller.getName(); boolean hasJWT = jwt != null; String helloReply = String.format("hello + %s, isSecure: %s, authScheme: %s, hasJWT: %s", name, ctx.isSecure(), ctx.getAuthenticationScheme(), hasJWT); - return helloReply; // <5> + return helloReply; // <6> } } ---- -<1> Here we inject the JsonWebToken interface, and extension of the java.security.Principal interface that provides access to the claims associated with the current authenticated token. -<2> @PermitAll is a JSR 250 common security annotation that indicates that the given endpoint is accessible by any caller, authenticated or not. -<3> Here we inject the JAX-RS SecurityContext to inspect the security state of the call. -<4> Here we obtain the current request user/caller `Principal`. For an unsecured call this will be null, so we build the user name by checking `caller` against null. -<5> The reply we build up makes use of the caller name, the `isSecure()` and `getAuthenticationScheme()` states of the request `SecurityContext`, and whether a non-null `JsonWebToken` was injected. +<1> Add a `RequestScoped` as {project-name} uses a default scoping of `ApplicationScoped` and this +will produce undesirable behavior since JWT claims are naturally request scoped. +<2> Here we inject the JsonWebToken interface, and extension of the java.security.Principal interface that provides access to the claims associated with the current authenticated token. +<3> @PermitAll is a JSR 250 common security annotation that indicates that the given endpoint is accessible by any caller, authenticated or not. +<4> Here we inject the JAX-RS SecurityContext to inspect the security state of the call. +<5> Here we obtain the current request user/caller `Principal`. For an unsecured call this will be null, so we build the user name by checking `caller` against null. +<6> The reply we build up makes use of the caller name, the `isSecure()` and `getAuthenticationScheme()` states of the request `SecurityContext`, and whether a non-null `JsonWebToken` was injected. == Run the application @@ -139,7 +148,7 @@ Now we are ready to run our application. Use: [source,shell] ---- -mvn compile quarkus:dev +./mvnw compile quarkus:dev ---- and you should see output similar to: @@ -147,7 +156,7 @@ and you should see output similar to: .quarkus:dev Output [source,shell] ---- -$ mvn compile quarkus:dev +$ ./mvnw compile quarkus:dev [INFO] Scanning for projects... [INFO] [INFO] ----------------------< org.acme:using-jwt-rbac >----------------------- @@ -205,6 +214,7 @@ import org.eclipse.microprofile.jwt.JsonWebToken; * Version 2 of the TokenSecuredResource */ @Path("/secured") +@RequestScoped public class TokenSecuredResource { @Inject @@ -238,7 +248,7 @@ public class TokenSecuredResource { <2> @RolesAllowed is a JSR 250 common security annotation that indicates that the given endpoint is accessible by a caller if they have either a "Echoer" or "Subscriber" role assigned. -After you make this addition to your `TokenSecuredResource`, rerun the `mvn compile quarkus:dev` command, and then try `curl -v http://127.0.0.1:8080/secured/roles-allowed; echo` to attempt to access the new endpoint. Your output should be: +After you make this addition to your `TokenSecuredResource`, rerun the `./mvnw compile quarkus:dev` command, and then try `curl -v http://127.0.0.1:8080/secured/roles-allowed; echo` to attempt to access the new endpoint. Your output should be: .curl command for /secured/roles-allowed [source,shell] @@ -274,11 +284,11 @@ In the <> section we introduce the `application.properties` file .application.properties for TokenSecuredResource [source, properties] ---- -mp.jwt.verify.publickey.location=publicKey.pem #<1> +mp.jwt.verify.publickey.location=META-INF/resources/publicKey.pem #<1> mp.jwt.verify.issuer=https://quarkus.io/using-jwt-rbac #<2> -quarkus.smalllrye-jwt.auth-mechanism=MP-JWT # <3> -quarkus.smalllrye-jwt.enabled=true # <4> +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`. @@ -333,6 +343,7 @@ import java.security.PublicKey; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.Base64; +import java.util.Date; import java.util.Map; import org.eclipse.microprofile.jwt.Claims; @@ -398,19 +409,14 @@ public class TokenUtils { JSONObject jwtContent = parser.parse(content, JSONObject.class); long currentTimeInSecs = currentTimeInSecs(); long exp = currentTimeInSecs + 300; - long iat = currentTimeInSecs; - long authTime = currentTimeInSecs; - boolean expWasInput = false; - // Check for an input exp to override the default of now + 300 seconds - if (timeClaims != null && timeClaims.containsKey(Claims.exp.name())) { + // If exp was passed in, use it + if (timeClaims.containsKey(Claims.exp.name())) { exp = timeClaims.get(Claims.exp.name()); - expWasInput = true; - } - // iat and auth_time should be before any input exp value - if (expWasInput) { - iat = exp - 5; - authTime = exp - 5; } + System.out.printf("Setting exp: %d / %s%n", exp, new Date(1000*exp)); + long iat = currentTimeInSecs; + long authTime = currentTimeInSecs; + jwtContent.put(Claims.exp.name(), exp); jwtContent.put(Claims.iat.name(), iat); jwtContent.put(Claims.auth_time.name(), authTime); // Return the token time values if requested @@ -423,6 +429,10 @@ public class TokenUtils { // Create RSA-signer with the private key JWSSigner signer = new RSASSASigner(pk); JWTClaimsSet claimsSet = JWTClaimsSet.parse(jwtContent); + for (String claim : claimsSet.getClaims().keySet()) { + Object claimValue = claimsSet.getClaim(claim); + System.out.printf("\tAdded claim: %s, value: %s%n", claim, claimValue); + } JWSAlgorithm alg = JWSAlgorithm.RS256; JWSHeader jwtHeader = new JWSHeader.Builder(alg) .keyID(kid) @@ -676,7 +686,7 @@ Now we can generate a JWT to use with `TokenSecuredResource` endpoint. To do thi mvn exec:java -Dexec.mainClass=org.acme.jwt.GenerateToken -Dexec.classpathScope=test ---- -TIP: You may need to run `mvn test-compile` before this if you are working strictly from the command line and not an IDE that +TIP: You may need to run `./mvnw test-compile` before this if you are working strictly from the command line and not an IDE that automatically compiles code as you write it. .Sample JWT Generation Output @@ -688,7 +698,6 @@ $ mvn exec:java -Dexec.mainClass=org.acme.jwt.GenerateToken -Dexec.classpathScop [INFO] ----------------------< org.acme:using-jwt-rbac >----------------------- [INFO] Building using-jwt-rbac 1.0-SNAPSHOT [INFO] --------------------------------[ jar ]--------------------------------- -Downloading from jboss: https://repository.jboss.org/nexus/content/groups/public/net/minidev/json-smart/maven-metadata.xml [INFO] [INFO] --- exec-maven-plugin:1.6.0:java (default-cli) @ using-jwt-rbac --- Setting exp: 1551659976 / Sun Mar 03 16:39:36 PST 2019 @@ -731,7 +740,6 @@ $ mvn exec:java -Dexec.mainClass=org.acme.jwt.GenerateToken -Dexec.classpathScop [INFO] ----------------------< org.acme:using-jwt-rbac >----------------------- [INFO] Building using-jwt-rbac 1.0-SNAPSHOT [INFO] --------------------------------[ jar ]--------------------------------- -Downloading from jboss: https://repository.jboss.org/nexus/content/groups/public/net/minidev/json-smart/maven-metadata.xml [INFO] [INFO] --- exec-maven-plugin:1.6.0:java (default-cli) @ using-jwt-rbac --- Setting exp: 1551663155 / Sun Mar 03 17:32:35 PST 2019 @@ -758,7 +766,7 @@ eyJraWQiOiJcL3ByaXZhdGVLZXkucGVtIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiO == Finally, Secured Access to /secured/roles-allowed -Now let's use this to make a secured request to the /secured/roles-allowed endpoint. Make sure you have the {project-name} server running using the `mvn compile quarkus:dev` command, and then run the following command, making sure to use your version of the generated JWT from the previous step: +Now let's use this to make a secured request to the /secured/roles-allowed endpoint. Make sure you have the {project-name} server running using the `./mvnw compile quarkus:dev` command, and then run the following command, making sure to use your version of the generated JWT from the previous step: [source,shell] ---- @@ -820,7 +828,8 @@ import org.eclipse.microprofile.jwt.JsonWebToken; * Version 3 of the TokenSecuredResource */ @Path("/secured") - public class TokenSecuredResourceV3 { + @RequestScoped +public class TokenSecuredResourceV3 { @Inject JsonWebToken jwt; @@ -912,6 +921,7 @@ import org.eclipse.microprofile.jwt.JsonWebToken; * Version 4 of the TokenSecuredResource */ @Path("/secured") +@RequestScoped public class TokenSecuredResource { @Inject @@ -969,6 +979,48 @@ $ curl -H "Authorization: Bearer eyJraWQiOiJcL3ByaXZhdGVLZXkucGVtIiwidHlwIjoiSld [13, 38, 36, 38, 36, 22] ---- +== Package and run the application +As usual, the application can be packaged using `./mvnw clean package` and executed using the `-runner.jar` file: +.Runner jar Example +[source,shell] +---- +Scotts-iMacPro:using-jwt-rbac starksm$ ./mvnw clean package +[INFO] Scanning for projects... +... +[INFO] [io.quarkus.creator.phase.runnerjar.RunnerJarPhase] Building jar: /Users/starksm/Dev/JBoss/Protean/starksm64-quarkus-quickstarts/using-jwt-rbac/target/using-jwt-rbac-runner.jar + +Scotts-iMacPro:using-jwt-rbac starksm$ java -jar target/using-jwt-rbac-runner.jar +2019-03-28 14:27:48,839 INFO [io.quarkus] (main) Quarkus 0.12.0 started in 0.796s. Listening on: http://[::]:8080 +2019-03-28 14:27:48,841 INFO [io.quarkus] (main) Installed features: [cdi, resteasy, resteasy-jsonb, security, smallrye-jwt] +---- + +You can also generate the native executable with `./mvnw clean package -Pnative`. +.Native Executable Example +[source,shell] +---- +Scotts-iMacPro:using-jwt-rbac starksm$ ./mvnw clean package -Pnative +[INFO] Scanning for projects... +... +[using-jwt-rbac-runner:25602] universe: 493.17 ms +[using-jwt-rbac-runner:25602] (parse): 660.41 ms +[using-jwt-rbac-runner:25602] (inline): 1,431.10 ms +[using-jwt-rbac-runner:25602] (compile): 7,301.78 ms +[using-jwt-rbac-runner:25602] compile: 10,542.16 ms +[using-jwt-rbac-runner:25602] image: 2,797.62 ms +[using-jwt-rbac-runner:25602] write: 988.24 ms +[using-jwt-rbac-runner:25602] [total]: 43,778.16 ms +[INFO] ------------------------------------------------------------------------ +[INFO] BUILD SUCCESS +[INFO] ------------------------------------------------------------------------ +[INFO] Total time: 51.500 s +[INFO] Finished at: 2019-03-28T14:30:56-07:00 +[INFO] ------------------------------------------------------------------------ + +Scotts-iMacPro:using-jwt-rbac starksm$ ./target/using-jwt-rbac-runner +2019-03-28 14:31:37,315 INFO [io.quarkus] (main) Quarkus 0.12.0 started in 0.006s. Listening on: http://[::]:8080 +2019-03-28 14:31:37,316 INFO [io.quarkus] (main) Installed features: [cdi, resteasy, resteasy-jsonb, security, smallrye-jwt] +---- + == Explore the Solution The solution repository located in the `using-jwt-rbac` {quickstarts-archive-url}[directory] contains all of the versions we have diff --git a/docs/src/main/asciidoc/kafka-guide.adoc b/docs/src/main/asciidoc/kafka-guide.adoc new file mode 100644 index 0000000000000..56cab2cb6b82f --- /dev/null +++ b/docs/src/main/asciidoc/kafka-guide.adoc @@ -0,0 +1,313 @@ +//// +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 +//// + +include::./attributes.adoc[] += {project-name} - Using Apache Kafka with Reactive Messaging + +This guide demonstrates how your {project-name} application can utilize MicroProfile Reactive Messaging to interact with Apache Kafka. + +== 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+ +* A running Kafka cluster, or Docker Compose to start a development cluster +* GraalVM installed if you want to run in native mode. + +== Architecture + +In this guide, we are going to generate (random) prices in one component. +These prices are written in a Kafka topic (`prices`). +A second component reads from the `prices` Kafka topic and apply some magic conversion to the price. +The result is sent to an in-memory stream consumed by a JAX-RS resource. +The data is sent to a browser using server-sent event. + +image::kafka-guide-architecture.png[alt=Architecture] + +== 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 `kafka-quickstart` {quickstarts-tree-url}/kafka-quickstart[directory]. + +== Creating the Maven Project + +First, we need a new project. Create a new project with the following command: + +[source, subs=attributes+] +---- +mvn io.quarkus:quarkus-maven-plugin:{quarkus-version}:create \ + -DprojectGroupId=org.acme \ + -DprojectArtifactId=kafka-quickstart \ + -Dextensions="reactive-kafka,vert.x" +---- + +This command generates a Maven project, importing the Reactive Messaging and Kafka connector extensions. Eclipse Vert.x is also required as the Kafka connector relies on Vert.x. + +== Starting Kafka + +Then, we need a Kafka cluster. +You can follow the instructions from the https://kafka.apache.org/quickstart[Apache Kafka web site] or create a `docker-compose.yaml` file with the following content: + +[source, yaml] +---- +version: '2' + +services: + + zookeeper: + image: wurstmeister/zookeeper:3.4.6 + expose: + - "2181" + + kafka: + image: wurstmeister/kafka:2.12-2.1.1 + depends_on: + - zookeeper + ports: + - "9092:9092" + environment: + KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092 + KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092 + KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 +---- + +Once created, run `docker-compose up`. + +NOTE: This is a development cluster, do not use in production. + +== The price generator + +Create the `src/main/java/org/acme/quarkus/sample/PriceGenerator.java` file, with the following content: + +[source, java] +---- +package org.acme.quarkus.sample; + +import io.reactivex.Flowable; +import org.eclipse.microprofile.reactive.messaging.Outgoing; + +import javax.enterprise.context.ApplicationScoped; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +/** + * A bean producing random prices every 5 seconds. + * The prices are written to a Kafka topic (prices). The Kafka configuration is specified in the application configuration. + */ +@ApplicationScoped +public class PriceGenerator { + + private Random random = new Random(); + + @Outgoing("generated-price") // <1> + public Flowable generate() { // <2> + return Flowable.interval(5, TimeUnit.SECONDS) + .map(tick -> random.nextInt(100)); + } + +} +---- +<1> Instruct Reactive Messaging to dispatch the items from returned stream to `generated-price`. +<2> The method returns a RX Java 2 _stream_ (`Flowable`) emitting a random _price_ every 5 seconds. + +The method returns a _Reactive Streams_. The generated items are sent to the stream named `generated-price`. +This stream is mapped to Kafka using the `application.properties` file that we will create soon. + +== The price converter + +The price converter reads the prices from Kafka, and transforms them. +Creates the `src/main/java/org/acme/quarkus/sample/PriceConverter.java` file with the following content: + +[source, java] +---- +package org.acme.quarkus.sample; + +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 "prices" Kafka topic and applying some conversion. + * The result is pushed to the "my-data-stream" stream which is an in-memory stream. + */ +@ApplicationScoped +public class PriceConverter { + + private static final double CONVERSION_RATE = 0.88; + + @Incoming("prices") // <1> + @Outgoing("my-data-stream") // <2> + @Broadcast // <3> + public double process(int priceInUsd) { + return priceInUsd * CONVERSION_RATE; + } + +} +---- +<1> Indicates that the method consumes the items from the `prices` topic +<2> Indicates that the objects returned by the method are sent to the `my-data-stream` stream +<3> Indicates that the item are dispatched to all _subscribers_ + +The `process` method is called for every Kafka _record_ from the `prices` topic (configured in the application configuration). +Every result is sent to the `my-data-stream` in-memory stream. + +== The price resource + +Finally, let's bind our stream to a JAX-RS resource. +Creates the `src/main/java/org/acme/quarkus/sample/PriceResource.java` file with the following content: + +[source, java] +---- +package org.acme.quarkus.sample; + +import io.smallrye.reactive.messaging.annotations.Stream; +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; + +/** + * A simple resource retrieving the in-memory "my-data-stream" and sending the items to a server sent event. + */ +@Path("/prices") +public class PriceResource { + + @Inject + @Stream("my-data-stream") Publisher prices; // <1> + + @GET + @Produces(MediaType.TEXT_PLAIN) + public String hello() { + return "hello"; + } + + @GET + @Path("/stream") + @Produces(MediaType.SERVER_SENT_EVENTS) // <2> + public Publisher stream() { // <3> + return prices; + } +} +---- +<1> Injects the `my-data-stream` stream using the `@Stream` qualifier +<2> Indicates that the content is sent using `Server Sent Events` +<3> Returns the stream (_Reactive Stream_) + +== Configuring the Kafka connector + +We need to configure Kafka connector. This is done in the `application.properties` file. +The keys are structured as follows: + +`smallrye.messaging.[sink|source].{stream-name}.property=value` + +The `stream-name` segment must match the value set in the `@Incoming` and `@Outgoing` annotation: +* `generated-price` -> sink in which we write the prices +* `prices` -> source in which we read the prices + +[source] +---- +# Configure the Kafka sink (we write to it) +smallrye.messaging.sink.generated-price.type=io.smallrye.reactive.messaging.kafka.Kafka +smallrye.messaging.sink.generated-price.topic=prices +smallrye.messaging.sink.generated-price.bootstrap.servers=localhost:9092 +smallrye.messaging.sink.generated-price.key.serializer=org.apache.kafka.common.serialization.StringSerializer +smallrye.messaging.sink.generated-price.value.serializer=org.apache.kafka.common.serialization.IntegerSerializer +smallrye.messaging.sink.generated-price.acks=1 + +# Configure the Kafka source (we read from it) +smallrye.messaging.source.prices.type=io.smallrye.reactive.messaging.kafka.Kafka +smallrye.messaging.source.prices.topic=prices +smallrye.messaging.source.prices.bootstrap.servers=localhost:9092 +smallrye.messaging.source.prices.key.deserializer=org.apache.kafka.common.serialization.StringDeserializer +smallrye.messaging.source.prices.value.deserializer=org.apache.kafka.common.serialization.IntegerDeserializer +smallrye.messaging.source.prices.group.id=my-group-id +---- + +More details about this configuration is available on the https://kafka.apache.org/documentation/#producerconfigs[Producer configuration] and https://kafka.apache.org/documentation/#consumerconfigs[Consumer configuration] section from the Kafka documentation. + +NOTE: What about `my-data-stream`? This is an in-memory stream, not connected to a message broker. + +== The HTML page + +Final touch, the HTML page reading the converted prices using SSE. + +Create the `src/main/resources/META-INF/resources/prices.html` file, with the following content: + +[source, html] +---- + + + + + Prices + + + + + +
+ +

Last price

+
+

The last price is N/A €.

+
+
+ + + + +---- + +Nothing spectacular here. On each received price, it updates the page. + +== Get it running + +If you followed the instructions, you should have Kafka running. +Then, you just need to run the application using: + +[source, shell] +---- +mvn compile quarkus:dev +---- + +Open `http://localhost:8080/prices.html` in your browser. + +NOTE: If you started the Kafka broker with docker compose, stop it using `CTRL+C` followed by `docker-compose down`. + +== Running Native + +You can build the native executable with: + +[source, shell] +---- +mvn package -Pnative +---- + +== Going further + +This guide has shown how you can interact with Kafka using Quarkus. +It utilizes MicroProfile Reactive Messaging to build data streaming applications. + +If you want to go further check the documentation of https://smallrye.io/smallrye-reactive-messaging[SmallRye Reactive Messaging], the implementation used in Quarkus. diff --git a/docs/src/main/asciidoc/keycloak-guide.adoc b/docs/src/main/asciidoc/keycloak-guide.adoc new file mode 100644 index 0000000000000..b502ab1c64506 --- /dev/null +++ b/docs/src/main/asciidoc/keycloak-guide.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 +//// + +include::./attributes.adoc[] += {project-name} - Using Keycloak to Protect JAX-RS Applications + +This guide demonstrates how your {project-name} application can use Keycloak to protect your JAX-RS applications using bearer token +authorization, where these tokens are issued by a Keycloak Server. + +Bearer Token Authorization is the process of authorizing HTTP requests based on the existence and validity of a bearer token representing a subject and his access context, where the token provides valuable information to determine the subject of the call as well whether or not a HTTP resource can be accessed. + +Keycloak is a OAuth 2.0 compliant Authorization Server, capable of issuing access tokens so that you can use them to access protected resources. We are not going +to enter into the details on what OAuth 2.0 is and how it works but give you a guideline on how to use OAuth 2.0 in your JAX-RS applications using the {project-name} Keycloak Extension. + +If you are already familiar with Keycloak, you'll notice that the extension is basically another adapter implementation but specific for {project-name} applications. Otherwise, you can find more information in https://keycloak.org/[Keycloak documentation]. + +== 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+ +* https://stedolan.github.io/jq/[jq tool] +* Docker + +== Architecture + +In this example, we build a very simple microservice which offers three endpoints: + +* `/api/users/me` +* `/api/admin` +* `/api/confidential` + +These endpoints are protected and can only be accessed if a client is sending a bearer token along with the request, which must be valid (e.g.: signature, expiration and audience) and trusted by the microservice. + +The bearer token is issued by a Keycloak Server and represents the subject to which the token was issued for. For being an OAuth 2.0 Authorization Server, the token also references the client acting on behalf of the user. + +The `/api/users/me` endpoint can be accessed by any user with a valid token. As a response, it returns a JSON document with details about the user where these details are obtained from the information carried on the token. + +The `/api/admin` endpoint is protected with RBAC (Role-Based Access Control) where only users granted with the `admin` role can access. At this endpoint, we use the `@RolesAllowed` annotation to declaratively enforce the access constraint. + +The `/api/confidential` endpoint is also protected with RBAC. However, instead of using a declarative approach, we are externalizing authorization where the decision to whether or not access should be granted is delegated to policies managed in the Keycloak Server. + +== 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 `using-keycloak` {quickstarts-tree-url}/using-keycloak[directory]. + +== Creating the Maven Project + +First, we need a new project. Create a new project with the following command: + +[source, subs=attributes+] +---- +mvn io.quarkus:quarkus-maven-plugin:{quarkus-version}:create \ + -DprojectGroupId=org.acme \ + -DprojectArtifactId=using-keycloak \ + -Dextensions="keycloak, resteasy-jsonb" +---- + +This command generates a Maven project, importing the `keycloak` extension +which is an implementation of a Keycloak Adapter for {project-name} applications and provides all the necessary capabilities to integrate with a Keycloak Server and perform bearer token authorization. + +== Writing the application + +Let's start by implementing the `/api/users/me` endpoint. As you can see from the source code below it is just a regular JAX-RS resource: + +[source,java] +---- +package org.acme.keycloak; + +import javax.annotation.security.RolesAllowed; +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.cache.NoCache; +import org.keycloak.KeycloakSecurityContext; + +public class UsersResource { + + @Inject + KeycloakSecurityContext keycloakSecurityContext; + + @GET + @Path("/me") + @RolesAllowed("user") + @Produces(MediaType.APPLICATION_JSON) + @NoCache + public User me() { + return new User(keycloakSecurityContext); + } + + public class User { + + private final String userName; + + User(KeycloakSecurityContext securityContext) { + this.userName = securityContext.getToken().getPreferredUsername(); + } + + public String getUserName() { + return userName; + } + } +} +---- + +Note that the source code above defines an injection point as follows: + +[source,java] +---- +@Inject +KeycloakSecurityContext keycloakSecurityContext; +---- + +The `KeycloakSecurityContext` is an object produced by the Keycloak extension that you can use to obtain information from tokens sent to your application. In the source code above we are using this object to access the token representation and obtain the username of the user represented by the token. + +The source code for the `/api/admin` endpoint is also very simple. The main difference here is that we are using a `@RolesAllowed` annotation to make sure that only users granted with the `admin` role can access the endpoint: + +[source,java] +---- +package org.acme.keycloak; + +import javax.annotation.security.RolesAllowed; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +@Path("/api/admin") +public class AdminResource { + + @GET + @RolesAllowed("admin") + @Produces(MediaType.TEXT_PLAIN) + public String admin() { + return "granted"; + } +} +---- + +For last, the `/api/confidential` endpoint. As you can see from the source code below, there is no explicit access control defined to this endpoint. The Keycloak extension will enforce access to this endpoint based on the policies defined in the Keycloak Server: + +[source,java] +---- +package org.acme.keycloak; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +@Path("/api/confidential") +public class ConfidentialResource { + + @GET + @Produces(MediaType.TEXT_PLAIN) + public String confidential() { + return "confidential"; + } +} +---- + +For now, don't worry about how the extension enforces access to the `/api/confidential`. Just keep in mind that there are some configuration that we need to define to make this happen. + +== Configuring the application + +The Keycloak extension allows you to define the adapter configuration using either the `application.properties` file or using a `keycloak.json`. Both files should be located at the `src/main/resources` directory. + +=== Configuring using the application.properties file + +[source,properties] +---- +quarkus.keycloak.realm=quarkus +quarkus.keycloak.auth-server-url=http://localhost:8180/auth +quarkus.keycloak.resource=backend-service +quarkus.keycloak.bearer-only=true +quarkus.keycloak.credentials.secret=secret +quarkus.keycloak.policy-enforcer.enforcement-mode=PERMISSIVE +---- + +=== Configuring using the keycloak.json file + +[source,json] +---- +{ + "realm": "quarkus", + "auth-server-url": "http://localhost:8180/auth", + "resource": "backend-service", + "bearer-only" : true, + "credentials": { + "secret": "secret" + }, + "policy-enforcer": { + "enforcement-mode": "PERMISSIVE" + } +} +---- + + +For more details about this file and all the supported options, please take a look at https://www.keycloak.org/docs/latest/securing_apps/index.html#_java_adapter_config[Keycloak Adapter Config]. + +== Starting and Configuring the Keycloak Server + +To start a Keycloak Server you can use Docker and just run the following command: + +[source,bash] +---- +docker run --name keycloak -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin -p 8180:8080 quay.io/keycloak/keycloak +---- + +You should be able to access your Keycloak Server at http://localhost:8180/auth[localhost:8180/auth]. + +Log in as the `admin` user to access the Keycloak Administration Console. Username should be `admin` and password `admin`. + +Import the {quickstarts-tree-url}/using-keycloak/config/quarkus-realm.json[realm configuration file] to create a new realm. For more details, see the Keycloak documentation about how to https://www.keycloak.org/docs/latest/server_admin/index.html#_create-realm[create a new realm]. + +== Running and Using the Application + +=== Running in Developer Mode + +To run the microservice in dev mode, use `./mvnw clean compile quarkus:dev`. + +=== Running in JVM Mode + +When you're done playing with "dev-mode" you can run it as a standard Java application. + +First compile it: + +[source,bash] +---- +mvn package +---- + +Then run it: + +[source,bash] +---- +java -jar ./target/using-keycloak-runner.jar +---- + +=== Runing in Native Mode + +This same demo can be compiled into native code: no modifications required. + +This implies that you no longer need to install a JVM on your +production environment, as the runtime technology is included in +the produced binary, and optimized to run with minimal resource overhead. + +Compilation will take a bit longer, so this step is disabled by default; +let's build again by enabling the `native` profile: + +[source,bash] +---- +mvn package -Dnative +---- + +After getting a cup of coffee, you'll be able to run this binary directly: + +[source,bash] +---- +./target/using-keycloak-runner +---- + +== Testing the Application + +The application is using bearer token authorization and the first +thing to do is obtain an access token from the Keycloak Server in +order to access the application resources: + +```bash +export access_token=$(\ + curl -X POST http://localhost:8180/auth/realms/quarkus/protocol/openid-connect/token \ + --user backend-service:secret \ + -H 'content-type: application/x-www-form-urlencoded' \ + -d 'username=alice&password=alice&grant_type=password' | jq --raw-output '.access_token' \ + ) +``` + +The example above obtains an access token for user `alice`. + +Any user is allowed to access the +`http://localhost:8080/api/users/me` endpoint +which basically returns a JSON payload with details about the user. + +```bash +curl -v -X GET \ + http://localhost:8080/api/users/me \ + -H "Authorization: Bearer "$access_token +``` + +The `http://localhost:8080/api/admin` endpoint can only be accessed by users with the `admin` role. If you try to access this endpoint with the + previously issued access token, you should get a `403` response + from the server. + +```bash + curl -v -X GET \ + http://localhost:8080/api/admin \ + -H "Authorization: Bearer "$access_token +``` + +In order to access the admin endpoint you should obtain a token for the `admin` user: + +```bash +export access_token=$(\ + curl -X POST http://localhost:8180/auth/realms/quarkus/protocol/openid-connect/token \ + --user backend-service:secret \ + -H 'content-type: application/x-www-form-urlencoded' \ + -d 'username=admin&password=admin&grant_type=password' | jq --raw-output '.access_token' \ + ) +``` + +The `http://localhost:8080/api/confidential` endpoint is protected with a policy defined in the Keycloak Server. The policy only grants access to the resource if the user is granted with a `confidential` role. The difference here is that the application is delegating the access decision to Keycloak. To access the confidential endpoint, you should obtain an access token for user `jdoe`: + +```bash +export access_token=$(\ + curl -X POST http://localhost:8180/auth/realms/quarkus/protocol/openid-connect/token \ + --user backend-service:secret \ + -H 'content-type: application/x-www-form-urlencoded' \ + -d 'username=jdoe&password=jdoe&grant_type=password' | jq --raw-output '.access_token' \ +) +``` + +== References + +* https://www.keycloak.org/documentation.html[Keycloak Documentation] +* https://www.keycloak.org/docs/latest/securing_apps/index.html#_java_adapter_config[Keycloak Adapter Configuration(keycloak.json)] +* https://www.keycloak.org/docs/latest/authorization_services/index.html#_enforcer_overview[Keycloak Policy Enforcer] diff --git a/docs/src/main/asciidoc/kotlin.adoc b/docs/src/main/asciidoc/kotlin.adoc index c1a83691757e4..36b78ffa75fc2 100644 --- a/docs/src/main/asciidoc/kotlin.adoc +++ b/docs/src/main/asciidoc/kotlin.adoc @@ -1,3 +1,9 @@ +//// +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 +//// + include::./attributes.adoc[] = {project-name} - Using Kotlin @@ -121,7 +127,7 @@ Future versions of {project-name} will configure the Kotlin compiler plugin in a {project-name} provides support for live reloading changes made to source code. This support is also available to Kotlin, meaning that developers can update their Kotlin source code and immediately see their changes reflected. -To see this feature in action, first execute: `mvn compile quarkus:dev` +To see this feature in action, first execute: `./mvnw compile quarkus:dev` When executing an HTTP GET request against `http://localhost:8080/greeting`, you should get `hello` as a response. @@ -138,4 +144,4 @@ 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 `mvn clean package` and executed using the `-runner.jar` file. You can also build the native executable using `mvn package -Pnative`. \ 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`. \ No newline at end of file diff --git a/docs/src/main/asciidoc/kubernetes-guide.adoc b/docs/src/main/asciidoc/kubernetes-guide.adoc index 3d2b4ff90d84f..28ff185de5132 100644 --- a/docs/src/main/asciidoc/kubernetes-guide.adoc +++ b/docs/src/main/asciidoc/kubernetes-guide.adoc @@ -1,3 +1,9 @@ +//// +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 +//// + include::./attributes.adoc[] = {project-name} - Deploying on Kubernetes and OpenShift @@ -40,7 +46,7 @@ For `minikube`, execute: # For minishift use "eval $(minishift docker-env)" instead eval $(minikube docker-env) -docker build -f src/main/docker/Dockerfile -t quarkus-quickstart/quickstart . +docker build -f src/main/docker/Dockerfile.native -t quarkus-quickstart/quickstart . ---- Once the image has been pushed to the Kubernetes image registry, instantiate the application as follows: @@ -55,7 +61,7 @@ The application is now exposed as an internal service. If you are using `minikub [source,shell] ---- -curl $(minikube service quarkus-quickstart --url)/hello/greeting/quarkus +curl $(minikube service quarkus-quickstart --url)/hello ---- == Deploying the application in OpenShift @@ -66,7 +72,7 @@ In this section, we are going to leverage the build mechanism of OpenShift. Run: ---- # To build the image on OpenShift oc new-build --binary --name=quarkus-quickstart -l app=quarkus-quickstart -oc patch bc/quarkus-quickstart -p '{"spec":{"strategy":{"dockerStrategy":{"dockerfilePath":"src/main/docker/Dockerfile"}}}}' +oc patch bc/quarkus-quickstart -p '{"spec":{"strategy":{"dockerStrategy":{"dockerfilePath":"src/main/docker/Dockerfile.native"}}}}' oc start-build quarkus-quickstart --from-dir=. --follow # To instantiate the image @@ -77,7 +83,8 @@ oc expose service quarkus-quickstart # Get the route URL export URL="http://$(oc get route | grep quarkus-quickstart | awk '{print $2}')" -curl $URL/hello/greeting/quarkus +echo "Application URL: $URL" +curl $URL/hello && echo ---- Your application is accessible at the printed URL. diff --git a/docs/src/main/asciidoc/logging-guide.adoc b/docs/src/main/asciidoc/logging-guide.adoc index 3664398cc29e5..944448ce68e65 100644 --- a/docs/src/main/asciidoc/logging-guide.adoc +++ b/docs/src/main/asciidoc/logging-guide.adoc @@ -1,3 +1,9 @@ +//// +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 +//// + include::./attributes.adoc[] = {project-name} - Configuring Logging @@ -22,15 +28,19 @@ Console logging is enabled by default. To configure or disable it, the followin === File configuration -Logging to a file is also supported and enabled by default. To configure or disable this behavior, use the following configuration properties: +Logging to a file is also supported but not enabled by default. To configure or enable this behavior, use the following configuration properties: [cols=">. -|quarkus.log.file.level|INFO|The minimum log level to write to the log file. +|quarkus.log.file.level|ALL|The minimum log level to write to the log file. |quarkus.log.file.path|quarkus.log|The path of the log file. +|quarkus.log.file.rotation.max-file-size||The maximum file size of the log file after which a rotation is executed. +|quarkus.log.file.rotation.max-backup-index|1| The maximum number of backups to keep. +|quarkus.log.file.rotation.file-suffix|| Rotating log file suffix. The format of suffix value has to be understood by `java.text.SimpleDateFormat`. +|quarkus.log.file.rotation.rotate-on-boot|true| Indicates whether to rotate log files on server initialization. |=== === Logging categories @@ -88,6 +98,9 @@ The logging format string supports the following symbols: |%t|Thread name|Render the thread name. |%t{id}|Thread ID|Render the thread ID. |%z{}|Time zone|Set the time zone of the output to ``. +|%X{}|Mapped Diagnostics Context Value|Renders the value from Mapped Diagnostics Context +|%X|Mapped Diagnostics Context Values|Renders all the values from Mapped Diagnostics Context in format {property.key=property.value} +|%x|Nested Diagnostics context values|Renders all the values from Nested Diagnostics Context in format {value1.value2} |=== @@ -100,8 +113,12 @@ quarkus.log.console.enable=true quarkus.log.console.format=%d{HH:mm:ss} %-5p [%c{2.}]] (%t) %s%e%n quarkus.log.console.level=DEBUG quarkus.log.console.color=false + +quarkus.log.category."io.quarkus".level=DEBUG ---- +NOTE: If you are adding these properties via command line make sure `"` is escaped. +For example `-Dquarkus.log.category.\"io.quarkus\".level=DEBUG`. [#category-example] .File TRACE Logging Configuration diff --git a/docs/src/main/asciidoc/maven-tooling.adoc b/docs/src/main/asciidoc/maven-tooling.adoc index d4872fcb30070..5610c96809a01 100644 --- a/docs/src/main/asciidoc/maven-tooling.adoc +++ b/docs/src/main/asciidoc/maven-tooling.adoc @@ -1,3 +1,9 @@ +//// +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 +//// + include::./attributes.adoc[] = Building {project-name} apps with Maven @@ -58,20 +64,24 @@ If you use the default `path`, the URL is: http://localhost:8080/hello. The project is either generated in the current directory or in a directory named after the passed artifactId. If the current directory is empty, the project is generated in-place. -A `Dockerfile` is also generated in `src/main/docker`. -Instructions to build the image and run the container are written in the `Dockerfile`. +A pair of Dockerfiles for native and jvm mode are also generated in `src/main/docker`. +Instructions to build the image and run the container are written in those Dockerfiles. == Dealing with extensions From inside a {project-name} project, you can obtain a list of the available extensions with: [source,shell] -mvn quarkus:list-extensions +---- +./mvnw quarkus:list-extensions +---- You can enable an extension using: [source,shell] -mvn quarkus:add-extension -Dextensions="hibernate-validator" +---- +./mvnw quarkus:add-extension -Dextensions="hibernate-validator" +---- Extensions are passed using a comma-separated list. @@ -81,7 +91,9 @@ Extensions are passed using a comma-separated list. Run your application with: [source,shell] -mvn compile quarkus:dev +---- +./mvnw compile quarkus:dev +---- You can then update the application sources, resources and configurations. The changes are automatically reflected in your running application. @@ -97,12 +109,48 @@ with compilation or deployment an error page will let you know. Hit `CTRL+C` to stop the application. +=== Remote Development Mode + +It is possible to use development mode remotely, so that you can run Quarkus in a container environment (such as Openshift) +and have changes made to your local files become immediately visible. + +This allows you to develop in the same environment you will actually run your app in, and with access to the same services. + +WARNING: Do not use this in production. This should only be used in a development environment. You should not run production application in dev mode. + +To do this you must have the `quarkus-undertow-websockets` extension installed: + +[source,shell] +---- +./mvnw quarkus:add-extension -Dextensions="undertow-websockets" +---- + +You must also have the following config properties set: + +- `quarkus.live-reload.password` +- `quarkus.live-reload.url` + +These can be set via `application.properties`, or any other way (e.g. system properties, environment vars etc). The +password must be set on both the local and remote processes, while the url only needs to be set on the local host. + +Start Quarkus in dev mode on the remote host. Now you need to connect your local agent to the remote host: + +[source,shell] +---- +./mvnw quarkus:remote-dev -Dquarkus.live-reload.url=http://my-remote-host:8080 +---- + +Now every time you refresh the browser you should see any changes you have made locally immediately visible in the remote +app. + == Debugging You can run a {project-name} application in debug mode using: [source,shell] -mvn compile quarkus:dev -Ddebug=true +---- +./mvnw compile quarkus:dev -Ddebug=true +---- Then, attach your debugger to `localhost:5005`. @@ -118,7 +166,7 @@ In the wizard, select: `Maven -> Existing Maven Project`. On the next screen, select the root location of the project. The next screen list the found modules; select the generated project and click on `Finish`. Done! -In a separated terminal, run `mvn compile quarkus:dev`, and enjoy a highly productive environment. +In a separated terminal, run `./mvnw compile quarkus:dev`, and enjoy a highly productive environment. **IntelliJ** @@ -130,7 +178,7 @@ In IntelliJ: 4. Next a few times (review the different options if needed) 5. On the last screen click on Finish -In a separated terminal or in the embedded terminal, run `mvn compile quarkus:dev`. Enjoy! +In a separated terminal or in the embedded terminal, run `./mvnw compile quarkus:dev`. Enjoy! **Apache Netbeans** @@ -140,7 +188,7 @@ In Netbeans: 2. Select the project root 3. Click on `Open Project` -In a separated terminal or the embedded terminal, go to the project root and run `mvn compile quarkus:dev`. Enjoy! +In a separated terminal or the embedded terminal, go to the project root and run `./mvnw compile quarkus:dev`. Enjoy! **Visual Studio Code** @@ -153,14 +201,14 @@ Native executables make {project-name} applications ideal for containers and ser Make sure to have `GRAALVM_HOME` configured and pointing to GraalVM version {graalvm-version}. Verify that your `pom.xml` has the proper `native` profile (see <>). -Create a native executable using: `mvn package -Pnative`. +Create a native executable using: `./mvnw package -Pnative`. A native executable will be present in `target/`. To run Integration Tests on the native executable, make sure to have the proper Maven plugin configured (see <>) and launch the `verify` goal. [source,shell] ---- -mvn verify -Pnative +./mvnw verify -Pnative ... [quarkus-quickstart-runner:50955] universe: 391.96 ms [quarkus-quickstart-runner:50955] (parse): 904.37 ms @@ -196,7 +244,7 @@ To create an executable that will run in a container, use the following: [source,shell] ---- -mvn package -Pnative -Dnative-image.docker-build=true +./mvnw package -Pnative -Dnative-image.docker-build=true ---- The produced executable will be a 64 bit Linux executable, so depending on your operating system it may no longer be runnable. @@ -264,7 +312,7 @@ If you have not used <>, add the following <4> org.apache.maven.plugins maven-failsafe-plugin - ${surefire.version} + ${surefire-plugin.version} @@ -289,3 +337,65 @@ If you have not used <>, add the following <2> Use the {project-name} Maven plugin that will hook into the build process <3> Use a native profile and plugin to activate GraalVM compilation <4> If you want to test your native executable with Integration Tests, add the following plugin configuration. Test names `*IT` and annotated `@SubstrateTest` will be run against the native executable. See the link:building-native-image-guide.html[Native executable guide] for more info. + +[[uber-jar-maven]] +=== Uber-Jar Creation + +Quarkus Maven plugin supports the generation of Uber-Jars by specifying an `` configuration option in your `pom.xml`. + +[source,xml,subs=attributes+] +---- + + + + io.quarkus + quarkus-maven-plugin + ${quarkus.version} + + true <1> + + + + + build + + + + + + +---- + +<1> Specifies that we want to build an Uber-Jar instead of a regular one. The regular jar will still be present in the `target` directory but it will be renamed to contain the `.original` suffix. + +When building an Uber-Jar you can specify entries that you want to exclude from the generated jar by using the `` configuration option. + +[source,xml,subs=attributes+] +---- + + + + io.quarkus + quarkus-maven-plugin + ${quarkus.version} + + true + + META-INF/DEPENDENCIES.txt <1> + + + + + + build + + + + + + +---- + +<1> The entries are relative to the root of the generated Uber-Jar. You can specify multiple entries by adding extra `` elements. + + diff --git a/docs/src/main/asciidoc/metrics-guide.adoc b/docs/src/main/asciidoc/metrics-guide.adoc new file mode 100644 index 0000000000000..bcdade0d75f90 --- /dev/null +++ b/docs/src/main/asciidoc/metrics-guide.adoc @@ -0,0 +1,187 @@ +//// +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 +//// + +include::./attributes.adoc[] += {project-name} - MicroProfile Metrics + +This guide demonstrates how your {project-name} application can utilize the MicroProfile +Metrics specification through the SmallRye Metrics extension. + +MicroProfile Metrics allows applications to gather various metrics and statistics that provide +insights into what is happening inside the application. + +The metrics can be read remotely using JSON format or the OpenMetrics format, so that +they can be processed by additional tools such as Prometheus, and stored for analysis +and visualisation. + +== 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+ + +== Architecture +In this example, we build a very simple microservice which offers one REST endpoint and that endpoint serves +for determining whether a number is prime. The implementation class is annotated with some metric annotations +so that while responding to user's requests, some metrics are gathered. The meaning of each metric will be explained later. + +== 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 `microprofile-metrics` {quickstarts-tree-url}/microprofile-metrics[directory]. + +== Creating the Maven Project + +First, we need a new project. Create a new project with the following command: + +[source, subs=attributes+] +---- +mvn io.quarkus:quarkus-maven-plugin:{quarkus-version}:create \ + -DprojectGroupId=org.acme \ + -DprojectArtifactId=microprofile-metrics \ + -Dextensions="smallrye-metrics, resteasy" +---- + +This command generates a Maven project, importing the `smallrye-metrics` extension +which is an implementation of the MicroProfile Metrics specification used in {project-name}. + +== Writing the application + +The application consists of a single class that implements an algorithm for checking whether a number is prime. +This algorithm is exposed over a REST interface. Additionally, we need a few annotations to make sure +that our desired metrics are calculated over time and can be exported for manual analysis or processing by additional tooling. + +The metrics that we will gather are these: + +* `performedChecks`: A counter which is increased by one each time the user asks about a number. +* `highestPrimeNumberSoFar`: This is a gauge that stores the highest number that was asked about by the user and which was determined to be prime. +* `checksTimer`: This is a timer, therefore a compound metric that benchmarks how much time the primality tests take. We will explain that one in more details later. + +The full source code looks like this: + +[source,java] +---- +package org.acme.metrics; + +import org.eclipse.microprofile.metrics.MetricUnits; +import org.eclipse.microprofile.metrics.annotation.Counted; +import org.eclipse.microprofile.metrics.annotation.Gauge; +import org.eclipse.microprofile.metrics.annotation.Timed; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; + +@Path("/") +public class PrimeNumberChecker { + + private long highestPrimeNumberSoFar = 2; + + @GET + @Path("/{number}") + @Produces("text/plain") + @Counted(name = "performedChecks", monotonic = true, description = "How many primality checks have been performed.") + @Timed(name = "checksTimer", description = "A measure of how long it takes to perform the primality test.", unit = MetricUnits.MILLISECONDS) + public String checkIfPrime(@PathParam("number") long number) { + if (number < 1) { + return "Only natural numbers can be prime numbers."; + } + if (number == 1) { + return "1 is not prime."; + } + if (number == 2) { + return "2 is prime."; + } + if (number % 2 == 0) { + return number + " is not prime, it is divisible by 2."; + } + for (int i = 3; i < Math.floor(Math.sqrt(number)) + 1; i = i + 2) { + if (number % i == 0) { + return number + " is not prime, is divisible by " + i + "."; + } + } + if (number > highestPrimeNumberSoFar) { + highestPrimeNumberSoFar = number; + } + return number + " is prime."; + } + + @Gauge(name = "highestPrimeNumberSoFar", unit = MetricUnits.NONE, description = "Highest prime number so far.") + public Long highestPrimeNumberSoFar() { + return highestPrimeNumberSoFar; + } + +} +---- + +== Running and using the application + +To run the microservice in dev mode, use `./mvnw clean compile quarkus:dev` + +=== Generate some values for the metrics +First, ask the endpoint whether some numbers are prime numbers. + + curl localhost:8080/350 + +The application will respond that 350 is not a prime number because it can be divided by 2. + +Now for some large prime number so that the test takes a bit more time: + + curl localhost:8080/629521085409773 + +The application will respond that 629521085409773 is a prime number. +If you want, try some more calls with numbers of your choice. + +=== Review the generated metrics +To view the metrics, execute `curl -H"Accept: application/json" localhost:8080/metrics/application` +You will receive a response such as: + +``` +{ + "org.acme.metrics.PrimeNumberChecker.checksTimer" : { + "p50": 217.231273, + "p75": 217.231273, + "p95": 217.231273, + "p98": 217.231273, + "p99": 217.231273, + "p999": 217.231273, + "min": 0.58961, + "mean": 112.15909190834819, + "max": 217.231273, + "stddev": 108.2721053982776, + "count": 2, + "meanRate": 0.04943519091742238, + "oneMinRate": 0.2232140583080189, + "fiveMinRate": 0.3559527083952095, + "fifteenMinRate": 0.38474303050928976 + }, + "org.acme.metrics.PrimeNumberChecker.performedChecks" : 2, + "org.acme.metrics.PrimeNumberChecker.highestPrimeNumberSoFar" : 629521085409773 +} +``` + +Let's explain the meaning of each metric: + +* `performedChecks`: A counter which is increased by one each time the user asks about a number. +* `highestPrimeNumberSoFar`: This is a gauge that stores the highest number that was asked about by the user and which was determined to be prime. +* `checksTimer`: This is a timer, therefore a compound metric that benchmarks how much time the primality tests take. All durations are measured in milliseconds. It consists of these values: +** `min`: The shortest duration it took to perform a primality test, probably it was performed for a small number. +** `max`: The longest duration, probably it was with a large prime number. +** `mean`: The mean value of the measured durations. +** `stddev`: The standard deviation. +** `count`: The number of observations (so it will be the same value as `performedChecks`). +** `p50, p75, p95, p99, p999`: Percentiles of the durations. For example the value in `p95` means that 95 % of the measurements were faster than this duration. +** `meanRate, oneMinRate, fiveMinRate, fifteenMinRate`: Mean throughput and one-, five-, and fifteen-minute exponentially-weighted moving average throughput. + +If you prefer an OpenMetrics export rather than the JSON format, remove the `-H"Accept: application/json"` argument from your command line. diff --git a/docs/src/main/asciidoc/native-and-ssl-guide.adoc b/docs/src/main/asciidoc/native-and-ssl-guide.adoc index fdc16f5629494..198959b63b62a 100644 --- a/docs/src/main/asciidoc/native-and-ssl-guide.adoc +++ b/docs/src/main/asciidoc/native-and-ssl-guide.adoc @@ -1,3 +1,9 @@ +//// +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 +//// + include::./attributes.adoc[] = Quarkus - Using SSL With Native Executables @@ -33,7 +39,7 @@ which configures our REST client to connect to an SSL REST service. Now let's build the application as a native executable and run the tests: ``` -mvn clean install -Pnative +./mvnw clean install -Pnative ``` And we obtain the following result: @@ -86,7 +92,7 @@ quarkus.ssl.native=false And let's try to build again: ``` -mvn clean install -Pnative +./mvnw clean install -Pnative ``` The native executable tests will fail with the following error: @@ -103,7 +109,7 @@ org.acme.restclient.CountriesService/mp-rest/url=http://restcountries.eu/rest And build again: ``` -mvn clean install -Pnative +./mvnw clean install -Pnative ``` If you check carefully the native executable build options, you can see that the SSL related options are gone: @@ -138,7 +144,7 @@ git checkout -- src/main/resources/application.properties And let's build the native executable again: ``` -mvn clean install -Pnative +./mvnw clean install -Pnative ``` == The SunEC library and friends diff --git a/docs/src/main/asciidoc/openapi-swaggerui-guide.adoc b/docs/src/main/asciidoc/openapi-swaggerui-guide.adoc new file mode 100644 index 0000000000000..5ca60eab37f4a --- /dev/null +++ b/docs/src/main/asciidoc/openapi-swaggerui-guide.adoc @@ -0,0 +1,307 @@ +//// +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 +//// + +include::./attributes.adoc[] += {project-name} - Using OpenAPI and Swagger UI + +This guide explains how your Quarkus application can expose its API description through an OpenAPI specification and +how you can test it via a user-friendly UI named Swagger UI. + +== 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+ + +== Architecture + +In this guide, we create a straightforward REST application to demonstrate how fast you can expose your API +specification and benefit from a user interface to test it. + +== Solution + +We recommend that you follow the instructions in the next sections and create the application step by step. +However, you can skip 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 `using-openapi-swaggerui` {quickstarts-tree-url}/using-openapi-swaggerui[directory]. + +== Creating the Maven project + +First, we need a new project. Create a new project with the following command: + +[source, subs=attributes+] +---- +mvn io.quarkus:quarkus-maven-plugin:{quarkus-version}:create \ + -DprojectGroupId=org.acme \ + -DprojectArtifactId=using-openapi-swaggerui \ + -DclassName="org.acme.openapi.swaggerui.FruitResource" \ + -Dpath="/fruits" \ + -Dextensions="resteasy-jsonb" +---- + +This command generates the Maven project with a `/fruits` REST endpoint. + +== Expose a REST Resource + +We will create a `Fruit` bean and a `FruitResouce` REST resource +(feel free to take a look to the link:rest-json-guide.html[Writing JSON REST services guide] if your want more details on how to build a REST API with Quarkus). + +[source,java] +---- +package org.acme.openapi.swaggerui; + +import java.util.Objects; + +public class Fruit { + + private String name; + + private String description; + + public Fruit() { + } + + public Fruit(String name, String description) { + this.name = name; + this.description = description; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Fruit)) { + return false; + } + + Fruit other = (Fruit) obj; + + return Objects.equals(other.name, this.name); + } + + @Override + public int hashCode() { + return Objects.hash(this.name); + } +} +---- + +[source,java] +---- +package org.acme.openapi.swaggerui; + +import javax.ws.rs.*; +import javax.ws.rs.core.MediaType; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Set; + +@Path("/fruits") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +public class FruitResource { + + private Set fruits = Collections.newSetFromMap(Collections.synchronizedMap(new LinkedHashMap<>())); + + public FruitResource() { + fruits.add(new Fruit("Apple", "Winter fruit")); + fruits.add(new Fruit("Pineapple", "Tropical fruit")); + } + + @GET + public Set list() { + return fruits; + } + + @POST + public Set add(Fruit fruit) { + fruits.add(fruit); + return fruits; + } + + @DELETE + public Set delete(Fruit fruit) { + fruits.remove(fruit); + return fruits; + } +} +---- + +As we changed the API, we also need to update the test: + +[source,java] +---- +package org.acme.openapi.swaggerui; + +import io.quarkus.test.junit.QuarkusTest; +import org.junit.jupiter.api.Test; + +import javax.ws.rs.core.MediaType; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.Matchers.containsInAnyOrder; + +@QuarkusTest +public class FruitResourceTest { + + @Test + public void testList() { + given() + .when().get("/fruits") + .then() + .statusCode(200) + .body("$.size()", is(2), + "name", containsInAnyOrder("Apple", "Pineapple"), + "description", containsInAnyOrder("Winter fruit", "Tropical fruit")); + } + + @Test + public void testAdd() { + given() + .body("{\"name\": \"Pear\", \"description\": \"Winter fruit\"}") + .header("Content-Type", MediaType.APPLICATION_JSON) + .when() + .post("/fruits") + .then() + .statusCode(200) + .body("$.size()", is(3), + "name", containsInAnyOrder("Apple", "Pineapple", "Pear"), + "description", containsInAnyOrder("Winter fruit", "Tropical fruit", "Winter fruit")); + + given() + .body("{\"name\": \"Pear\", \"description\": \"Winter fruit\"}") + .header("Content-Type", MediaType.APPLICATION_JSON) + .when() + .delete("/fruits") + .then() + .statusCode(200) + .body("$.size()", is(2), + "name", containsInAnyOrder("Apple", "Pineapple"), + "description", containsInAnyOrder("Winter fruit", "Tropical fruit")); + } +} +---- + +== Expose OpenAPI Specifications + +Quarkus proposes a `smallrye-openapi` extension compliant with the https://github.com/eclipse/microprofile-open-api/[Eclipse MicroProfile OpenAPI] +specification in order to generate your API https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md[OpenAPI v3 specification]. + +You just need to add the `smallrye-openapi` extension to your Quarkus application: +---- +./mvnw quarkus:add-extension -Dextensions="smallrye-openapi" +---- + +Now, we are ready to run our application: +---- +./mvnw compile quarkus:dev +---- + +Once your application is started, you can make a request to the default `/openapi` endpoint: +---- +$ curl http://localhost:8080/openapi +openapi: 3.0.1 +info: + title: Generated API + version: "1.0" +paths: + /fruits: + get: + responses: + 200: + description: OK + content: + application/json: {} + post: + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Fruit' + responses: + 200: + description: OK + content: + application/json: {} + delete: + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Fruit' + responses: + 200: + description: OK + content: + application/json: {} +components: + schemas: + Fruit: + properties: + description: + type: string + name: + type: string +---- + +Hit `CTRL+C` to stop the application. + +== Use Swagger UI for development + +When building APIs, developers want to test them quickly. https://swagger.io/tools/swagger-ui/[Swagger UI] is a great tool +permitting to visualize and interact with your APIs. +The UI is automatically generated from your OpenAPI specification. + +The Quarkus `smallrye-openapi` extension comes with a `swagger-ui` extension embedding a properly configured Swagger UI page. + +NOTE: Swagger UI is only available when Quarkus is started in dev or test mode. + +By default, Swagger UI is accessible at `/swagger-ui`. + +You can update this path by setting the `quarkus.swagger-ui.path` property in your `application.properties`: +---- +quarkus.swagger-ui.path=/my-custom-path +---- + +Now, we are ready to run our application: +---- +./mvnw compile quarkus:dev +---- + +You can check the Swagger UI path in your application's log: +---- +00:00:00,000 INFO [io.qua.swa.run.SwaggerUiServletExtension] Swagger UI available at /swagger-ui +---- + +Once your application is started, you can go to http://localhost:8080/swagger-ui and play with your API. + +You can visualize your API's operations and schemas. +image:openapi-swaggerui-guide-screenshot01.png[alt=Visualize your API] + +You can also interact with your API in order to quickly test it. +image:openapi-swaggerui-guide-screenshot02.png[alt=Interact with your API] + +Hit `CTRL+C` to stop the application. diff --git a/docs/src/main/asciidoc/openshift-s2i-guide.adoc b/docs/src/main/asciidoc/openshift-s2i-guide.adoc new file mode 100644 index 0000000000000..c0a3464ab80d3 --- /dev/null +++ b/docs/src/main/asciidoc/openshift-s2i-guide.adoc @@ -0,0 +1,148 @@ +//// +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 +//// + +include::./attributes.adoc[] += {project-name} - Deploying on OpenShift with S2I + +This guide covers: + +* The deployment of the application to OpenShift using S2I to build + +== Prerequisites + +For this guide you need: + +* roughly 5 minutes +* having access to an OpenShift cluster. Minishift is a valid option. + +== Solution + +We recommend to follow the instructions in the next sections and build 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 `getting-started` directory. + +== Deploying the application as GraalVM native executable in OpenShift + +In this section, we are going to leverage the S2I build mechanism of OpenShift. +We use Quarkus' GraalVM Native S2I Builder, and therefore do not need a `Dockerfile` in this approach. +You do not need to locally clone the Git repository, as it will be directly built inside OpenShift. +We are going to create an OpenShift `build` executing it: + +[source,shell, subs="attributes"] +---- +# To build the image on OpenShift +oc new-app quay.io/quarkus/centos-quarkus-native-s2i~{quickstarts-clone-url} --context-dir=getting-started --name=quarkus-quickstart-native +oc logs -f bc/quarkus-quickstart-native + +# To create the route +oc expose svc/quarkus-quickstart-native + +# Get the route URL +export URL="http://$(oc get route | grep quarkus-quickstart-native | awk '{print $2}')" +echo $URL +curl $URL/hello/greeting/quarkus +---- + +Your application is accessible at the printed URL. + +Note that GraalVM-based native build are more memory & CPU intensive than regular pure Java builds. +https://docs.openshift.com/container-platform/3.11/dev_guide/builds/advanced_build_operations.html[By default, builds are completed by pods using unbound resources, such as memory and CPU], +but note that https://docs.openshift.com/container-platform/3.11/admin_guide/limits.html#admin-guide-limits[your OpenShift Project may have limit ranges defined]. + +Testing indicates that the "hello, world" getting-started demo application builds in around 2 minutes on typical hardware when the build is given 4 GB of RAM and 4 (virtual) CPUs for concurrency. You therefore may need to increase the respective limits for OpenShift's S2I build containers like so: + +[source,yaml] +---- +apiVersion: "v1" +kind: "BuildConfig" +metadata: + name: "quarkus-quickstart-native" +spec: + resources: + limits: + cpu: '4' + memory: 4Gi +---- + +The following `oc patch` command adds these `limits`, and `oc start-build` launches a new build: + +[source,shell] +---- +oc patch bc/quarkus-quickstart-native -p '{"spec":{"resources":{"limits":{"cpu":"4", "memory":"4Gi"}}}}' + +oc start-build quarkus-quickstart-native +---- + +== Building a minimal runtime container + +As an alternative to creating an application from the S2I build process one can use chained builds to produce a runner image that is minimal in size and does not contain all the dependencies required to build the application. + +The following command will create a chained build that is triggered whenever the quarkus-quickstart-native is built. + +[source,shell] +---- +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' \ +---- + +To create a service from the minimal build run the following command: + +[source,shell] +---- +oc new-app minimal-quarkus-quickstart-native + +oc expose svc minimal-quarkus-quickstart-native +---- + +The end result is an image that is less than 40 MB in size (compressed) and does not contain build dependencies like GraalVM, OpenJDK, Maven, etc. + +[TIP] +.Creating build without config. +==== +The minimal build is depending on the S2I build since it is using the output (native runnable application) from the S2I build. However, you do not need to create an application with `oc new-app`. Instead you could use `oc new-build` like this: +[source, shell, subs="attributes"] +---- +oc new-build quay.io/quarkus/centos-quarkus-native-s2i~{quickstarts-clone-url} \ + --context-dir=getting-started --name=quarkus-quickstart-native +---- +==== + +== Deploying the application as Java application in OpenShift + +In this section, we are going to leverage the S2I build mechanism of OpenShift. +We use a Java S2I Builder, and therefore do not need a `Dockerfile` in this approach. +You do not need to locally clone the Git repository, as it will be directly built inside OpenShift. +We are going to create an OpenShift `build` executing it: + +[source,shell, subs="attributes"] +---- +# To build the image on OpenShift +oc new-app registry.access.redhat.com/redhat-openjdk-18/openjdk18-openshift~{quickstarts-clone-url} --context-dir=getting-started --name=quarkus-quickstart +oc logs -f bc/quarkus-quickstart + +# To create the route +oc expose svc/quarkus-quickstart + +# Get the route URL +export URL="http://$(oc get route | grep quarkus-quickstart | awk '{print $2}')" +curl $URL/hello/greeting/quarkus +---- + +Your application is accessible at the printed URL. + +The `.s2i/environment` file in the quickstart sets required variables for the S2I Builder image to find the Quarkus `runner` JAR, and copy the JARs from the `lib/` directory. + +== Going further + +This guide covered the deployment of a Quarkus application on OpenShift using S2I. +However, there is much more, and the integration with these environments has been tailored to make Quarkus applications execution very smooth. +For instance, the health extension can be used for health check; the configuration support allows mounting the application configuration using config map, the metric extension produces data _scrappable_ by Prometheus and so on. diff --git a/docs/src/main/asciidoc/opentracing-guide.adoc b/docs/src/main/asciidoc/opentracing-guide.adoc index 0f6ae2c836a0d..ea591d45ab9cd 100644 --- a/docs/src/main/asciidoc/opentracing-guide.adoc +++ b/docs/src/main/asciidoc/opentracing-guide.adoc @@ -1,3 +1,9 @@ +//// +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 +//// + include::./attributes.adoc[] = {project-name} - Using OpenTracing @@ -73,6 +79,26 @@ public class TracedResource { Notice that there is no tracing specific code included in the application. By default, requests sent to this endpoint will be traced without any code changes being required. It is also possible to enhance the tracing information. For more information on this, please see the https://github.com/eclipse/microprofile-opentracing/blob/master/spec/src/main/asciidoc/microprofile-opentracing.asciidoc[MicroProfile OpenTracing specification]. +=== Create the configuration + +There are two ways to configure the Jaeger tracer within the application. + +The first approach is by providing the properties within the `src/main/resources/application.properties` file: + +[source,shell] +---- +quarkus.jaeger.service-name=myservice +quarkus.jaeger.sampler-type=const +quarkus.jaeger.sampler-param=1 +quarkus.jaeger.endpoint=http://localhost:14268/api/traces +---- + +The second approach is to supply the properties as https://www.jaegertracing.io/docs/latest/client-features/[environment variables]. These can be specified as `jvm.args` as shown in the following section. + +If the `quarkus.jaeger.service-name` property (or `JAEGER_SERVICE_NAME` environment variable) is not provided then a "no-op" tracer will be configured, resulting in no tracing data being reported to the backend. + +NOTE: Currently the tracer can only be configured to report spans directly to the collector via HTTP, using the `quarkus.jaeger.endpoint` property (or `JAEGER_ENDPOINT` environment variable). Support for using the Jaeger agent, via UDP, will be available in a future version. + == Run the application The first step is to start the tracing system to collect and display the captured traces: @@ -82,16 +108,19 @@ The first step is to start the tracing system to collect and display the capture docker run -e COLLECTOR_ZIPKIN_HTTP_PORT=9411 -p 5775:5775/udp -p 6831:6831/udp -p 6832:6832/udp -p 5778:5778 -p 16686:16686 -p 14268:14268 -p 9411:9411 jaegertracing/all-in-one:latest ---- -Now we are ready to run our application. Use: +Now we are ready to run our application. If using `application.properties` to configure the tracer: [source, text] ---- -mvn compile quarkus:dev -Djvm.args="-DJAEGER_SERVICE_NAME=myservice -DJAEGER_SAMPLER_TYPE=const -DJAEGER_SAMPLER_PARAM=1 -DJAEGER_ENDPOINT=http://localhost:14268/api/traces" +./mvnw compile quarkus:dev ---- -The supplied `jvm.args` are used to configure the default OpenTracing tracer (Jaeger). If the `JAEGER_SERVICE_NAME` property is not provided then a "no-op" tracer will be configured, resulting in no tracing data being reported to the backend. The other properties are explained in the https://www.jaegertracing.io/docs/latest/client-features/[Tracer configuration via environment variables] section of the Jaeger documentation. +or if configuring the tracer via environment variables: -NOTE: Currently the tracer can only be configured to report spans directly to the collector via HTTP, using the `JAEGER_ENDPOINT` property. Support for using the Jaeger agent, via UDP, will be available in a future version. +[source, text] +---- +./mvnw compile quarkus:dev -Djvm.args="-DJAEGER_SERVICE_NAME=myservice -DJAEGER_SAMPLER_TYPE=const -DJAEGER_SAMPLER_PARAM=1 -DJAEGER_ENDPOINT=http://localhost:14268/api/traces" +---- Once both the application and tracing system are started, you can make a request to the provided endpoint: diff --git a/docs/src/main/asciidoc/performance-measure.adoc b/docs/src/main/asciidoc/performance-measure.adoc index 5500ab7b87118..d612622c3cbee 100644 --- a/docs/src/main/asciidoc/performance-measure.adoc +++ b/docs/src/main/asciidoc/performance-measure.adoc @@ -1,3 +1,9 @@ +//// +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 +//// + include::./attributes.adoc[] = {project-name} - Measuring Performance @@ -5,6 +11,7 @@ This guide covers: * how we measure memory usage * how we measure startup time +* which additional flags will Quarkus apply to native-image by default All of our tests are run on the same hardware for a given batch. It goes without saying but it's better when you say it. @@ -13,7 +20,7 @@ It goes without saying but it's better when you say it. When measuring the footprint of a {project-name} application, we measure https://en.wikipedia.org/wiki/Resident_set_size[Resident Set Size (RSS)] and not the JVM heap size which is only a small part of the overall problem. -The JVM not only allocates native memory for heap (`-Xms`, `-Xmx`) but also structures required by the jvm to run your application. Depending on the JVM implementation, the total memory allocated for an application will include, be not limited to; +The JVM not only allocates native memory for heap (`-Xms`, `-Xmx`) but also structures required by the jvm to run your application. Depending on the JVM implementation, the total memory allocated for an application will include, but not limited to: * Heap space * Class metadata @@ -146,13 +153,63 @@ In a separate terminal, we start the timing application that we are testing, pri [source, shell] -- -$ date +"%H:%M:%S.%s" && ./target/quarkus-timing-runner +$ date +"%T.%3N" && ./target/quarkus-timing-runner -16:17:01.1551889021 -16:17:01.626 -2019-03-06 16:17:01,626 INFO [io.quarkus] (main) Quarkus 0.11.0 started in 0.035s. Listening on: http://127.0.0.1:8080 -2019-03-06 16:17:01,626 INFO [io.quarkus] (main) Installed features: [cdi, resteasy, resteasy-jsonb] -16:17:01.637 +10:57:32.508 +10:57:32.512 +2019-04-05 10:57:32,512 INFO [io.quarkus] (main) Quarkus 0.11.0 started in 0.002s. Listening on: http://127.0.0.1:8080 +2019-04-05 10:57:32,512 INFO [io.quarkus] (main) Installed features: [cdi, resteasy, resteasy-jsonb] +10:57:32.537 -- -The difference between the final timestamp and the first timestamp is the total startup time for the application to serve the first request. \ No newline at end of file +The difference between the final timestamp and the first timestamp is the total startup time for the application to serve the first request. + +== Additional flags applied by Quarkus + +When Quarkus invokes GraalVM `native-image` it will apply some additional flags by default. + +You might want to know about the following ones in case you're comparing performance properties with other builds. + +=== Disable fallback images + +Fallback native images are a feature of GraalVM to "fall back" to run your application in the normal JVM, should the compilation +to native code fail for some reason. + +Quarkus disables this feature by setting `-H:FallbackThreshold=0`: this will ensure you get a compilation failure rather +risking to not notice that the application is unable to really run in native mode. + +If you instead want to just run in Java mode, that's totally possible: just skip the native-image build and run it as a jar. + +=== Disable Isolates + +Isolates are a neat feature of GraalVM, but Quarkus isn't using them at this stage. + +Disable via `-H:-SpawnIsolates`. + +=== Disable auto-registration of all Service Loader implementations + +Quarkus extensions can automatically pick the right services they need, while GraalVM's native-image defaults to include +all services it's able to find on the classpath. + +We prefer listing services explicitly as it produces better optimised binaries. Disable it as well by setting `-H:-UseServiceLoaderFeature`. + +=== Better default for Garbage Collection implementation + +The default in GraalVM seems meant to optimise for short lived processes. + +Quarkus defaults to server applications, so we switch to a better default by setting + `-H:InitialCollectionPolicy=com.oracle.svm.core.genscavenge.CollectionPolicy$BySpaceAndTime`. + +=== Others ... + +This section is provided as high level guidance, but can't presume to be comprehensive as some flags are controlled + dynamically by the extensions, the platform you're building on, configuration details, your code and possibly + a combination of any of these. + +Generally speaking the ones listed here are those most likely to affect performance metrics, but in the right +circumstances one could observe non negligible impact from the other flags too. + +If you're to investigate some differences in detail make sure to check what Quarkus is invoking exactly: when the build +plugin is producing a native image, the full command lines are logged. + + diff --git a/docs/src/main/asciidoc/rest-client-guide.adoc b/docs/src/main/asciidoc/rest-client-guide.adoc index 3f52b58f0d82d..b1124b6fbdbe6 100644 --- a/docs/src/main/asciidoc/rest-client-guide.adoc +++ b/docs/src/main/asciidoc/rest-client-guide.adoc @@ -1,3 +1,9 @@ +//// +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 +//// + include::./attributes.adoc[] = {project-name} - Using the REST Client @@ -157,7 +163,8 @@ The `getByName` method gives our code the ability to query a country by name fro The purpose of the annotations in the code above is the following: -* `@RegisterRestClient` allows {project-name} to know that this interface is meant to be used as a REST Client +* `@RegisterRestClient` allows {project-name} to know that this interface is meant to be available for +CDI injection as a REST Client * `@Path`, `@GET` and `@PathParam` are the standard JAX-RS annotations used to define how to access the service * `@Produces` defines the expected content-type @@ -261,10 +268,14 @@ The code above uses link:http://rest-assured.io/[REST Assured]'s link:https://gi == Package and run the application -Run the application with: `mvn compile quarkus:dev`. +Run the application with: `./mvnw compile quarkus:dev`. Open your browser to http://localhost:8080/country/name/greece. You should see a JSON object containing some basic information about Greece. -As usual, the application can be packaged using `mvn clean package` and executed using the `-runner.jar` file. -You can also generate the native executable with `mvn clean package -Pnative`. +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`. + +== Further reading + + * link:https://download.eclipse.org/microprofile/microprofile-rest-client-1.2.1/microprofile-rest-client-1.2.1.html[MicroProfile Rest Client specification] diff --git a/docs/src/main/asciidoc/rest-json-guide.adoc b/docs/src/main/asciidoc/rest-json-guide.adoc index 0371034324ace..b3af781d0cea0 100644 --- a/docs/src/main/asciidoc/rest-json-guide.adoc +++ b/docs/src/main/asciidoc/rest-json-guide.adoc @@ -1,3 +1,9 @@ +//// +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 +//// + include::./attributes.adoc[] = Quarkus - Writing JSON REST Services @@ -175,13 +181,13 @@ In the `src/main/resources/META-INF/resources` directory, add a `fruits.html` fi You can now interact with your REST service: - * start Quarkus with `mvn compile quarkus:dev` + * start Quarkus with `./mvnw compile quarkus:dev` * open a browser to `http://localhost:8080/fruits.html` * add new fruits to the list via the form == Building a native executable -You can build a native executable with the usual command `mvn package -Pnative`. +You can build a native executable with the usual command `./mvnw package -Pnative`. Running it is as simple as executing `./target/rest-json-1.0-SNAPSHOT-runner`. @@ -336,7 +342,7 @@ Open a browser to http://localhost:8080/legumes.html and you will see our list o The interesting part starts when running the application as a native executable: - * create the native executable with `mvn package -Pnative`. + * create the native executable with `./mvnw package -Pnative`. * execute it with `./target/rest-json-1.0-SNAPSHOT-runner` * open a browser and go to http://localhost:8080/legumes.html @@ -365,7 +371,7 @@ public class Legume { Let's do that and follow the same steps as before: * hit `Ctrl+C` to stop the application - * create the native executable with `mvn package -Pnative`. + * create the native executable with `./mvnw package -Pnative`. * execute it with `./target/rest-json-1.0-SNAPSHOT-runner` * open a browser and go to http://localhost:8080/legumes.html diff --git a/docs/src/main/asciidoc/scheduled-guide.adoc b/docs/src/main/asciidoc/scheduled-guide.adoc index bbea3b9608901..89c905a3d208d 100644 --- a/docs/src/main/asciidoc/scheduled-guide.adoc +++ b/docs/src/main/asciidoc/scheduled-guide.adoc @@ -1,3 +1,9 @@ +//// +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 +//// + include::./attributes.adoc[] = {project-name} - Scheduling Periodic Tasks @@ -29,7 +35,7 @@ However, you can go right to the completed example. Clone the Git repository: `git clone {quickstarts-clone-url}`, or download an {quickstarts-archive-url}[archive]. -The solution is located in the `scheduling-periodic-task` directory. +The solution is located in the `scheduling-periodic-tasks` directory. == Creating the Maven project @@ -49,7 +55,7 @@ It generates: * the Maven structure * a landing page accessible on `http://localhost:8080` -* an example of `Dockerfile` +* example `Dockerfile` files for both `native` and `jvm` modes * the application configuration file * an `org.acme.scheduling.CountResource` resource * an associated test @@ -154,9 +160,9 @@ public class CountResourceTest { == Package and run the application -Run the application with: `mvn compile quarkus:dev`. +Run the application with: `./mvnw compile quarkus:dev`. In another terminal, run `curl localhost:8080/count` to check the counter value. After a few seconds, re-run `curl localhost:8080/count` to verify the counter has been incremented. -As usual, the application can be packaged using `mvn clean package` and executed using the `-runner.jar` file. -You can also generate the native executable with `mvn clean package -Pnative`. +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`. diff --git a/docs/src/main/asciidoc/security-guide.adoc b/docs/src/main/asciidoc/security-guide.adoc index fa8b413711a0b..dc25748a25fe9 100644 --- a/docs/src/main/asciidoc/security-guide.adoc +++ b/docs/src/main/asciidoc/security-guide.adoc @@ -1,3 +1,9 @@ +//// +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 +//// + include::./attributes.adoc[] = {project-name} - Using Security @@ -79,7 +85,7 @@ The property files realm supports mapping of users to password and users to role |=== |Property Name|Default|Description -|quarkus.security.file.enabled|false|Determine whether security via the fila realm is enabled. +|quarkus.security.file.enabled|false|Determine whether security via the file realm is enabled |quarkus.security.file.auth-mechanism|BASIC|Name of authentication mechanism to use |quarkus.security.file.realm-name|Quarkus|Name to assign the security realm |quarkus.security.file.users|users.properties|Classpath resource name of properties file containing user to password mappings; see <> @@ -194,6 +200,19 @@ quarkus.security.embedded.roles.noadmin=user <1> User `scott` has roles `Admin`, `admin`, `Tester`, and `user` <2> User `stuart` has roles `admin` and `user` +#### Registering Security Providers +When running in native mode the default behavior for Graal native image generation is to only include the main "SUN" provider +unless you have enabled SSL, in which case all security providers are registered. If you are not using SSL, then you can selectively +register security providers by name using the `quarkus.security.security-providers` property. The following example illustrates +configuration to register the "SunRsaSign" and "SunJCE" security providers: + +.Example Security Providers Configuration +[source,properties] +---- +quarkus.security-providers=SunRsaSign,SunJCE +... +---- + ## Augmenting the Elytron Security Extension __Advanced Topic__ [TIP] ==== diff --git a/docs/src/main/asciidoc/spring-di-guide.adoc b/docs/src/main/asciidoc/spring-di-guide.adoc index d74cf7aae91ca..5059fdc6ac5c4 100644 --- a/docs/src/main/asciidoc/spring-di-guide.adoc +++ b/docs/src/main/asciidoc/spring-di-guide.adoc @@ -1,3 +1,9 @@ +//// +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 +//// + include::./attributes.adoc[] = {project-name} - Using our Spring Dependency Injection compatibility layer @@ -253,7 +259,7 @@ public class GreetingResourceTest { == Package and run the application -Run the application with: `mvn compile quarkus:dev`. +Run the application with: `./mvnw compile quarkus:dev`. Open your browser to http://localhost:8080/greeting. The result should be: `HELLO WORLD!`. \ No newline at end of file diff --git a/docs/src/main/asciidoc/tooling.adoc b/docs/src/main/asciidoc/tooling.adoc index 093b17257bbad..91cf188764cd6 100644 --- a/docs/src/main/asciidoc/tooling.adoc +++ b/docs/src/main/asciidoc/tooling.adoc @@ -1,3 +1,9 @@ +//// +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 +//// + include::./attributes.adoc[] = {project-name} - Using our Tooling diff --git a/docs/src/main/asciidoc/transaction-guide.adoc b/docs/src/main/asciidoc/transaction-guide.adoc index 42cbf776b77b1..5b0a63d7ebd47 100644 --- a/docs/src/main/asciidoc/transaction-guide.adoc +++ b/docs/src/main/asciidoc/transaction-guide.adoc @@ -1,3 +1,9 @@ +//// +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 +//// + include::./attributes.adoc[] = Using Transactions in Quarkus diff --git a/docs/src/main/asciidoc/using-vertx.adoc b/docs/src/main/asciidoc/using-vertx.adoc new file mode 100644 index 0000000000000..9068259aee00c --- /dev/null +++ b/docs/src/main/asciidoc/using-vertx.adoc @@ -0,0 +1,480 @@ +include::./attributes.adoc[] += {project-name} - Using Eclipse Vert.x + +Eclipse https://vertx.io[Vert.x] is a toolkit for building reactive applications. +It is designed to be lightweight and embeddable. +Vert.x defines a reactive execution model and provides a large ecosystem. +Quarkus integrates Vert.x to implement different reactive features, such as asynchronous message passing, and non-blocking HTTP client. +Basically, {project-name} uses Vert.x as its reactive engine. +While lots of reactive features from {project-name} don't _show_ Vert.x, it's used underneath. +But you can also access the managed Vert.x instance and benefit from the Vert.x ecosystem. + +== Installing + +To access Vert.x, well, you need to enable the `vertx` extension to use this feature. +If you are creating a new project, set the `extensions` parameter are follows: + +[source, shell, subs=attributes+] +---- +mvn io.quarkus:quarkus-maven-plugin:{quarkus-version}:create \ + -DprojectGroupId=org.acme \ + -DprojectArtifactId=vertx-quickstart \ + -Dextensions="vertx" +---- + +If you have an already created project, the `vertx` extension can be added to an existing {project-name} project with +the `add-extension` command: + +[source,shell] +---- +mvn quarkus:add-extension -Dextensions="vertx" +---- + +Otherwise, you can manually add this to the dependencies section of your `pom.xml` file: + +[source,xml] +---- + + io.quarkus + quarkus-vertx + +---- + +== Accessing Vert.x + +Once the extension has been added, you can access the _managed_ Vert.x instance using `@Inject`: + +[source, java] +---- +@Inject Vertx vertx; +---- + +If you are familiar with Vert.x, you know that Vert.x provides different API models. +For instance _bare_ Vert.x uses callbacks, the RX Java 2 version uses `Single`, `Maybe`, `Completable`, `Observable` and `Flowable`. + +{project-name} provides 3 Vert.x APIs: + +[options="header"] +|=== + +| Name | Code | Description + +| _bare_ | `@Inject io.vertx.core.Vertx vertx` | _bare_ Vert.x instance, the API uses callbacks. + +| RX Java 2 | `@Inject io.vertx.reactivex.core.Vertx vertx` | RX Java 2 Vert.x, the API uses RX Java 2 types. + +| _Axle_ | `@Inject io.vertx.axle.core.Vertx vertx` | Axle Vert.x, the API uses `CompletionStage` and `Reactive Streams`. + +|=== + +TIP: You may inject any of the 3 flavors of `Vertx` as well as the `EventBus` in your {project-name} application beans: `bare`, `Axle`, `RxJava2`. +They are just shims and rely on a single _managed_ Vert.x instance. + +You will pick one or the other depending on your use cases. + +- `bare`: for advanced usage or if you have existing Vert.x code you want to reuse in your {project-name} application +- `Axle`: works well with {project-name} and MicroProfile APIs (`CompletionStage` for single results and `Publisher` for streams) +- `Rx Java 2`: when you need support for a wide range of data transformation operators on your streams + +The following snippets illustrate the difference between these 3 APIs: + +[source, java] +---- +// Bare Vert.x: +vertx.fileSystem().readFile("lorem-ipsum.txt", ar -> { + if (ar.succeeded()) { + System.out.println("Content:" + ar.result().toString("UTF-8")); + } else { + System.out.println("Cannot read the file: " + ar.cause().getMessage()); + } +}); + +// Rx Java 2 Vert.x +vertx.fileSystem().rxReadFile("lorem-ipsum.txt") + .map(buffer -> buffer.toString("UTF-8")) + .subscribe( + content -> System.out.println("Content: " + content), + err -> System.out.println("Cannot read the file: " + err.getMessage()) + ); + +// Axle API: +vertx.fileSystem().readFile("lorem-ipsum.txt") + .thenApply(buffer -> buffer.toString("UTF-8")) + .whenComplete((res, err) -> { + if (err != null) { + System.out.println("Cannot read the file: " + err.getMessage()); + } else { + System.out.println("Content: " + content); + } + }); +---- + +== Using Vert.x in Reactive JAX-RS resources + +{project-name} web resources support asynchronous processing and streaming results over https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events[server-sent events]. + +=== Asynchronous processing + +Most programming guides start easy with a greeting service and this one makes no exception. + +To asynchronously greet a client, the endpoint method must return a `java.util.concurrent.CompletionStage`: + +[source,java] +---- +@Path("/hello") +public class GreetingResource { + + @Inject + Vertx vertx; + + @GET + @Produces(MediaType.TEXT_PLAIN) + @Path("{name}") + public CompletionStage greeting(@PathParam("name") String name) { + // When complete, return the content to the client + CompletableFuture future = new CompletableFuture<>(); + + long start = System.nanoTime(); + + // TODO: asynchronous greeting + + return future; + } +} +---- + +So far so good. +Now let's use the Vert.x API to implement the artificially delayed reply with the `setTimer` provided by Vert.x: + +[source,java] +---- +// Delay reply by 10ms +vertx.setTimer(10, l -> { + // Compute elapsed time in milliseconds + long duration = MILLISECONDS.convert(System.nanoTime() - start, NANOSECONDS); + + // Format message + String message = String.format("Hello %s! (%d ms)%n", name, duration); + + // Complete + future.complete(message); +}); +---- + +That's it. +Now start {project-name} in `dev` mode with: + +[source, shell] +---- +mvn compile quarkus:dev +---- + +Eventually, open your browser and navigate to http://localhost:8080/hello/Quarkus, you should see: + +[source, shell] +---- +Hello Quarkus! (10 ms) +---- + +=== Streaming using Server-Sent Events + +{project-name} web resources that need to send content as https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events[server-sent events] must have a method: + +* declaring the `text/event-stream` response content type +* returning a https://www.reactive-streams.org/[Reactive Streams] `Publisher` or an RX Java 2 `Observable` or `Flowable` + +In practice, a streaming greeting service would look like: + +[source,java] +---- +@Path("/hello") +public class StreamingResource { + + @GET + @Produces(MediaType.SERVER_SENT_EVENTS) + @Path("{name}/streaming") + public Publisher greeting(@PathParam String name) { + // TODO: create a Reactive Streams publisher + return publisher; + } +} +---- + +How to create a Reactive Streams publisher? +There are a few ways to do this: + +1. If you use `io.vertx.axle.core.Vertx`, the API provides `toPublisher` methods (and then use RX Java 2 or Reactive Streams Operators to manipulate the stream) +2. You can also use `io.vertx.reactivex.core.Vertx` which already provides RX Java 2 (RX Java 2 `Flowable` implement Reactive Streams `publisher`). + +The first approach can be implemented as follows: + +[source, java] +---- +// Use io.vertx.axle.core.Vertx; +@Inject Vertx vertx; + +@GET +@Produces(MediaType.SERVER_SENT_EVENTS) +@Path("{name}/streaming") +public Publisher greeting(@PathParam("name") String name) { + return vertx.periodicStream(2000).toPublisherBuilder() + .map(l -> String.format("Hello %s! (%s)%n", name, new Date())) + .buildRs(); +} +---- + +The second approach slightly differs: + +[source,java] +---- +// Use io.vertx.reactivex.core.Vertx; +@Inject Vertx vertx; + +@GET +@Produces(MediaType.SERVER_SENT_EVENTS) +@Path("{name}/streaming") +public Publisher greeting(@PathParam("name") String name) { + return vertx.periodicStream(2000).toFlowable() + .map(l -> String.format("Hello %s! (%s)%n", name, new Date())); +} +---- + +The server side is ready. +In order to see the result in the browser, we need a web page. + +[source,html] +.META-INF/resources/streaming.html +---- + + + + + SSE with Vert.x - Quarkus + + + +
+ + +---- + +Our web page just has an empty `
` container. +The magic, as always, lies in the Javascript code: + +[source,javascript] +.META-INF/resources/streaming.js +---- +var eventSource = new EventSource("/hello/Quarkus/streaming"); +eventSource.onmessage = function (event) { + var container = document.getElementById("container"); + var paragraph = document.createElement("p"); + paragraph.innerHTML = event.data; + container.appendChild(paragraph); +}; +---- + +IMPORTANT: Most browsers support SSE but some don't. +More about this in Mozilla's https://developer.mozilla.org/en-US/docs/Web/API/EventSource#Browser_compatibility[SSE browser-compatibility list]. + +Navigate to http://localhost:8080/streaming.html. +A new greeting should show-up every 2 seconds. + +[source, text] +---- +Hello Quarkus! (Thu Mar 21 17:26:12 CET 2019) + +Hello Quarkus! (Thu Mar 21 17:26:14 CET 2019) + +Hello Quarkus! (Thu Mar 21 17:26:16 CET 2019) + +... +---- + +=== Using Vert.x JSON + +Vert.x API heavily relies on JSON, namely the `io.vertx.core.json.JsonObject` and `io.vertx.core.json.JsonArray` types. +They are both supported as {project-name} web resource request and response bodies. + +Consider these endpoints: + +[source,java] +---- +@Path("/hello") +@Produces(MediaType.APPLICATION_JSON) +public class VertxJsonResource { + + @GET + @Path("{name}/object") + public JsonObject jsonObject(@PathParam("name") String name) { + return new JsonObject().put("Hello", name); + } + + @GET + @Path("{name}/array") + public JsonArray jsonArray(@PathParam("name") String name) { + return new JsonArray().add("Hello").add(name); + } +} +---- + +In your browser, navigate to http://localhost:8080/hello/Quarkus/object. You should see: + +[source, text] +---- +{"Hello":"Quarkus"} +---- + +Then, navigate to http://localhost:8080/hello/Quarkus/array: + +[source, text] +---- +["Hello","Quarkus"] +---- + +Needless to say, this works equally well when the JSON content is a request body or is wrapped in a `CompletionStage` or `Publisher`. + +== Using Vert.x Clients + +As you can inject a Vert.x instance, you can use Vert.x clients in a {project-name} application. +This section gives an example with the `WebClient`. + +=== Picking the right dependency + +Depending on the API model you want to use you need to add the right dependency to your `pom.xml` file: + +[source, xml, subs=attributes+] +---- + + + io.vertx + vertx-web-client + {vertx-version} + + + + + io.smallrye.reactive + smallrye-axle-web-client + {axle-version} + + + + + io.vertx + vertx-rx-java2 + {vertx-version} + +---- + +NOTE: The `vertx-rx-java2` provides the RX Java 2 API for the whole Vert.x stack, not only the web client. + +In this guide, we are going to use the Axle API, so: + +[source, xml, subs=attributes+] +---- + + io.smallrye.reactive + smallrye-axle-web-client + {axle-version} + +---- + +Now, create a new resource in your project with the following content: + +[source, java] +.src/main/java/org/acme/vertx/ResourceUsingWebClient.java +---- +package org.acme.vertx; + +import io.vertx.axle.core.Vertx; +import io.vertx.axle.ext.web.client.WebClient; +import io.vertx.axle.ext.web.codec.BodyCodec; +import io.vertx.core.json.JsonObject; +import io.vertx.ext.web.client.WebClientOptions; + +import javax.annotation.PostConstruct; +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 java.util.concurrent.CompletionStage; + +@Path("/swapi") +public class ResourceUsingWebClient { + + + @Inject + Vertx vertx; + + private WebClient client; + + @PostConstruct + void initialize() { + this.client = WebClient.create(vertx, + new WebClientOptions().setDefaultHost("swapi.co").setDefaultPort(443).setSsl(true)); + } + + @GET + @Produces(MediaType.APPLICATION_JSON) + @Path("/{id}") + public CompletionStage getStarWarsCharacter(@PathParam("id") int id) { + return client.get("/api/people/" + id) + .send() + .thenApply(resp -> { + if (resp.statusCode() == 200) { + return resp.bodyAsJsonObject(); + } else { + return new JsonObject() + .put("code", resp.statusCode()) + .put("message", resp.bodyAsString()); + } + }); + } + +} +---- + +This resource creates a `WebClient` and upon request use this client to invoke the https://swapi.co/ API. +Depending on the result the response is forwarded as it's received, or a new JSON object is created with the status and body. +The `WebClient` is obviously asynchronous (and non-blocking), to the endpoint returns a `CompletionStage`. + +Run the application with: + +[source, shell] +---- +mvn compile quarkus:dev +---- + +And then, open a browser to: http://localhost:8080/swapi/1. You should get _Luke Skywalker_. + +The application can also run as a native executable. +But, first, we need to instruct Quarkus to enable _ssl_. +Open the `src/main/resources/application.properties` and add: + +[source] +---- +quarkus.ssl.native=true +---- + +Then, create the native executable with: + +[source, shell] +---- +mvn package -Pnative +---- + +== Going further + +There are many other facets of {project-name} using Vert.x underneath: + +* The event bus is the connecting tissue of Vert.x application. +{project-name} integrates it so different beans can interact asynchronous messages. +This part is covered in the link:async-message-passing.html[Async Message Passing documentation]. + +* Data streaming and Apache Kafka are a important parts of modern systems. +{project-name} integrates data streaming using Reactive Messaging. +More details on link:kafka-guide.html[Interacting with Kafka]. + + diff --git a/docs/src/main/asciidoc/validation-guide.adoc b/docs/src/main/asciidoc/validation-guide.adoc index bc1a10c7ea92d..88ad71fefa729 100644 --- a/docs/src/main/asciidoc/validation-guide.adoc +++ b/docs/src/main/asciidoc/validation-guide.adoc @@ -1,3 +1,9 @@ +//// +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 +//// + include::./attributes.adoc[] = {project-name} - Validation with Hibernate Validator @@ -260,7 +266,7 @@ In the `src/main/resources/META-INF/resources` directory, replace the `index.htm Now, let's see our application in action. Run it with: ``` -mvn compile quarkus:dev +./mvnw compile quarkus:dev ``` Then, open your browser to http://localhost:8080/: @@ -270,5 +276,5 @@ Then, open your browser to http://localhost:8080/: image:validation-guide-screenshot.png[alt=Application] -As usual, the application can be packaged using `mvn clean package` and executed using the `-runner.jar` file. -You can also build the native executable using `mvn package -Pnative`. +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`. diff --git a/docs/src/main/asciidoc/websocket-guide.adoc b/docs/src/main/asciidoc/websocket-guide.adoc index fa366cc3dbb0a..5cf0ce1a06774 100644 --- a/docs/src/main/asciidoc/websocket-guide.adoc +++ b/docs/src/main/asciidoc/websocket-guide.adoc @@ -1,3 +1,9 @@ +//// +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 +//// + include::./attributes.adoc[] = {project-name} - Using WebSockets @@ -121,7 +127,7 @@ Create the `src/main/resources/META-INF/resources` directory and copy this {quic Now, let's see our application in action. Run it with: ``` -mvn compile quarkus:dev +./mvnw compile quarkus:dev ``` Then open your 2 browser windows to http://localhost:8080/: @@ -132,7 +138,7 @@ Then open your 2 browser windows to http://localhost:8080/: image:websocket-guide-screenshot.png[alt=Application] -As usual, the application can be packaged using `mvn clean package` and executed using the `-runner.jar` file. -You can also build the native executable using `mvn package -Pnative`. +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`. You can also test your web socket applications using the approach detailed {quickstarts-blob-url}/using-websockets/src/test/java/org/acme/websocket/ChatTestCase.java[here]. diff --git a/docs/src/main/asciidoc/writing-native-applications-tips.adoc b/docs/src/main/asciidoc/writing-native-applications-tips.adoc new file mode 100644 index 0000000000000..3a9445ad36c12 --- /dev/null +++ b/docs/src/main/asciidoc/writing-native-applications-tips.adoc @@ -0,0 +1,190 @@ +//// +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 +//// + +include::./attributes.adoc[] += {project-name} - Tips for writing native applications + +This guide contains various tips and tricks for getting around problems that might arise when attempting to run java applications as native executables. + +== Register reflection + +Due to the fact that when building a native executable GraalVM operates with a closed world assumption (which enables it to remove all unused code, which in turns yields multiple benefits), reflection targets need to be explicitly declared. + +An example of failing to explicitly specify reflection is the following: + +[source] +---- +Caused by: org.xml.sax.SAXException: SAX2 driver class com.sun.org.apache.xerces.internal.parsers.SAXParser not found +java.lang.ClassNotFoundException: com.sun.org.apache.xerces.internal.parsers.SAXParser +at org.xml.sax.helpers.XMLReaderFactory.loadClass(XMLReaderFactory.java:230) +---- + +If one were to execute the native executable generation process manually, the `-H:ReflectionConfigurationFiles=` flag would be used to point to a JSON configuration file that specifies the program elements that would be accessed reflectively. + +Quarkus however makes registration of reflection a breeze by using the `ReflectiveClassBuildItem`, thus eliminating the need for such a JSON configuration file. + +To solve the problem shown in the example above one would need to create a Quarkus processor class and add a build step that registers reflection. +A simple example could like the following: + +[source,java] +---- +public class SaxParserProcessor { + + @BuildStep + ReflectiveClassBuildItem reflection() { + // since we only need reflection to the constructor of the class, we can specify `false` for both the methods and the fields arguments. + return new ReflectiveClassBuildItem(false, false, "com.sun.org.apache.xerces.internal.parsers.SAXParser"); + } + +} +---- + +More information about reflection in GraalVM can be found https://github.com/oracle/graal/blob/master/substratevm/REFLECTION.md[here]. + +=== Alternative with @RegisterForReflection + +In application code it is often needed to pass classes to libraries that use reflection to figure out the methods/fields of those classes. +One such example is using http://json-b.net/[JSON-B] like so: + +[source,java] +---- + public class Person { + private String first; + private String last; + + public String getFirst() { + return first; + } + + public void setFirst(String first) { + this.first = first; + } + + public String getLast() { + return last; + } + + public void setValue(String last) { + this.last = last; + } + } + + @Path("/person") + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + public class PersonResource { + + private final Jsonb jsonb; + + public PersonResource() { + jsonb = JsonbBuilder.create(new JsonbConfig()); + } + + @GET + public Response list() { + return Response.ok(jsonb.fromJson("{\"first\": \"foo\", \"last\": \"bar\"}", Person.class)).build(); + } + } +---- + +If we were to use the code above, we would get an exception like the following when using the native executable: + +[source] +---- + Exception handling request to /person: org.jboss.resteasy.spi.UnhandledException: javax.json.bind.JsonbException: Can't create instance of a class: class org.acme.jsonb.Person, No default constructor found +---- + +An even nastier possible outcome could be for no exception to be thrown, but instead the JSON result would be completely empty. + +To get around such problems, simply annotate `Person` with `@RegisterForReflection` so Quarkus will register the class as using reflection. + +== Including resources + +By default when building a native executable, GraalVM will not include any of the resources which are on the classpath into the native executable it creates. Such resources that are meant to be part of the native executable need to be specified explicitly. + +If one were to execute the native executable generation process manually, the `-H:IncludeResources=` flag would be used to point to a JSON configuration file that specifies which resources are to be included. + +Quarkus eliminates the need for such a JSON configuration file by allowing extension authors to specify a `SubstrateResourceBuildItem`. + +An example use could look like the following: + +[source,java] +---- +public class ResourcesProcessor { + + @BuildStep + SubstrateResourceBuildItem substrateResourceBuildItem() { + return new SubstrateResourceBuildItem("META-INF/extra.properties"); + } + +} +---- + +For more information about GraalVM's resource handling in native executables please see https://github.com/oracle/graal/blob/master/substratevm/RESOURCES.md[this]. + + +== Delay class initialization + +There are cases where the initialization of certain classes is done in a static block (which means by default that GraalVM performs the initialization at image build time) needs to be postponed to runtime. +Typically omitting such configuration would result in a runtime exception like the following: + +[source] +---- +Error: No instances are allowed in the image heap for a class that is initialized or reinitialized at image runtime: sun.security.provider.NativePRNG +Trace: object java.security.SecureRandom +method com.amazonaws.services.s3.model.CryptoConfiguration.(CryptoMode) +Call path from entry point to com.amazonaws.services.s3.model.CryptoConfiguration.(CryptoMode): +---- + +If one were to execute the native executable generation process manually, the `--delay-class-initialization-to-runtime=com.amazonaws.services.s3.model.CryptoConfiguration` flag would need to be passed to `native-image` to configure runtime initialization for `CryptoConfiguration`. + +Quarkus simplifies things by allowing extensions authors to simply register a `RuntimeInitializedClassBuildItem`. A simple example of doing so could be: + +[source,java] +---- +public class S3Processor { + + @BuildStep + RuntimeInitializedClassBuildItem cryptoConfiguration() { + return new RuntimeInitializedClassBuildItem(CryptoConfiguration.class.getCanonicalName()); + } + +} +---- + +Using such a construct means that a `--delay-class-initialization-to-runtime` option will automatically be added to the `native-image` command line + +For more information about `--delay-class-initialization-to-runtime`, please read https://medium.com/graalvm/understanding-class-initialization-in-graalvm-native-image-generation-d765b7e4d6ed[this blog post]. + +== Proxy Classes management + +While writing native application you'll need to define proxy classes at image build time by specifying the list of interfaces that they implement. + +In such a situation the error you might encounter is: + +[source] +---- +com.oracle.svm.core.jdk.UnsupportedFeatureError: Proxy class defined by interfaces [interface org.apache.http.conn.HttpClientConnectionManager, interface org.apache.http.pool.ConnPoolControl, interface com.amazonaws.http.conn.Wrapped] not found. Generating proxy classes at runtime is not supported. Proxy classes need to be defined at image build time by specifying the list of interfaces that they implement. To define proxy classes use -H:DynamicProxyConfigurationFiles= and -H:DynamicProxyConfigurationResources= options. +---- + +Quarkus allows extensions authors to register a `SubstrateProxyDefinitionBuildItem`. An example of doing so is: + +[source,java] +---- +public class S3Processor { + + @BuildStep + SubstrateProxyDefinitionBuildItem httpProxies() { + return new SubstrateProxyDefinitionBuildItem("org.apache.http.conn.HttpClientConnectionManager", + "org.apache.http.pool.ConnPoolControl", "com.amazonaws.http.conn.Wrapped"); + } + +} +---- + +Using such a construct means that a `-H:DynamicProxyConfigurationResources=` option will automatically be added to the `native-image` command line. + +For more information about Proxy Classes you can read the following https://github.com/oracle/graal/blob/master/substratevm/DYNAMIC_PROXY.md[documentation] diff --git a/extensions/agroal/deployment/pom.xml b/extensions/agroal/deployment/pom.xml index fc7421a928a49..26ace6e486951 100644 --- a/extensions/agroal/deployment/pom.xml +++ b/extensions/agroal/deployment/pom.xml @@ -26,25 +26,25 @@ 4.0.0 - quarkus-agroal + quarkus-agroal-deployment Quarkus - Agroal - Deployment io.quarkus - quarkus-core + quarkus-core-deployment io.quarkus - quarkus-arc + quarkus-arc-deployment io.quarkus - quarkus-agroal-runtime + quarkus-agroal io.quarkus - quarkus-narayana-jta + quarkus-narayana-jta-deployment 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 e25ce61633194..4b91fedeb813a 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 @@ -27,7 +27,6 @@ import javax.enterprise.context.ApplicationScoped; import javax.enterprise.inject.Default; import javax.enterprise.inject.Produces; -import javax.inject.Singleton; import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationValue; @@ -76,11 +75,6 @@ class AgroalProcessor { */ AgroalBuildTimeConfig agroalBuildTimeConfig; - /** - * The Agroal runtime configuration. - */ - AgroalRuntimeConfig agroalRuntimeConfig; - @SuppressWarnings("unchecked") @Record(STATIC_INIT) @BuildStep @@ -93,8 +87,7 @@ BeanContainerListenerBuildItem build( BuildProducer dataSourceDriver, SslNativeConfigBuildItem sslNativeConfig, BuildProducer sslNativeSupport, BuildProducer generatedBean) throws Exception { - // TODO @dmlloyd - // Funilly enough, here the config in the map seems to be properly injected... + feature.produce(new FeatureBuildItem(FeatureBuildItem.AGROAL)); if (!agroalBuildTimeConfig.defaultDataSource.driver.isPresent() && agroalBuildTimeConfig.namedDataSources.isEmpty()) { @@ -152,17 +145,13 @@ BeanContainerListenerBuildItem build( @Record(ExecutionTime.RUNTIME_INIT) @BuildStep void configureRuntimeProperties(AgroalTemplate template, - BuildProducer dataSourceInitialized) { + BuildProducer dataSourceInitialized, + AgroalRuntimeConfig agroalRuntimeConfig) { if (!agroalBuildTimeConfig.defaultDataSource.driver.isPresent() && agroalBuildTimeConfig.namedDataSources.isEmpty()) { // No datasource has been configured so bail out return; } - // TODO @dmlloyd - // Here we have the first issue: - // - things are working well for the default database - // - we have the datasource1 and datasource2 elements in the map but the values are not injected - // - as mentioned above, it doesn't seem to be an issue for the build time config I use in the above method... template.configureRuntimeProperties(agroalRuntimeConfig); dataSourceInitialized.produce(new DataSourceInitializedBuildItem()); @@ -205,7 +194,7 @@ public void write(String name, byte[] data) { if (agroalBuildTimeConfig.defaultDataSource.driver.isPresent()) { MethodCreator defaultDataSourceMethodCreator = classCreator.getMethodCreator("createDefaultDataSource", AgroalDataSource.class); - defaultDataSourceMethodCreator.addAnnotation(Singleton.class); + defaultDataSourceMethodCreator.addAnnotation(ApplicationScoped.class); defaultDataSourceMethodCreator.addAnnotation(Produces.class); defaultDataSourceMethodCreator.addAnnotation(Default.class); diff --git a/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/DataSourceDriverBuildItem.java b/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/DataSourceDriverBuildItem.java index 7a57a6e929486..07d9a17404f07 100644 --- a/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/DataSourceDriverBuildItem.java +++ b/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/DataSourceDriverBuildItem.java @@ -1,6 +1,6 @@ package io.quarkus.agroal.deployment; -import org.jboss.builder.item.SimpleBuildItem; +import io.quarkus.builder.item.SimpleBuildItem; /** */ diff --git a/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/DataSourceInitializedBuildItem.java b/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/DataSourceInitializedBuildItem.java index d6c0a9cf4f04d..0a5329d3f437c 100644 --- a/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/DataSourceInitializedBuildItem.java +++ b/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/DataSourceInitializedBuildItem.java @@ -1,6 +1,6 @@ package io.quarkus.agroal.deployment; -import org.jboss.builder.item.SimpleBuildItem; +import io.quarkus.builder.item.SimpleBuildItem; /** * Marker build item indicating the datasource has been fully initialized. diff --git a/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/DefaultDataSourceConfigTest.java b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/DefaultDataSourceConfigTest.java index ed4ae2c09f823..7aa750c167ab6 100644 --- a/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/DefaultDataSourceConfigTest.java +++ b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/DefaultDataSourceConfigTest.java @@ -14,13 +14,16 @@ 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.quarkus.test.QuarkusUnitTest; public class DefaultDataSourceConfigTest { + //tag::injection[] @Inject AgroalDataSource defaultDataSource; + //end::injection[] @RegisterExtension static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( @@ -38,9 +41,11 @@ private static void testDataSource(AgroalDataSource dataSource, String username, int initialSize, Duration backgroundValidationInterval, Duration acquisitionTimeout, Duration leakDetectionInterval, Duration idleRemovalInterval) throws SQLException { AgroalConnectionPoolConfiguration configuration = dataSource.getConfiguration().connectionPoolConfiguration(); + AgroalConnectionFactoryConfiguration agroalConnectionFactoryConfiguration = configuration + .connectionFactoryConfiguration(); - assertEquals("jdbc:h2:tcp://localhost/mem:default", configuration.connectionFactoryConfiguration().jdbcUrl()); - assertEquals(username, configuration.connectionFactoryConfiguration().principal().getName()); + assertEquals("jdbc:h2:tcp://localhost/mem:default", agroalConnectionFactoryConfiguration.jdbcUrl()); + assertEquals(username, agroalConnectionFactoryConfiguration.principal().getName()); assertEquals(minSize, configuration.minSize()); assertEquals(maxSize, configuration.maxSize()); assertEquals(initialSize, configuration.initialSize()); @@ -48,6 +53,8 @@ private static void testDataSource(AgroalDataSource dataSource, String username, assertEquals(acquisitionTimeout, configuration.acquisitionTimeout()); assertEquals(leakDetectionInterval, configuration.leakTimeout()); assertEquals(idleRemovalInterval, configuration.reapTimeout()); + assertEquals(AgroalConnectionFactoryConfiguration.TransactionIsolation.SERIALIZABLE, + agroalConnectionFactoryConfiguration.jdbcTransactionIsolation()); try (Connection connection = dataSource.getConnection()) { } diff --git a/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/MultipleDataSourcesConfigTest.java b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/MultipleDataSourcesConfigTest.java index 15f503f3ba688..b811e6ad0bb4c 100644 --- a/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/MultipleDataSourcesConfigTest.java +++ b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/MultipleDataSourcesConfigTest.java @@ -10,7 +10,6 @@ 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; @@ -19,19 +18,20 @@ import io.quarkus.agroal.DataSource; import io.quarkus.test.QuarkusUnitTest; -@Disabled public class MultipleDataSourcesConfigTest { + //tag::injection[] @Inject AgroalDataSource defaultDataSource; @Inject - @DataSource("datasource1") + @DataSource("users") AgroalDataSource dataSource1; @Inject - @DataSource("datasource2") + @DataSource("inventory") AgroalDataSource dataSource2; + //end::injection[] @RegisterExtension static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( @@ -42,8 +42,8 @@ public class MultipleDataSourcesConfigTest { @Test public void testDataSourceInjection() throws SQLException { testDataSource("default", defaultDataSource, "jdbc:h2:tcp://localhost/mem:default", "username-default", 3, 13); - testDataSource("datasource1", dataSource1, "jdbc:h2:tcp://localhost/mem:datasource1", "username1", 1, 11); - testDataSource("datasource2", dataSource2, "jdbc:h2:tcp://localhost/mem:datasource2", "username2", 2, 12); + testDataSource("users", dataSource1, "jdbc:h2:tcp://localhost/mem:users", "username1", 1, 11); + testDataSource("inventory", dataSource2, "jdbc:h2:tcp://localhost/mem:inventory", "username2", 2, 12); } private static void testDataSource(String dataSourceName, AgroalDataSource dataSource, String jdbcUrl, String username, diff --git a/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/NamedDataSourceConfigTest.java b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/NamedDataSourceConfigTest.java new file mode 100644 index 0000000000000..2e02e9598c8bd --- /dev/null +++ b/extensions/agroal/deployment/src/test/java/io/quarkus/agroal/test/NamedDataSourceConfigTest.java @@ -0,0 +1,57 @@ +package io.quarkus.agroal.test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import java.sql.Connection; +import java.sql.SQLException; + +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.agroal.api.AgroalDataSource; +import io.agroal.api.configuration.AgroalConnectionPoolConfiguration; +import io.quarkus.agroal.DataSource; +import io.quarkus.test.QuarkusUnitTest; + +public class NamedDataSourceConfigTest { + @Inject + @DataSource("testing") + AgroalDataSource ds; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class) + .addAsResource("application-named-datasource.properties", + "application.properties")); + + @Test + public void testNamedDataSourceInjection() throws SQLException { + dataSourceAssert("testing", ds, "jdbc:h2:tcp://localhost/mem:testing", + "username-named", 3, 13); + } + + private static void dataSourceAssert(String dataSourceName, AgroalDataSource dataSource, String jdbcUrl, String username, + int minSize, int maxSize) + throws SQLException { + AgroalConnectionPoolConfiguration configuration = null; + + try { + configuration = dataSource.getConfiguration().connectionPoolConfiguration(); + } catch (NullPointerException e) { + // we catch the NPE here as we have a proxy and we can't test dataSource directly + fail("Datasource " + dataSourceName + " should not be null"); + } + assertEquals(jdbcUrl, configuration.connectionFactoryConfiguration().jdbcUrl()); + assertEquals(username, configuration.connectionFactoryConfiguration().principal().getName()); + assertEquals(minSize, configuration.minSize()); + assertEquals(maxSize, configuration.maxSize()); + + try (Connection connection = dataSource.getConnection()) { + } + } +} diff --git a/extensions/agroal/deployment/src/test/resources/application-default-datasource.properties b/extensions/agroal/deployment/src/test/resources/application-default-datasource.properties index e15dd37651bf6..e1622386f5386 100644 --- a/extensions/agroal/deployment/src/test/resources/application-default-datasource.properties +++ b/extensions/agroal/deployment/src/test/resources/application-default-datasource.properties @@ -1,10 +1,13 @@ +#tag::basic[] 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 +#end::basic[] quarkus.datasource.initial-size=7 quarkus.datasource.background-validation-interval=PT53S quarkus.datasource.acquisition-timeout=PT54S quarkus.datasource.leak-detection-interval=PT55S quarkus.datasource.idle-removal-interval=PT56S +quarkus.datasource.transaction-isolation-level=serializable diff --git a/extensions/agroal/deployment/src/test/resources/application-multiple-datasources.properties b/extensions/agroal/deployment/src/test/resources/application-multiple-datasources.properties index aa6f4843cbbda..98fc934def1e7 100644 --- a/extensions/agroal/deployment/src/test/resources/application-multiple-datasources.properties +++ b/extensions/agroal/deployment/src/test/resources/application-multiple-datasources.properties @@ -4,14 +4,14 @@ quarkus.datasource.username=username-default quarkus.datasource.min-size=3 quarkus.datasource.max-size=13 -quarkus.datasource.datasource1.driver=org.h2.Driver -quarkus.datasource.datasource1.url=jdbc:h2:tcp://localhost/mem:datasource1 -quarkus.datasource.datasource1.username=username1 -quarkus.datasource.datasource1.min-size=1 -quarkus.datasource.datasource1.max-size=11 +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.datasource2.driver=org.h2.Driver -quarkus.datasource.datasource2.url=jdbc:h2:tcp://localhost/mem:datasource2 -quarkus.datasource.datasource2.username=username2 -quarkus.datasource.datasource2.min-size=2 -quarkus.datasource.datasource2.max-size=12 +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 diff --git a/extensions/agroal/deployment/src/test/resources/application-named-datasource.properties b/extensions/agroal/deployment/src/test/resources/application-named-datasource.properties new file mode 100644 index 0000000000000..73915158383d7 --- /dev/null +++ b/extensions/agroal/deployment/src/test/resources/application-named-datasource.properties @@ -0,0 +1,5 @@ +quarkus.datasource.testing.url=jdbc:h2:tcp://localhost/mem:testing +quarkus.datasource.testing.driver=org.h2.Driver +quarkus.datasource.testing.username=username-named +quarkus.datasource.testing.min-size=3 +quarkus.datasource.testing.max-size=13 diff --git a/extensions/agroal/runtime/pom.xml b/extensions/agroal/runtime/pom.xml index d0145f903f5b7..5f0c8336e1a64 100644 --- a/extensions/agroal/runtime/pom.xml +++ b/extensions/agroal/runtime/pom.xml @@ -26,17 +26,17 @@ 4.0.0 - quarkus-agroal-runtime + quarkus-agroal Quarkus - Agroal - Runtime io.quarkus - quarkus-core-runtime + quarkus-core io.quarkus - quarkus-arc-runtime + quarkus-arc com.oracle.substratevm @@ -75,7 +75,8 @@ - maven-dependency-plugin + io.quarkus + quarkus-bootstrap-maven-plugin maven-compiler-plugin 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 62af2a6b153b0..e2594deedd11e 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 @@ -31,6 +31,7 @@ import org.jboss.logging.Logger; import io.agroal.api.AgroalDataSource; +import io.agroal.api.configuration.supplier.AgroalConnectionFactoryConfigurationSupplier; import io.agroal.api.configuration.supplier.AgroalConnectionPoolConfigurationSupplier; import io.agroal.api.configuration.supplier.AgroalDataSourceConfigurationSupplier; import io.agroal.api.security.NamePrincipal; @@ -107,8 +108,16 @@ public AgroalDataSource createDataSource(String dataSourceName, AgroalDataSourceConfigurationSupplier dataSourceConfiguration = new AgroalDataSourceConfigurationSupplier(); AgroalConnectionPoolConfigurationSupplier poolConfiguration = dataSourceConfiguration.connectionPoolConfiguration(); - poolConfiguration.connectionFactoryConfiguration().jdbcUrl(url); - poolConfiguration.connectionFactoryConfiguration().connectionProviderClass(driver); + AgroalConnectionFactoryConfigurationSupplier agroalConnectionFactoryConfigurationSupplier = poolConfiguration + .connectionFactoryConfiguration(); + agroalConnectionFactoryConfigurationSupplier.jdbcUrl(url); + agroalConnectionFactoryConfigurationSupplier.connectionProviderClass(driver); + + if (dataSourceRuntimeConfig.transactionIsolationLevel.isPresent()) { + agroalConnectionFactoryConfigurationSupplier + .jdbcTransactionIsolation( + dataSourceRuntimeConfig.transactionIsolationLevel.get().jdbcTransactionIsolationLevel); + } TransactionIntegration txIntegration = new NarayanaTransactionIntegration(transactionManager, transactionSynchronizationRegistry); @@ -116,11 +125,11 @@ public AgroalDataSource createDataSource(String dataSourceName, // Authentication if (dataSourceRuntimeConfig.username.isPresent()) { - poolConfiguration.connectionFactoryConfiguration() + agroalConnectionFactoryConfigurationSupplier .principal(new NamePrincipal(dataSourceRuntimeConfig.username.get())); } if (dataSourceRuntimeConfig.password.isPresent()) { - poolConfiguration.connectionFactoryConfiguration() + agroalConnectionFactoryConfigurationSupplier .credential(new SimplePassword(dataSourceRuntimeConfig.password.get())); } @@ -149,10 +158,10 @@ public AgroalDataSource createDataSource(String dataSourceName, if (disableSslSupport) { switch (driverName) { case "org.postgresql.Driver": - poolConfiguration.connectionFactoryConfiguration().jdbcProperty("sslmode", "disable"); + agroalConnectionFactoryConfigurationSupplier.jdbcProperty("sslmode", "disable"); break; case "org.mariadb.jdbc.Driver": - poolConfiguration.connectionFactoryConfiguration().jdbcProperty("useSSL", "false"); + agroalConnectionFactoryConfigurationSupplier.jdbcProperty("useSSL", "false"); break; default: log.warn("Agroal does not support disabling SSL for driver " + 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 9b685f54ca1c3..26de53544aab7 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 @@ -85,4 +85,10 @@ public class DataSourceRuntimeConfig { @ConfigItem(defaultValue = "PT5M") public Optional idleRemovalInterval; + /** + * The transaction isolation level. + */ + @ConfigItem + public Optional transactionIsolationLevel; + } diff --git a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/TransactionIsolationLevel.java b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/TransactionIsolationLevel.java new file mode 100644 index 0000000000000..b38ef55084e8c --- /dev/null +++ b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/TransactionIsolationLevel.java @@ -0,0 +1,51 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Licensed 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.agroal.runtime; + +import io.agroal.api.configuration.AgroalConnectionFactoryConfiguration.TransactionIsolation; + +public enum TransactionIsolationLevel { + UNDEFINED(TransactionIsolation.UNDEFINED), + NONE(TransactionIsolation.NONE), + READ_UNCOMMITTED(TransactionIsolation.READ_UNCOMMITTED), + READ_COMMITTED(TransactionIsolation.READ_COMMITTED), + REPEATABLE_READ(TransactionIsolation.REPEATABLE_READ), + SERIALIZABLE(TransactionIsolation.SERIALIZABLE); + + TransactionIsolation jdbcTransactionIsolationLevel; + + TransactionIsolationLevel(TransactionIsolation jdbcTransactionIsolationLevel) { + this.jdbcTransactionIsolationLevel = jdbcTransactionIsolationLevel; + } + + public static TransactionIsolationLevel of(String value) { + switch (value) { + case "none": + return NONE; + case "read-committed": + return READ_COMMITTED; + case "read-uncommitted": + return READ_UNCOMMITTED; + case "repeatable-read": + return REPEATABLE_READ; + case "serializable": + return SERIALIZABLE; + default: + return UNDEFINED; + } + } +} \ No newline at end of file diff --git a/extensions/amazon-lambda-resteasy/deployment/pom.xml b/extensions/amazon-lambda-resteasy/deployment/pom.xml new file mode 100644 index 0000000000000..fa48c28c0fc20 --- /dev/null +++ b/extensions/amazon-lambda-resteasy/deployment/pom.xml @@ -0,0 +1,73 @@ + + + + + + quarkus-amazon-lambda-resteasy-parent + io.quarkus + 999-SNAPSHOT + ../ + + 4.0.0 + + quarkus-amazon-lambda-resteasy-deployment + Quarkus - Amazon Lambda - RESTEasy - Deployment + + + + io.quarkus + quarkus-core-deployment + + + io.quarkus + quarkus-resteasy-server-common-deployment + + + io.quarkus + quarkus-amazon-lambda-resteasy + + + com.oracle.substratevm + svm + + + io.quarkus + quarkus-junit5-internal + test + + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + + diff --git a/extensions/amazon-lambda-resteasy/deployment/src/main/java/io/quarkus/amazon/lambda/resteasy/deployment/AmazonLambdaResteasyProcessor.java b/extensions/amazon-lambda-resteasy/deployment/src/main/java/io/quarkus/amazon/lambda/resteasy/deployment/AmazonLambdaResteasyProcessor.java new file mode 100755 index 0000000000000..36835bc363070 --- /dev/null +++ b/extensions/amazon-lambda-resteasy/deployment/src/main/java/io/quarkus/amazon/lambda/resteasy/deployment/AmazonLambdaResteasyProcessor.java @@ -0,0 +1,40 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Licensed 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.amazon.lambda.resteasy.deployment; + +import java.util.Optional; + +import io.quarkus.amazon.lambda.resteasy.runtime.AmazonLambdaResteasyConfig; +import io.quarkus.amazon.lambda.resteasy.runtime.AmazonLambdaResteasyTemplate; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.ExecutionTime; +import io.quarkus.deployment.annotations.Record; +import io.quarkus.resteasy.server.common.deployment.ResteasyInjectionReadyBuildItem; +import io.quarkus.resteasy.server.common.deployment.ResteasyServerConfigBuildItem; + +public class AmazonLambdaResteasyProcessor { + + AmazonLambdaResteasyConfig config; + + @BuildStep + @Record(ExecutionTime.STATIC_INIT) + void setup(AmazonLambdaResteasyTemplate template, Optional resteasyServerConfig, + ResteasyInjectionReadyBuildItem resteasyInjectionReady) { + if (resteasyServerConfig.isPresent()) { + template.initHandler(resteasyServerConfig.get().getInitParameters(), config); + } + } +} diff --git a/extensions/amazon-lambda-resteasy/deployment/src/test/java/io/quarkus/resteasy/test/ConstructorInjectionResource.java b/extensions/amazon-lambda-resteasy/deployment/src/test/java/io/quarkus/resteasy/test/ConstructorInjectionResource.java new file mode 100644 index 0000000000000..1b78b0b158a0f --- /dev/null +++ b/extensions/amazon-lambda-resteasy/deployment/src/test/java/io/quarkus/resteasy/test/ConstructorInjectionResource.java @@ -0,0 +1,19 @@ +package io.quarkus.resteasy.test; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; + +@Path("/ctor") +public class ConstructorInjectionResource { + + final Service service; + + public ConstructorInjectionResource(Service service) { + this.service = service; + } + + @GET + public String val() { + return service.execute(); + } +} diff --git a/extensions/amazon-lambda-resteasy/deployment/src/test/java/io/quarkus/resteasy/test/ConstructorInjectionResourceTestCase.java b/extensions/amazon-lambda-resteasy/deployment/src/test/java/io/quarkus/resteasy/test/ConstructorInjectionResourceTestCase.java new file mode 100644 index 0000000000000..9f7f862c86d80 --- /dev/null +++ b/extensions/amazon-lambda-resteasy/deployment/src/test/java/io/quarkus/resteasy/test/ConstructorInjectionResourceTestCase.java @@ -0,0 +1,21 @@ +package io.quarkus.resteasy.test; + +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 ConstructorInjectionResourceTestCase { + + @RegisterExtension + static QuarkusUnitTest runner = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(ConstructorInjectionResource.class, Service.class)); + + @Test + public void testConstructorInjectionResource() { + // we just test that it was properly started + } +} diff --git a/extensions/amazon-lambda-resteasy/deployment/src/test/java/io/quarkus/resteasy/test/NoResourceTestCase.java b/extensions/amazon-lambda-resteasy/deployment/src/test/java/io/quarkus/resteasy/test/NoResourceTestCase.java new file mode 100644 index 0000000000000..db51cfb7874ff --- /dev/null +++ b/extensions/amazon-lambda-resteasy/deployment/src/test/java/io/quarkus/resteasy/test/NoResourceTestCase.java @@ -0,0 +1,20 @@ +package io.quarkus.resteasy.test; + +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 NoResourceTestCase { + + @RegisterExtension + static QuarkusUnitTest runner = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)); + + @Test + public void testNoResource() { + // we just test that it was properly started + } +} diff --git a/extensions/amazon-lambda-resteasy/deployment/src/test/java/io/quarkus/resteasy/test/RootResource.java b/extensions/amazon-lambda-resteasy/deployment/src/test/java/io/quarkus/resteasy/test/RootResource.java new file mode 100644 index 0000000000000..f1375d319dc14 --- /dev/null +++ b/extensions/amazon-lambda-resteasy/deployment/src/test/java/io/quarkus/resteasy/test/RootResource.java @@ -0,0 +1,13 @@ +package io.quarkus.resteasy.test; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; + +@Path("/") +public class RootResource { + + @GET + public String root() { + return "Root Resource"; + } +} diff --git a/extensions/amazon-lambda-resteasy/deployment/src/test/java/io/quarkus/resteasy/test/RootResourceTestCase.java b/extensions/amazon-lambda-resteasy/deployment/src/test/java/io/quarkus/resteasy/test/RootResourceTestCase.java new file mode 100644 index 0000000000000..eaf8f30f754ae --- /dev/null +++ b/extensions/amazon-lambda-resteasy/deployment/src/test/java/io/quarkus/resteasy/test/RootResourceTestCase.java @@ -0,0 +1,21 @@ +package io.quarkus.resteasy.test; + +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 RootResourceTestCase { + + @RegisterExtension + static QuarkusUnitTest runner = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(RootResource.class)); + + @Test + public void testRootResource() { + // we just test that it was properly started + } +} diff --git a/extensions/amazon-lambda-resteasy/deployment/src/test/java/io/quarkus/resteasy/test/Service.java b/extensions/amazon-lambda-resteasy/deployment/src/test/java/io/quarkus/resteasy/test/Service.java new file mode 100644 index 0000000000000..b2ed70d0d1417 --- /dev/null +++ b/extensions/amazon-lambda-resteasy/deployment/src/test/java/io/quarkus/resteasy/test/Service.java @@ -0,0 +1,11 @@ +package io.quarkus.resteasy.test; + +import javax.inject.Singleton; + +@Singleton +public class Service { + + String execute() { + return "service"; + } +} diff --git a/extensions/amazon-lambda-resteasy/pom.xml b/extensions/amazon-lambda-resteasy/pom.xml new file mode 100644 index 0000000000000..f64f04b3ae330 --- /dev/null +++ b/extensions/amazon-lambda-resteasy/pom.xml @@ -0,0 +1,37 @@ + + + + + + quarkus-build-parent + io.quarkus + 999-SNAPSHOT + ../../build-parent/pom.xml + + 4.0.0 + + quarkus-amazon-lambda-resteasy-parent + Quarkus - Amazon Lambda - RESTEasy + pom + + deployment + runtime + + + diff --git a/extensions/amazon-lambda-resteasy/runtime/NOTICE b/extensions/amazon-lambda-resteasy/runtime/NOTICE new file mode 100644 index 0000000000000..bf0ce0bd43ea7 --- /dev/null +++ b/extensions/amazon-lambda-resteasy/runtime/NOTICE @@ -0,0 +1,4 @@ +Tests for the AWS Serverless Java Container feature come from the AWS Serverless Java Container project. + +AWS Serverless Java Container +Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. \ No newline at end of file diff --git a/extensions/amazon-lambda-resteasy/runtime/pom.xml b/extensions/amazon-lambda-resteasy/runtime/pom.xml new file mode 100644 index 0000000000000..e7cee59ed6735 --- /dev/null +++ b/extensions/amazon-lambda-resteasy/runtime/pom.xml @@ -0,0 +1,88 @@ + + + + + + quarkus-amazon-lambda-resteasy-parent + io.quarkus + 999-SNAPSHOT + ../ + + 4.0.0 + + quarkus-amazon-lambda-resteasy + Quarkus - Amazon Lambda - RESTEasy - Runtime + + + + io.quarkus + quarkus-core + + + io.quarkus + quarkus-resteasy-server-common + + + com.amazonaws.serverless + aws-serverless-java-container-core + + + + junit + junit + test + + + org.jboss.resteasy + resteasy-multipart-provider + test + + + javax.annotation + javax.annotation-api + test + + + org.jboss.resteasy + resteasy-jackson2-provider + test + + + + + + + io.quarkus + quarkus-bootstrap-maven-plugin + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + diff --git a/extensions/amazon-lambda-resteasy/runtime/src/main/java/io/quarkus/amazon/lambda/resteasy/runtime/AmazonLambdaResteasyConfig.java b/extensions/amazon-lambda-resteasy/runtime/src/main/java/io/quarkus/amazon/lambda/resteasy/runtime/AmazonLambdaResteasyConfig.java new file mode 100644 index 0000000000000..b5c2eabd043ee --- /dev/null +++ b/extensions/amazon-lambda-resteasy/runtime/src/main/java/io/quarkus/amazon/lambda/resteasy/runtime/AmazonLambdaResteasyConfig.java @@ -0,0 +1,30 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Licensed 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.amazon.lambda.resteasy.runtime; + +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 AmazonLambdaResteasyConfig { + + /** + * Indicates if we are in debug mode. + */ + @ConfigItem(defaultValue = "false") + boolean debug; +} \ No newline at end of file diff --git a/extensions/amazon-lambda-resteasy/runtime/src/main/java/io/quarkus/amazon/lambda/resteasy/runtime/AmazonLambdaResteasyTemplate.java b/extensions/amazon-lambda-resteasy/runtime/src/main/java/io/quarkus/amazon/lambda/resteasy/runtime/AmazonLambdaResteasyTemplate.java new file mode 100644 index 0000000000000..173e2f40655f4 --- /dev/null +++ b/extensions/amazon-lambda-resteasy/runtime/src/main/java/io/quarkus/amazon/lambda/resteasy/runtime/AmazonLambdaResteasyTemplate.java @@ -0,0 +1,29 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Licensed 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.amazon.lambda.resteasy.runtime; + +import java.util.Map; + +import io.quarkus.amazon.lambda.resteasy.runtime.container.StreamLambdaHandler; +import io.quarkus.runtime.annotations.Template; + +@Template +public class AmazonLambdaResteasyTemplate { + + public void initHandler(Map initParameters, AmazonLambdaResteasyConfig config) { + StreamLambdaHandler.initHandler(initParameters, config.debug); + } +} diff --git a/extensions/amazon-lambda-resteasy/runtime/src/main/java/io/quarkus/amazon/lambda/resteasy/runtime/container/ResteasyLambdaContainerHandler.java b/extensions/amazon-lambda-resteasy/runtime/src/main/java/io/quarkus/amazon/lambda/resteasy/runtime/container/ResteasyLambdaContainerHandler.java new file mode 100644 index 0000000000000..ebe606761b71a --- /dev/null +++ b/extensions/amazon-lambda-resteasy/runtime/src/main/java/io/quarkus/amazon/lambda/resteasy/runtime/container/ResteasyLambdaContainerHandler.java @@ -0,0 +1,178 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Licensed 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.amazon.lambda.resteasy.runtime.container; + +import java.util.Collections; +import java.util.EnumSet; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.CountDownLatch; + +import javax.servlet.DispatcherType; +import javax.servlet.FilterRegistration; +import javax.servlet.ServletConfig; +import javax.servlet.ServletContext; + +import org.jboss.resteasy.plugins.server.servlet.ServletBootstrap; + +import com.amazonaws.serverless.proxy.AwsProxyExceptionHandler; +import com.amazonaws.serverless.proxy.AwsProxySecurityContextWriter; +import com.amazonaws.serverless.proxy.ExceptionHandler; +import com.amazonaws.serverless.proxy.RequestReader; +import com.amazonaws.serverless.proxy.ResponseWriter; +import com.amazonaws.serverless.proxy.SecurityContextWriter; +import com.amazonaws.serverless.proxy.internal.servlet.AwsHttpServletResponse; +import com.amazonaws.serverless.proxy.internal.servlet.AwsLambdaServletContainerHandler; +import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletRequest; +import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletRequestReader; +import com.amazonaws.serverless.proxy.internal.servlet.AwsProxyHttpServletResponseWriter; +import com.amazonaws.serverless.proxy.internal.testutils.Timer; +import com.amazonaws.serverless.proxy.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import com.amazonaws.services.lambda.runtime.Context; + +public class ResteasyLambdaContainerHandler extends + AwsLambdaServletContainerHandler { + + private ResteasyLambdaFilter resteasyFilter; + + public static ResteasyLambdaContainerHandler getAwsProxyHandler( + Map initParameters) { + ResteasyLambdaContainerHandler newHandler = new ResteasyLambdaContainerHandler<>( + AwsProxyRequest.class, + AwsProxyResponse.class, + new AwsProxyHttpServletRequestReader(), + new AwsProxyHttpServletResponseWriter(), + new AwsProxySecurityContextWriter(), + new AwsProxyExceptionHandler(), + initParameters); + newHandler.initialize(); + return newHandler; + } + + public ResteasyLambdaContainerHandler(Class requestTypeClass, + Class responseTypeClass, + RequestReader requestReader, + ResponseWriter responseWriter, + SecurityContextWriter securityContextWriter, + ExceptionHandler exceptionHandler, + Map initParameters) { + super(requestTypeClass, responseTypeClass, requestReader, responseWriter, securityContextWriter, exceptionHandler); + Timer.start("RESTEASY_CONTAINER_CONSTRUCTOR"); + + this.resteasyFilter = new ResteasyLambdaFilter(getServletContext(), + new LambdaServletBootstrap(new ServletConfigImpl(getServletContext(), initParameters))); + + Timer.stop("RESTEASY_CONTAINER_CONSTRUCTOR"); + } + + @Override + protected AwsHttpServletResponse getContainerResponse(AwsProxyHttpServletRequest request, CountDownLatch latch) { + return new AwsHttpServletResponse(request, latch); + } + + @Override + protected void handleRequest(AwsProxyHttpServletRequest httpServletRequest, AwsHttpServletResponse httpServletResponse, + Context lambdaContext) + throws Exception { + Timer.start("RESTEASY_HANDLE_REQUEST"); + + httpServletRequest.setServletContext(getServletContext()); + + doFilter(httpServletRequest, httpServletResponse, null); + Timer.stop("RESTEASY_HANDLE_REQUEST"); + } + + @Override + public void initialize() { + Timer.start("RESTEASY_COLD_START_INIT"); + + // manually add the spark filter to the chain. This should be the last one and match all uris + FilterRegistration.Dynamic resteasyFilterReg = getServletContext().addFilter("RESTEasyFilter", resteasyFilter); + resteasyFilterReg.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*"); + + Timer.stop("RESTEASY_COLD_START_INIT"); + } + + private static class LambdaServletBootstrap extends ServletBootstrap { + + public LambdaServletBootstrap(ServletConfig config) { + super(config); + } + + @Override + public String getParameter(String name) { + return super.getInitParameter(name); + } + } + + private static class ServletConfigImpl implements ServletConfig { + + private static final String SERVLET_NAME = "RESTEasy Lambda Servlet"; + + private final ServletContext servletContext; + + private final Map initParameters; + + private ServletConfigImpl(ServletContext servletContext, Map initParameters) { + this.servletContext = servletContext; + this.initParameters = Collections.unmodifiableMap(initParameters); + } + + @Override + public String getServletName() { + return SERVLET_NAME; + } + + @Override + public ServletContext getServletContext() { + return servletContext; + } + + @Override + public String getInitParameter(String name) { + if (name == null) { + throw new IllegalArgumentException("Init parameter name cannot be null."); + } + return initParameters.get(name); + } + + @Override + public Enumeration getInitParameterNames() { + return new IteratorEnumeration<>(initParameters.keySet().iterator()); + } + } + + public static class IteratorEnumeration implements Enumeration { + + private final Iterator iterator; + + public IteratorEnumeration(final Iterator iterator) { + this.iterator = iterator; + } + + @Override + public boolean hasMoreElements() { + return iterator.hasNext(); + } + + @Override + public T nextElement() { + return iterator.next(); + } + } +} diff --git a/extensions/amazon-lambda-resteasy/runtime/src/main/java/io/quarkus/amazon/lambda/resteasy/runtime/container/ResteasyLambdaFilter.java b/extensions/amazon-lambda-resteasy/runtime/src/main/java/io/quarkus/amazon/lambda/resteasy/runtime/container/ResteasyLambdaFilter.java new file mode 100644 index 0000000000000..be207a74500a1 --- /dev/null +++ b/extensions/amazon-lambda-resteasy/runtime/src/main/java/io/quarkus/amazon/lambda/resteasy/runtime/container/ResteasyLambdaFilter.java @@ -0,0 +1,111 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Licensed 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.amazon.lambda.resteasy.runtime.container; + +import java.io.IOException; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.jboss.resteasy.core.SynchronousDispatcher; +import org.jboss.resteasy.plugins.server.servlet.ConfigurationBootstrap; +import org.jboss.resteasy.plugins.server.servlet.HttpRequestFactory; +import org.jboss.resteasy.plugins.server.servlet.HttpResponseFactory; +import org.jboss.resteasy.plugins.server.servlet.HttpServletInputMessage; +import org.jboss.resteasy.plugins.server.servlet.HttpServletResponseWrapper; +import org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher; +import org.jboss.resteasy.specimpl.ResteasyHttpHeaders; +import org.jboss.resteasy.specimpl.ResteasyUriInfo; +import org.jboss.resteasy.spi.HttpRequest; +import org.jboss.resteasy.spi.HttpResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.amazonaws.serverless.proxy.internal.testutils.Timer; + +public class ResteasyLambdaFilter implements Filter, HttpRequestFactory, HttpResponseFactory { + + private static final Logger LOG = LoggerFactory.getLogger(ResteasyLambdaFilter.class); + + private ServletContainerDispatcher servletContainerDispatcher; + + private ServletContext servletContext; + + ResteasyLambdaFilter(ServletContext servletContext, ConfigurationBootstrap bootstrap) { + Timer.start("RESTEASY_FILTER_CONSTRUCTOR"); + + try { + servletContainerDispatcher = new ServletContainerDispatcher(); + servletContainerDispatcher.init(servletContext, bootstrap, this, this); + } catch (ServletException e) { + throw new IllegalStateException("Unable to initialize the RESTEasy dispatcher", e); + } + + Timer.stop("RESTEASY_FILTER_CONSTRUCTOR"); + } + + @Override + public void init(FilterConfig filterConfig) { + LOG.info("Initialize RESTEasy filter"); + } + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) + throws IOException, ServletException { + Timer.start("RESTEASY_FILTER_DOFILTER"); + + assert servletRequest instanceof HttpServletRequest; + assert servletResponse instanceof HttpServletResponse; + + HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest; + HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse; + + servletContainerDispatcher.service(httpServletRequest.getMethod(), httpServletRequest, httpServletResponse, true); + + Timer.stop("RESTEASY_FILTER_DOFILTER"); + filterChain.doFilter(servletRequest, servletResponse); + } + + @Override + public HttpResponse createResteasyHttpResponse(HttpServletResponse response) { + return new HttpServletResponseWrapper(response, servletContainerDispatcher.getDispatcher().getProviderFactory()); + } + + @Override + public HttpRequest createResteasyHttpRequest(String httpMethod, HttpServletRequest request, ResteasyHttpHeaders headers, + ResteasyUriInfo uriInfo, HttpResponse theResponse, HttpServletResponse response) { + // in the Jersey implementation, there is code to strip the base path from the URI + // we don't need it here as we use directly the AwsProxyHttpServletRequest + // which has the contextPath properly configured. + + return new HttpServletInputMessage(request, response, servletContext, theResponse, headers, uriInfo, + httpMethod.toUpperCase(), + (SynchronousDispatcher) servletContainerDispatcher.getDispatcher()); + } + + @Override + public void destroy() { + LOG.info("Destroy RESTEasy filter"); + servletContainerDispatcher.destroy(); + } +} diff --git a/extensions/amazon-lambda-resteasy/runtime/src/main/java/io/quarkus/amazon/lambda/resteasy/runtime/container/StreamLambdaHandler.java b/extensions/amazon-lambda-resteasy/runtime/src/main/java/io/quarkus/amazon/lambda/resteasy/runtime/container/StreamLambdaHandler.java new file mode 100644 index 0000000000000..6aa948355d3c8 --- /dev/null +++ b/extensions/amazon-lambda-resteasy/runtime/src/main/java/io/quarkus/amazon/lambda/resteasy/runtime/container/StreamLambdaHandler.java @@ -0,0 +1,48 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Licensed 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.amazon.lambda.resteasy.runtime.container; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Map; + +import com.amazonaws.serverless.proxy.internal.testutils.Timer; +import com.amazonaws.serverless.proxy.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestStreamHandler; + +public class StreamLambdaHandler implements RequestStreamHandler { + + private static ResteasyLambdaContainerHandler handler; + + public StreamLambdaHandler() { + } + + public static void initHandler(Map initParameters, boolean debugMode) { + if (debugMode) { + Timer.enable(); + } + StreamLambdaHandler.handler = ResteasyLambdaContainerHandler.getAwsProxyHandler(initParameters); + } + + @Override + public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) + throws IOException { + handler.proxyStream(inputStream, outputStream, context); + } +} \ No newline at end of file diff --git a/extensions/amazon-lambda-resteasy/runtime/src/test/java/io/quarkus/amazon/lambda/resteasy/adapter/test/EchoResteasyResource.java b/extensions/amazon-lambda-resteasy/runtime/src/test/java/io/quarkus/amazon/lambda/resteasy/adapter/test/EchoResteasyResource.java new file mode 100644 index 0000000000000..8d7ece0841024 --- /dev/null +++ b/extensions/amazon-lambda-resteasy/runtime/src/test/java/io/quarkus/amazon/lambda/resteasy/adapter/test/EchoResteasyResource.java @@ -0,0 +1,296 @@ +/* + * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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.amazon.lambda.resteasy.adapter.test; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Enumeration; +import java.util.List; +import java.util.Map; +import java.util.Random; + +import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.Consumes; +import javax.ws.rs.Encoded; +import javax.ws.rs.GET; +import javax.ws.rs.HeaderParam; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.SecurityContext; +import javax.ws.rs.core.UriInfo; + +import org.apache.commons.io.IOUtils; +import org.jboss.resteasy.plugins.providers.multipart.InputPart; +import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataInput; +import org.jboss.resteasy.spi.HttpRequest; + +import com.amazonaws.serverless.proxy.RequestReader; +import com.amazonaws.serverless.proxy.model.AwsProxyRequestContext; + +import io.quarkus.amazon.lambda.resteasy.adapter.test.model.MapResponseModel; +import io.quarkus.amazon.lambda.resteasy.adapter.test.model.SingleValueModel; +import io.quarkus.amazon.lambda.resteasy.adapter.test.provider.ServletRequestFilter; + +/** + * RESTEasy resource class for aws-serverless-java-container unit proxy + */ +@Path("/echo") +public class EchoResteasyResource { + public static final String SERVLET_RESP_HEADER_KEY = "X-HttpServletResponse"; + public static final String EXCEPTION_MESSAGE = "Fake exception"; + + @Context + SecurityContext securityCtx; + + @Path("/decoded-param") + @GET + @Produces(MediaType.APPLICATION_JSON) + public SingleValueModel echoDecodedParam(@QueryParam("param") String param) { + SingleValueModel model = new SingleValueModel(); + model.setValue(param); + return model; + } + + @Path("/filter-attribute") + @GET + @Produces(MediaType.APPLICATION_JSON) + public SingleValueModel returnFilterAttribute(@Context HttpServletRequest req) { + SingleValueModel model = new SingleValueModel(); + if (req.getAttribute(ServletRequestFilter.FILTER_ATTRIBUTE_NAME) == null) { + model.setValue(""); + } else { + model.setValue(req.getAttribute(ServletRequestFilter.FILTER_ATTRIBUTE_NAME).toString()); + } + return model; + } + + @Path("/list-query-string") + @GET + @Produces(MediaType.APPLICATION_JSON) + public SingleValueModel echoQueryStringLength(@QueryParam("list") List param) { + System.out.println("param: " + param + " = " + param.size()); + SingleValueModel model = new SingleValueModel(); + model.setValue(param.size() + ""); + return model; + } + + @Path("/encoded-param") + @GET + @Produces(MediaType.APPLICATION_JSON) + public SingleValueModel echoEncodedParam(@QueryParam("param") @Encoded String param) { + SingleValueModel model = new SingleValueModel(); + model.setValue(param); + return model; + } + + @Path("/headers") + @GET + @Produces(MediaType.APPLICATION_JSON) + public MapResponseModel echoHeaders(@Context HttpRequest httpRequest) { + MapResponseModel headers = new MapResponseModel(); + for (String key : httpRequest.getHttpHeaders().getRequestHeaders().keySet()) { + headers.addValue(key, httpRequest.getHttpHeaders().getHeaderString(key)); + } + + return headers; + } + + @Path("/security-context") + @GET + @Produces(MediaType.APPLICATION_JSON) + public SingleValueModel getPrincipal() { + SingleValueModel output = new SingleValueModel(); + if (securityCtx != null) { + output.setValue(securityCtx.getUserPrincipal().getName()); + } + return output; + } + + @Path("/servlet-headers") + @GET + @Produces(MediaType.APPLICATION_JSON) + public MapResponseModel echoServletHeaders(@Context HttpServletRequest context) { + MapResponseModel headers = new MapResponseModel(); + Enumeration headerNames = context.getHeaderNames(); + while (headerNames.hasMoreElements()) { + String headerName = headerNames.nextElement(); + headers.addValue(headerName, context.getHeader(headerName)); + } + return headers; + } + + @Path("/servlet-context") + @GET + @Produces(MediaType.APPLICATION_JSON) + public SingleValueModel echoContextInformation(@Context ServletContext context) { + SingleValueModel singleValueModel = new SingleValueModel(); + singleValueModel.setValue(context.getServerInfo()); + + return singleValueModel; + } + + @Path("/query-string") + @GET + @Produces(MediaType.APPLICATION_JSON) + public MapResponseModel echoQueryString(@Context UriInfo context) { + MapResponseModel queryStrings = new MapResponseModel(); + for (String key : context.getQueryParameters().keySet()) { + queryStrings.addValue(key, context.getQueryParameters().getFirst(key)); + } + + return queryStrings; + } + + @Path("/scheme") + @GET + @Produces(MediaType.APPLICATION_JSON) + public SingleValueModel echoRequestScheme(@Context UriInfo context) { + SingleValueModel model = new SingleValueModel(); + System.out.println("RequestUri: " + context.getRequestUri().toString()); + model.setValue(context.getRequestUri().getScheme()); + return model; + } + + @Path("/authorizer-principal") + @GET + @Produces(MediaType.APPLICATION_JSON) + public SingleValueModel echoAuthorizerPrincipal(@Context HttpRequest httpRequest) { + SingleValueModel valueModel = new SingleValueModel(); + AwsProxyRequestContext awsProxyRequestContext = (AwsProxyRequestContext) httpRequest + .getAttribute(RequestReader.API_GATEWAY_CONTEXT_PROPERTY); + valueModel.setValue(awsProxyRequestContext.getAuthorizer().getPrincipalId()); + + return valueModel; + } + + @Path("/authorizer-context") + @GET + @Produces(MediaType.APPLICATION_JSON) + public SingleValueModel echoAuthorizerContext(@Context HttpRequest httpRequest, @QueryParam("key") String key) { + SingleValueModel valueModel = new SingleValueModel(); + AwsProxyRequestContext awsProxyRequestContext = (AwsProxyRequestContext) httpRequest + .getAttribute(RequestReader.API_GATEWAY_CONTEXT_PROPERTY); + valueModel.setValue(awsProxyRequestContext.getAuthorizer().getContextValue(key)); + + return valueModel; + } + + @Path("/json-body") + @POST + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public SingleValueModel echoJsonValue(final SingleValueModel requestValue) { + SingleValueModel output = new SingleValueModel(); + output.setValue(requestValue.getValue()); + + return output; + } + + @Path("/status-code") + @GET + @Produces(MediaType.APPLICATION_JSON) + public Response echoCustomStatusCode(@QueryParam("status") int statusCode) { + SingleValueModel output = new SingleValueModel(); + output.setValue("" + statusCode); + + return Response.status(statusCode).entity(output).build(); + } + + @Path("/servlet-response") + @GET + @Produces(MediaType.APPLICATION_JSON) + public Response echoCustomStatusCode(@Context HttpServletResponse resp) { + SingleValueModel output = new SingleValueModel(); + output.setValue("Custom header in resp"); + resp.setHeader(SERVLET_RESP_HEADER_KEY, "1"); + return Response.ok().entity(output).build(); + } + + @Path("/binary") + @GET + @Produces("application/octet-stream") + public Response echoBinaryData() { + byte[] b = new byte[128]; + new Random().nextBytes(b); + + return Response.ok(b).build(); + } + + @Path("/empty-stream/{paramId}/test/{param2}") + @PUT + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + public Response emptyStream(@PathParam("paramId") String paramId, @PathParam("param2") String param2) { + SingleValueModel sv = new SingleValueModel(); + sv.setValue(paramId); + return Response.ok(sv).build(); + } + + @Path("/exception") + @GET + public Response throwException() { + throw new UnsupportedOperationException(EXCEPTION_MESSAGE); + } + + @Path("/encoded-path/{resource}") + @GET + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + public Response encodedPathParam(@Encoded @PathParam("resource") String resource) { + SingleValueModel sv = new SingleValueModel(); + sv.setValue(resource); + return Response.ok(sv).build(); + } + + @Path("/referer-header") + @GET + @Produces(MediaType.APPLICATION_JSON) + @Consumes(MediaType.APPLICATION_JSON) + public Response referer(@HeaderParam("Referer") String referer) { + SingleValueModel sv = new SingleValueModel(); + sv.setValue(referer); + return Response.ok(sv).build(); + } + + @Path("/file-size") + @POST + @Consumes(MediaType.MULTIPART_FORM_DATA) + @Produces(MediaType.APPLICATION_JSON) + public Response fileSize(MultipartFormDataInput input) { + SingleValueModel sv = new SingleValueModel(); + + try { + Map> uploadForm = input.getFormDataMap(); + List inputParts = uploadForm.get("file"); + + InputPart fileInput = inputParts.get(0); + InputStream inputStream = fileInput.getBody(InputStream.class, null); + byte[] bytes = IOUtils.toByteArray(inputStream); + + sv.setValue("" + bytes.length); + return Response.ok(sv).build(); + } catch (IOException e) { + e.printStackTrace(); + return Response.status(500).build(); + } + } +} diff --git a/extensions/amazon-lambda-resteasy/runtime/src/test/java/io/quarkus/amazon/lambda/resteasy/adapter/test/ResteasyAwsProxyTest.java b/extensions/amazon-lambda-resteasy/runtime/src/test/java/io/quarkus/amazon/lambda/resteasy/adapter/test/ResteasyAwsProxyTest.java new file mode 100644 index 0000000000000..96fa7eeb937dd --- /dev/null +++ b/extensions/amazon-lambda-resteasy/runtime/src/test/java/io/quarkus/amazon/lambda/resteasy/adapter/test/ResteasyAwsProxyTest.java @@ -0,0 +1,413 @@ +/* + * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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.amazon.lambda.resteasy.adapter.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.StringJoiner; +import java.util.UUID; + +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.apache.commons.codec.binary.Base64; +import org.jboss.resteasy.plugins.server.servlet.ResteasyContextParameters; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler; +import com.amazonaws.serverless.proxy.internal.servlet.AwsServletContext; +import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; +import com.amazonaws.serverless.proxy.internal.testutils.MockLambdaContext; +import com.amazonaws.serverless.proxy.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import com.amazonaws.services.lambda.runtime.Context; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import io.quarkus.amazon.lambda.resteasy.adapter.test.model.MapResponseModel; +import io.quarkus.amazon.lambda.resteasy.adapter.test.model.SingleValueModel; +import io.quarkus.amazon.lambda.resteasy.adapter.test.provider.CustomExceptionMapper; +import io.quarkus.amazon.lambda.resteasy.adapter.test.provider.ServletRequestFilter; +import io.quarkus.amazon.lambda.resteasy.runtime.container.ResteasyLambdaContainerHandler; + +/** + * Unit test class for the RESTEasy AWS_PROXY default implementation + */ +@RunWith(Parameterized.class) +public class ResteasyAwsProxyTest { + private static final String CUSTOM_HEADER_KEY = "x-custom-header"; + private static final String CUSTOM_HEADER_VALUE = "my-custom-value"; + private static final String AUTHORIZER_PRINCIPAL_ID = "test-principal-" + UUID.randomUUID().toString(); + private static final String USER_PRINCIPAL = "user1"; + + private static ObjectMapper objectMapper = new ObjectMapper(); + + private static ResteasyLambdaContainerHandler handler; + + private static Context lambdaContext = new MockLambdaContext(); + + private boolean isAlb; + + public ResteasyAwsProxyTest(boolean alb) { + isAlb = alb; + } + + @Parameterized.Parameters + public static Collection data() { + return Arrays.asList(new Object[] { false, true }); + } + + @BeforeClass + public static void setup() { + Map initParameters = new HashMap<>(); + initParameters.put(ResteasyContextParameters.RESTEASY_SCANNED_RESOURCES, EchoResteasyResource.class.getName()); + initParameters.put("resteasy.servlet.mapping.prefix", "/"); + initParameters.put(ResteasyContextParameters.RESTEASY_USE_BUILTIN_PROVIDERS, "true"); + initParameters.put(ResteasyContextParameters.RESTEASY_PROVIDERS, new StringJoiner(",") + .add(CustomExceptionMapper.class.getName()) + .add(ServletRequestFilter.class.getName()).toString()); + + handler = ResteasyLambdaContainerHandler.getAwsProxyHandler(initParameters); + } + + private AwsProxyRequestBuilder getRequestBuilder(String path, String method) { + AwsProxyRequestBuilder builder = new AwsProxyRequestBuilder(path, method); + if (isAlb) + builder.alb(); + + return builder; + } + + @Test + public void alb_basicRequest_expectSuccess() { + AwsProxyRequest request = getRequestBuilder("/echo/headers", "GET") + .json() + .header(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE) + .alb() + .build(); + + AwsProxyResponse output = handler.proxy(request, lambdaContext); + assertEquals(200, output.getStatusCode()); + assertEquals("application/json", output.getMultiValueHeaders().getFirst("Content-Type")); + assertNotNull(output.getStatusDescription()); + System.out.println(output.getStatusDescription()); + + validateMapResponseModel(output); + } + + @Test + public void headers_getHeaders_echo() { + AwsProxyRequest request = getRequestBuilder("/echo/headers", "GET") + .json() + .header(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE) + .build(); + + AwsProxyResponse output = handler.proxy(request, lambdaContext); + assertEquals(200, output.getStatusCode()); + assertEquals("application/json", output.getMultiValueHeaders().getFirst("Content-Type")); + + validateMapResponseModel(output); + } + + @Test + public void headers_servletRequest_echo() { + AwsProxyRequest request = getRequestBuilder("/echo/servlet-headers", "GET") + .json() + .header(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE) + .build(); + + AwsProxyResponse output = handler.proxy(request, lambdaContext); + assertEquals(200, output.getStatusCode()); + assertEquals("application/json", output.getMultiValueHeaders().getFirst("Content-Type")); + + validateMapResponseModel(output); + } + + @Test + public void context_servletResponse_setCustomHeader() { + AwsProxyRequest request = getRequestBuilder("/echo/servlet-response", "GET") + .json() + .build(); + + AwsProxyResponse output = handler.proxy(request, lambdaContext); + assertEquals(200, output.getStatusCode()); + assertTrue(output.getMultiValueHeaders().containsKey(EchoResteasyResource.SERVLET_RESP_HEADER_KEY)); + } + + @Test + public void context_serverInfo_correctContext() { + AwsProxyRequest request = getRequestBuilder("/echo/servlet-context", "GET").build(); + AwsProxyResponse output = handler.proxy(request, lambdaContext); + for (String header : output.getMultiValueHeaders().keySet()) { + System.out.println(header + ": " + output.getMultiValueHeaders().getFirst(header)); + } + assertEquals(200, output.getStatusCode()); + assertEquals("application/json", output.getMultiValueHeaders().getFirst("Content-Type")); + + validateSingleValueModel(output, AwsServletContext.SERVER_INFO); + } + + @Test + public void requestScheme_valid_expectHttps() { + AwsProxyRequest request = getRequestBuilder("/echo/scheme", "GET") + .json() + .build(); + + AwsProxyResponse output = handler.proxy(request, lambdaContext); + assertEquals(200, output.getStatusCode()); + assertEquals("application/json", output.getMultiValueHeaders().getFirst("Content-Type")); + + validateSingleValueModel(output, "https"); + } + + @Test + public void requestFilter_injectsServletRequest_expectCustomAttribute() { + AwsProxyRequest request = getRequestBuilder("/echo/filter-attribute", "GET") + .json() + .build(); + + AwsProxyResponse output = handler.proxy(request, lambdaContext); + assertEquals(200, output.getStatusCode()); + assertEquals("application/json", output.getMultiValueHeaders().getFirst("Content-Type")); + + validateSingleValueModel(output, ServletRequestFilter.FILTER_ATTRIBUTE_VALUE); + } + + @Test + public void authorizer_securityContext_customPrincipalSuccess() { + AwsProxyRequest request = getRequestBuilder("/echo/authorizer-principal", "GET") + .json() + .authorizerPrincipal(AUTHORIZER_PRINCIPAL_ID) + .build(); + + AwsProxyResponse output = handler.proxy(request, lambdaContext); + if (!isAlb) { + assertEquals(200, output.getStatusCode()); + assertEquals("application/json", output.getMultiValueHeaders().getFirst("Content-Type")); + validateSingleValueModel(output, AUTHORIZER_PRINCIPAL_ID); + } + + } + + @Test + public void authorizer_securityContext_customAuthorizerContextSuccess() { + AwsProxyRequest request = getRequestBuilder("/echo/authorizer-context", "GET") + .json() + .authorizerPrincipal(AUTHORIZER_PRINCIPAL_ID) + .authorizerContextValue(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE) + .queryString("key", CUSTOM_HEADER_KEY) + .build(); + + AwsProxyResponse output = handler.proxy(request, lambdaContext); + assertEquals(200, output.getStatusCode()); + assertEquals("application/json", output.getMultiValueHeaders().getFirst("Content-Type")); + + validateSingleValueModel(output, CUSTOM_HEADER_VALUE); + } + + @Test + public void errors_unknownRoute_expect404() { + AwsProxyRequest request = getRequestBuilder("/echo/test33", "GET").build(); + + AwsProxyResponse output = handler.proxy(request, lambdaContext); + assertEquals(404, output.getStatusCode()); + } + + @Test + public void error_contentType_invalidContentType() { + AwsProxyRequest request = getRequestBuilder("/echo/json-body", "POST") + .header("Content-Type", "application/octet-stream") + .body("asdasdasd") + .build(); + + AwsProxyResponse output = handler.proxy(request, lambdaContext); + assertEquals(415, output.getStatusCode()); + } + + @Test + public void error_statusCode_methodNotAllowed() { + AwsProxyRequest request = getRequestBuilder("/echo/status-code", "POST") + .json() + .queryString("status", "201") + .build(); + + AwsProxyResponse output = handler.proxy(request, lambdaContext); + assertEquals(405, output.getStatusCode()); + } + + @Test + public void responseBody_responseWriter_validBody() throws JsonProcessingException { + SingleValueModel singleValueModel = new SingleValueModel(); + singleValueModel.setValue(CUSTOM_HEADER_VALUE); + AwsProxyRequest request = getRequestBuilder("/echo/json-body", "POST") + .json() + .body(objectMapper.writeValueAsString(singleValueModel)) + .build(); + + AwsProxyResponse output = handler.proxy(request, lambdaContext); + assertEquals(200, output.getStatusCode()); + assertNotNull(output.getBody()); + + validateSingleValueModel(output, CUSTOM_HEADER_VALUE); + } + + @Test + public void statusCode_responseStatusCode_customStatusCode() { + AwsProxyRequest request = getRequestBuilder("/echo/status-code", "GET") + .json() + .queryString("status", "201") + .build(); + + AwsProxyResponse output = handler.proxy(request, lambdaContext); + assertEquals(201, output.getStatusCode()); + } + + @Test + public void base64_binaryResponse_base64Encoding() { + AwsProxyRequest request = getRequestBuilder("/echo/binary", "GET").build(); + + AwsProxyResponse response = handler.proxy(request, lambdaContext); + assertNotNull(response.getBody()); + assertTrue(Base64.isBase64(response.getBody())); + } + + @Test + public void exception_mapException_mapToNotImplemented() { + AwsProxyRequest request = getRequestBuilder("/echo/exception", "GET").build(); + + AwsProxyResponse response = handler.proxy(request, lambdaContext); + assertNotNull(response.getBody()); + assertEquals(EchoResteasyResource.EXCEPTION_MESSAGE, response.getBody()); + assertEquals(Response.Status.NOT_IMPLEMENTED.getStatusCode(), response.getStatusCode()); + } + + @Test + public void stripBasePath_route_shouldRouteCorrectly() { + AwsProxyRequest request = getRequestBuilder("/custompath/echo/status-code", "GET") + .json() + .queryString("status", "201") + .build(); + handler.stripBasePath("/custompath"); + AwsProxyResponse output = handler.proxy(request, lambdaContext); + assertEquals(201, output.getStatusCode()); + handler.stripBasePath(""); + } + + @Test + /** + * In the case of RESTEasy, we properly route things as the AWS library includes the + * stripBasePath element inside the contextPath so it's automatically removed + * from the URL to resolve. + *

+ * The name of this test is not really consistent with what it is doing now but + * we keep it that way for consistency with the Jersey tests. + */ + public void stripBasePath_route_shouldReturn404WithStageAsContext() { + AwsProxyRequest request = getRequestBuilder("/custompath/echo/status-code", "GET") + .stage("prod") + .json() + .queryString("status", "201") + .build(); + handler.stripBasePath("/custompath"); + LambdaContainerHandler.getContainerConfig().setUseStageAsServletContext(true); + AwsProxyResponse output = handler.proxy(request, lambdaContext); + assertEquals(201, output.getStatusCode()); + handler.stripBasePath(""); + LambdaContainerHandler.getContainerConfig().setUseStageAsServletContext(false); + } + + @Test + public void stripBasePath_route_shouldReturn404() { + AwsProxyRequest request = getRequestBuilder("/custompath/echo/status-code", "GET") + .json() + .queryString("status", "201") + .build(); + handler.stripBasePath("/custom"); + AwsProxyResponse output = handler.proxy(request, lambdaContext); + assertEquals(404, output.getStatusCode()); + handler.stripBasePath(""); + } + + @Test + public void securityContext_injectPrincipal_expectPrincipalName() { + AwsProxyRequest request = getRequestBuilder("/echo/security-context", "GET") + .authorizerPrincipal(USER_PRINCIPAL).build(); + + AwsProxyResponse resp = handler.proxy(request, lambdaContext); + assertEquals(200, resp.getStatusCode()); + validateSingleValueModel(resp, USER_PRINCIPAL); + } + + @Test + public void emptyStream_putNullBody_expectPutToSucceed() { + AwsProxyRequest request = getRequestBuilder("/echo/empty-stream/" + CUSTOM_HEADER_KEY + "/test/2", "PUT") + .nullBody() + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) + .build(); + AwsProxyResponse resp = handler.proxy(request, lambdaContext); + assertEquals(200, resp.getStatusCode()); + validateSingleValueModel(resp, CUSTOM_HEADER_KEY); + } + + @Test + public void refererHeader_headerParam_expectCorrectInjection() { + String refererValue = "test-referer"; + AwsProxyRequest request = getRequestBuilder("/echo/referer-header", "GET") + .nullBody() + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) + .header("Referer", refererValue) + .build(); + + AwsProxyResponse resp = handler.proxy(request, lambdaContext); + assertEquals(200, resp.getStatusCode()); + validateSingleValueModel(resp, refererValue); + } + + private void validateMapResponseModel(AwsProxyResponse output) { + validateMapResponseModel(output, CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE); + } + + private void validateMapResponseModel(AwsProxyResponse output, String key, String value) { + try { + MapResponseModel response = objectMapper.readValue(output.getBody(), MapResponseModel.class); + assertNotNull(response.getValues().get(key)); + assertEquals(value, response.getValues().get(key)); + } catch (IOException e) { + fail("Exception while parsing response body: " + e.getMessage()); + e.printStackTrace(); + } + } + + private void validateSingleValueModel(AwsProxyResponse output, String value) { + try { + SingleValueModel response = objectMapper.readValue(output.getBody(), SingleValueModel.class); + assertNotNull(response.getValue()); + assertEquals(value, response.getValue()); + } catch (IOException e) { + fail("Exception while parsing response body: " + e.getMessage()); + e.printStackTrace(); + } + } +} diff --git a/extensions/amazon-lambda-resteasy/runtime/src/test/java/io/quarkus/amazon/lambda/resteasy/adapter/test/ResteasyParamEncodingTest.java b/extensions/amazon-lambda-resteasy/runtime/src/test/java/io/quarkus/amazon/lambda/resteasy/adapter/test/ResteasyParamEncodingTest.java new file mode 100644 index 0000000000000..9d21d0e006e37 --- /dev/null +++ b/extensions/amazon-lambda-resteasy/runtime/src/test/java/io/quarkus/amazon/lambda/resteasy/adapter/test/ResteasyParamEncodingTest.java @@ -0,0 +1,314 @@ +package io.quarkus.amazon.lambda.resteasy.adapter.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import javax.ws.rs.core.MediaType; + +import org.jboss.resteasy.plugins.server.servlet.ResteasyContextParameters; +import org.junit.BeforeClass; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler; +import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; +import com.amazonaws.serverless.proxy.internal.testutils.MockLambdaContext; +import com.amazonaws.serverless.proxy.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.model.AwsProxyResponse; +import com.amazonaws.services.lambda.runtime.Context; +import com.fasterxml.jackson.databind.ObjectMapper; + +import io.quarkus.amazon.lambda.resteasy.adapter.test.model.MapResponseModel; +import io.quarkus.amazon.lambda.resteasy.adapter.test.model.SingleValueModel; +import io.quarkus.amazon.lambda.resteasy.runtime.container.ResteasyLambdaContainerHandler; + +@RunWith(Parameterized.class) +public class ResteasyParamEncodingTest { + + private static final String SIMPLE_ENCODED_PARAM = "p/z+3"; + private static final String JSON_ENCODED_PARAM = "{\"name\":\"faisal\"}"; + private static final String QUERY_STRING_KEY = "identifier"; + private static final String QUERY_STRING_NON_ENCODED_VALUE = "Space Test"; + private static final String QUERY_STRING_ENCODED_VALUE = "Space%20Test"; + private static final byte[] FILE_CONTENTS = new byte[] { + (byte) 47, (byte) 85, (byte) 135, (byte) 12, (byte) 53, (byte) 7, (byte) 158, (byte) 212, (byte) 55, (byte) 193, + (byte) 149, (byte) 3, (byte) 166, (byte) 181, + (byte) 151, (byte) 84, (byte) 122, (byte) 200, (byte) 244, (byte) 5, (byte) 115, (byte) 159, (byte) 66, (byte) 64, + (byte) 143, (byte) 211, (byte) 13, (byte) 63, + (byte) 235, (byte) 184, (byte) 51, (byte) 49, (byte) 143, (byte) 167, (byte) 231, (byte) 31, (byte) 78, (byte) 234, + (byte) 145, (byte) 105, (byte) 190, (byte) 170, + (byte) 49, (byte) 135, (byte) 175, (byte) 106, (byte) 25, (byte) 86, (byte) 145, (byte) 181, (byte) 156, (byte) 23, + (byte) 153, (byte) 99, (byte) 175, (byte) 63, + (byte) 43, (byte) 208, (byte) 5, (byte) 16, (byte) 140, (byte) 103, (byte) 146, (byte) 254, (byte) 155, (byte) 97, + (byte) 53, (byte) 100, (byte) 137, (byte) 6, + (byte) 62, (byte) 101, (byte) 189, (byte) 137, (byte) 140, (byte) 5, (byte) 110, (byte) 218, (byte) 113, (byte) 132, + (byte) 36, (byte) 188, (byte) 19, (byte) 168, + (byte) 93, (byte) 169, (byte) 124, (byte) 253, (byte) 201, (byte) 233, (byte) 21, (byte) 80, (byte) 4, (byte) 56, + (byte) 0, (byte) 204, (byte) 205, (byte) 232, + (byte) 213, (byte) 253, (byte) 232, (byte) 91, (byte) 153, (byte) 169, (byte) 82, (byte) 247, (byte) 78, (byte) 71, + (byte) 188, (byte) 71, (byte) 23, (byte) 171, + (byte) 232, (byte) 26, (byte) 146, (byte) 189, (byte) 145, (byte) 82, (byte) 79, (byte) 148, (byte) 1, (byte) 201, + (byte) 243, (byte) 73, (byte) 98, (byte) 65, + (byte) 236, (byte) 177, (byte) 211, (byte) 106, (byte) 105, (byte) 46, (byte) 204, (byte) 214, (byte) 55, + (byte) 182, (byte) 55, (byte) 149, (byte) 221, (byte) 52, + (byte) 186, (byte) 122, (byte) 255, (byte) 195, (byte) 60, (byte) 146, (byte) 21, (byte) 212, (byte) 139, (byte) 38, + (byte) 146, (byte) 166, (byte) 14, (byte) 174, + (byte) 242, (byte) 145, (byte) 16, (byte) 44, (byte) 68, (byte) 89, (byte) 25, (byte) 219, (byte) 62, (byte) 227, + (byte) 6, (byte) 89, (byte) 194, (byte) 146, (byte) 93, + (byte) 167, (byte) 230, (byte) 90, (byte) 59, (byte) 35, (byte) 136, (byte) 37, (byte) 196, (byte) 118, (byte) 16, + (byte) 28, (byte) 107, (byte) 105, (byte) 87, + (byte) 195, (byte) 86, (byte) 87, (byte) 180, (byte) 176, (byte) 118, (byte) 6, (byte) 29, (byte) 26, (byte) 51, + (byte) 94, (byte) 21, (byte) 23, (byte) 32, (byte) 156, + (byte) 150, (byte) 204, (byte) 53, (byte) 110, (byte) 134, (byte) 153, (byte) 138, (byte) 247, (byte) 98, + (byte) 135, (byte) 249, (byte) 119, (byte) 121, (byte) 2, + (byte) 42, (byte) 62, (byte) 198, (byte) 197, (byte) 112, (byte) 153, (byte) 244, (byte) 174, (byte) 145, (byte) 54, + (byte) 246, (byte) 44, (byte) 198, (byte) 50, + (byte) 2, (byte) 37, (byte) 102, (byte) 50, (byte) 103, (byte) 207, (byte) 81, (byte) 62, (byte) 138, (byte) 164, + (byte) 140, (byte) 64, (byte) 247, (byte) 115, + (byte) 40, (byte) 41, (byte) 252, (byte) 54, (byte) 189, (byte) 207, (byte) 124, (byte) 147, (byte) 122, (byte) 243, + (byte) 83, (byte) 34, (byte) 160, (byte) 64, (byte) 189, (byte) 226, (byte) 202, (byte) 181, (byte) 55, (byte) 158, + (byte) 121, (byte) 78, (byte) 143, (byte) 41, (byte) 58, (byte) 27, (byte) 77, (byte) 186, (byte) 214, (byte) 23, + (byte) 132, (byte) 100, (byte) 180, (byte) 26, (byte) 37, (byte) 247, (byte) 254, (byte) 97, (byte) 214, (byte) 57, + (byte) 30, (byte) 46, (byte) 96, (byte) 44, (byte) 138, (byte) 15, (byte) 162, (byte) 93, (byte) 222, (byte) 239, + (byte) 189, (byte) 72, (byte) 15, (byte) 79, (byte) 136, (byte) 210, (byte) 44, (byte) 233, (byte) 99, (byte) 72, + (byte) 234, (byte) 225, (byte) 245, (byte) 27, (byte) 111, (byte) 175, (byte) 132, (byte) 112, (byte) 135, + (byte) 253, (byte) 66, (byte) 215, (byte) 168, (byte) 156, (byte) 168, (byte) 79, (byte) 78, (byte) 140, (byte) 14, + (byte) 129, (byte) 37, (byte) 238, (byte) 196, (byte) 34, (byte) 245, (byte) 141, (byte) 148, (byte) 161, (byte) 29, + (byte) 110, (byte) 32, (byte) 255, (byte) 247, (byte) 52, (byte) 48, (byte) 102, (byte) 42, (byte) 54, (byte) 97, + (byte) 185, (byte) 10, (byte) 114, (byte) 225, (byte) 247, (byte) 254, (byte) 108, (byte) 116, (byte) 73, (byte) 84, + (byte) 242, (byte) 86, (byte) 15, (byte) 72, (byte) 68, (byte) 172, (byte) 74, (byte) 107, (byte) 103, (byte) 222, + (byte) 246, (byte) 152, (byte) 67, (byte) 12, (byte) 104, (byte) 245, (byte) 20, (byte) 112, (byte) 94, (byte) 197, + (byte) 201, (byte) 89, (byte) 182, (byte) 214, (byte) 6, (byte) 182, (byte) 165, (byte) 209, (byte) 79, (byte) 192, + (byte) 211, (byte) 163, (byte) 208, (byte) 12, (byte) 73, (byte) 53, (byte) 99, (byte) 59, (byte) 182, (byte) 186, + (byte) 48, (byte) 184, (byte) 215, (byte) 22, (byte) 24, (byte) 233, (byte) 109, (byte) 206, (byte) 59, (byte) 0, + (byte) 118, (byte) 141, (byte) 25, (byte) 50, (byte) 242, (byte) 247, (byte) 240, (byte) 238, (byte) 127, + (byte) 236, (byte) 241, (byte) 224, (byte) 20, (byte) 61, (byte) 65, (byte) 148, (byte) 120, (byte) 192, (byte) 99, + (byte) 172, (byte) 194, (byte) 135, (byte) 61, (byte) 147, (byte) 251, (byte) 161, (byte) 219, (byte) 252, + (byte) 187, (byte) 154, (byte) 115, (byte) 193, (byte) 118, (byte) 167, (byte) 130, (byte) 174, (byte) 211, + (byte) 236, (byte) 141, (byte) 14, (byte) 8, (byte) 244, (byte) 110, (byte) 66, (byte) 210, (byte) 110, (byte) 236, + (byte) 255, (byte) 25, (byte) 16, (byte) 134, (byte) 70, (byte) 196, (byte) 163, (byte) 30, (byte) 177, (byte) 238, + (byte) 225, (byte) 237, (byte) 12, (byte) 14, (byte) 215, (byte) 40, (byte) 77, (byte) 206, (byte) 76, (byte) 122, + (byte) 205, (byte) 20, (byte) 183, (byte) 106, (byte) 230, (byte) 230, (byte) 123, (byte) 209, (byte) 77, + (byte) 102, (byte) 65, (byte) 241, (byte) 41, (byte) 213, (byte) 219, (byte) 79, (byte) 37, (byte) 61, (byte) 10, + (byte) 154, (byte) 19, (byte) 93, (byte) 33, (byte) 72, (byte) 105, (byte) 247, (byte) 221, (byte) 145, (byte) 179, + (byte) 69, (byte) 38, (byte) 234, (byte) 163, (byte) 218, (byte) 131, (byte) 179, (byte) 30, (byte) 114, (byte) 150, + (byte) 106, (byte) 17, (byte) 187, (byte) 229, (byte) 106, (byte) 7, (byte) 112 + }; + + private static ObjectMapper objectMapper = new ObjectMapper(); + + private static ResteasyLambdaContainerHandler handler; + + private static Context lambdaContext = new MockLambdaContext(); + + private boolean isAlb; + + public ResteasyParamEncodingTest(boolean alb) { + isAlb = alb; + LambdaContainerHandler.getContainerConfig().addBinaryContentTypes(MediaType.MULTIPART_FORM_DATA); + } + + @Parameterized.Parameters + public static Collection data() { + return Arrays.asList(new Object[] { false, true }); + } + + @BeforeClass + public static void setup() { + Map initParameters = new HashMap<>(); + initParameters.put(ResteasyContextParameters.RESTEASY_SCANNED_RESOURCES, EchoResteasyResource.class.getName()); + initParameters.put("resteasy.servlet.mapping.prefix", "/"); + initParameters.put(ResteasyContextParameters.RESTEASY_USE_BUILTIN_PROVIDERS, "true"); + + handler = ResteasyLambdaContainerHandler.getAwsProxyHandler(initParameters); + } + + private AwsProxyRequestBuilder getRequestBuilder(String path, String method) { + AwsProxyRequestBuilder builder = new AwsProxyRequestBuilder(path, method); + if (isAlb) + builder.alb(); + + return builder; + } + + @Test + public void queryString_uriInfo_echo() { + AwsProxyRequest request = getRequestBuilder("/echo/query-string", "GET") + .json() + .queryString(QUERY_STRING_KEY, QUERY_STRING_NON_ENCODED_VALUE) + .build(); + + AwsProxyResponse output = handler.proxy(request, lambdaContext); + assertEquals(200, output.getStatusCode()); + assertEquals("application/json", output.getMultiValueHeaders().getFirst("Content-Type")); + + validateMapResponseModel(output, QUERY_STRING_KEY, QUERY_STRING_NON_ENCODED_VALUE); + } + + @Test + public void queryString_notEncoded_echo() { + AwsProxyRequest request = getRequestBuilder("/echo/query-string", "GET") + .json() + .queryString(QUERY_STRING_KEY, QUERY_STRING_NON_ENCODED_VALUE) + .build(); + + AwsProxyResponse output = handler.proxy(request, lambdaContext); + assertEquals(200, output.getStatusCode()); + assertEquals("application/json", output.getMultiValueHeaders().getFirst("Content-Type")); + + validateMapResponseModel(output, QUERY_STRING_KEY, QUERY_STRING_NON_ENCODED_VALUE); + } + + @Test + @Ignore("We expect to only receive decoded values from API Gateway") + public void queryString_encoded_echo() { + AwsProxyRequest request = getRequestBuilder("/echo/query-string", "GET") + .json() + .queryString(QUERY_STRING_KEY, QUERY_STRING_ENCODED_VALUE) + .build(); + + AwsProxyResponse output = handler.proxy(request, lambdaContext); + assertEquals(200, output.getStatusCode()); + assertEquals("application/json", output.getMultiValueHeaders().getFirst("Content-Type")); + + validateMapResponseModel(output, QUERY_STRING_KEY, QUERY_STRING_NON_ENCODED_VALUE); + } + + @Test + public void simpleQueryParam_encoding_expectDecodedParam() { + AwsProxyRequest request = getRequestBuilder("/echo/decoded-param", "GET").queryString("param", SIMPLE_ENCODED_PARAM) + .build(); + + AwsProxyResponse resp = handler.proxy(request, lambdaContext); + assertEquals(200, resp.getStatusCode()); + validateSingleValueModel(resp, SIMPLE_ENCODED_PARAM); + } + + @Test + public void jsonQueryParam_encoding_expectDecodedParam() { + AwsProxyRequest request = getRequestBuilder("/echo/decoded-param", "GET").queryString("param", JSON_ENCODED_PARAM) + .build(); + + AwsProxyResponse resp = handler.proxy(request, lambdaContext); + assertEquals(200, resp.getStatusCode()); + validateSingleValueModel(resp, JSON_ENCODED_PARAM); + } + + @Test + public void simpleQueryParam_encoding_expectEncodedParam() { + AwsProxyRequest request = getRequestBuilder("/echo/encoded-param", "GET").queryString("param", SIMPLE_ENCODED_PARAM) + .build(); + String encodedVal = ""; + try { + encodedVal = URLEncoder.encode(SIMPLE_ENCODED_PARAM, "UTF-8"); + } catch (UnsupportedEncodingException e) { + fail("Could not encode parameter value"); + } + AwsProxyResponse resp = handler.proxy(request, lambdaContext); + assertEquals(200, resp.getStatusCode()); + validateSingleValueModel(resp, encodedVal); + } + + @Test + public void jsonQueryParam_encoding_expectEncodedParam() { + AwsProxyRequest request = getRequestBuilder("/echo/encoded-param", "GET").queryString("param", JSON_ENCODED_PARAM) + .build(); + String encodedVal = ""; + try { + encodedVal = URLEncoder.encode(JSON_ENCODED_PARAM, "UTF-8"); + } catch (UnsupportedEncodingException e) { + fail("Could not encode parameter value"); + } + AwsProxyResponse resp = handler.proxy(request, lambdaContext); + assertEquals(200, resp.getStatusCode()); + validateSingleValueModel(resp, encodedVal); + } + + @Test + public void queryParam_encoding_expectFullyEncodedUrl() { + String paramValue = "/+="; + AwsProxyRequest request = getRequestBuilder("/echo/encoded-param", "GET").queryString("param", paramValue).build(); + AwsProxyResponse resp = handler.proxy(request, lambdaContext); + assertNotNull(resp); + assertEquals(resp.getStatusCode(), 200); + validateSingleValueModel(resp, "%2F%2B%3D"); + System.out.println("body:" + resp.getBody()); + } + + @Test + public void pathParam_encoded_routesToCorrectPath() { + String encodedParam = "http%3A%2F%2Fhelloresource.com"; + String path = "/echo/encoded-path/" + encodedParam; + AwsProxyRequest request = getRequestBuilder(path, "GET").build(); + AwsProxyResponse resp = handler.proxy(request, lambdaContext); + assertNotNull(resp); + assertEquals(resp.getStatusCode(), 200); + validateSingleValueModel(resp, encodedParam); + } + + @Test + public void pathParam_encoded_returns404() { + String encodedParam = "http://helloresource.com"; + String path = "/echo/encoded-path/" + encodedParam; + AwsProxyRequest request = getRequestBuilder(path, "GET").build(); + AwsProxyResponse resp = handler.proxy(request, lambdaContext); + assertNotNull(resp); + assertEquals(resp.getStatusCode(), 404); + } + + @Test + @Ignore + public void queryParam_listOfString_expectCorrectLength() { + AwsProxyRequest request = getRequestBuilder("/echo/list-query-string", "GET").queryString("list", "v1,v2,v3").build(); + AwsProxyResponse resp = handler.proxy(request, lambdaContext); + assertNotNull(resp); + assertEquals(resp.getStatusCode(), 200); + validateSingleValueModel(resp, "3"); + } + + @Test + public void multipart_getFileSize_expectCorrectLength() + throws IOException { + AwsProxyRequest request = getRequestBuilder("/echo/file-size", "POST") + .formFilePart("file", "myfile.jpg", FILE_CONTENTS) + //.formFieldPart("name", QUERY_STRING_ENCODED_VALUE) + .build(); + AwsProxyResponse resp = handler.proxy(request, lambdaContext); + assertNotNull(resp); + assertEquals(200, resp.getStatusCode()); + validateSingleValueModel(resp, "" + FILE_CONTENTS.length); + } + + private void validateSingleValueModel(AwsProxyResponse output, String value) { + try { + SingleValueModel response = objectMapper.readValue(output.getBody(), SingleValueModel.class); + assertNotNull(response.getValue()); + assertEquals(value, response.getValue()); + } catch (IOException e) { + fail("Exception while parsing response body: " + e.getMessage()); + e.printStackTrace(); + } + } + + private void validateMapResponseModel(AwsProxyResponse output, String key, String value) { + try { + MapResponseModel response = objectMapper.readValue(output.getBody(), MapResponseModel.class); + assertNotNull(response.getValues().get(key)); + assertEquals(value, response.getValues().get(key)); + } catch (IOException e) { + fail("Exception while parsing response body: " + e.getMessage()); + e.printStackTrace(); + } + } +} diff --git a/extensions/amazon-lambda-resteasy/runtime/src/test/java/io/quarkus/amazon/lambda/resteasy/adapter/test/model/MapResponseModel.java b/extensions/amazon-lambda-resteasy/runtime/src/test/java/io/quarkus/amazon/lambda/resteasy/adapter/test/model/MapResponseModel.java new file mode 100644 index 0000000000000..2d2d019e6e802 --- /dev/null +++ b/extensions/amazon-lambda-resteasy/runtime/src/test/java/io/quarkus/amazon/lambda/resteasy/adapter/test/model/MapResponseModel.java @@ -0,0 +1,39 @@ +/* + * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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.amazon.lambda.resteasy.adapter.test.model; + +import java.util.HashMap; +import java.util.Map; + +/** + * Request/response model + */ +public class MapResponseModel { + private Map values; + + public MapResponseModel() { + this.values = new HashMap<>(); + } + + public void addValue(String key, String value) { + this.values.put(key, value); + } + + public Map getValues() { + return values; + } + + public void setValues(Map values) { + this.values = values; + } +} \ No newline at end of file diff --git a/extensions/amazon-lambda-resteasy/runtime/src/test/java/io/quarkus/amazon/lambda/resteasy/adapter/test/model/SingleValueModel.java b/extensions/amazon-lambda-resteasy/runtime/src/test/java/io/quarkus/amazon/lambda/resteasy/adapter/test/model/SingleValueModel.java new file mode 100644 index 0000000000000..e73bc77619c25 --- /dev/null +++ b/extensions/amazon-lambda-resteasy/runtime/src/test/java/io/quarkus/amazon/lambda/resteasy/adapter/test/model/SingleValueModel.java @@ -0,0 +1,28 @@ +/* + * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file 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.amazon.lambda.resteasy.adapter.test.model; + +/** + * Request/response model + */ +public class SingleValueModel { + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} \ No newline at end of file diff --git a/extensions/amazon-lambda-resteasy/runtime/src/test/java/io/quarkus/amazon/lambda/resteasy/adapter/test/provider/CustomExceptionMapper.java b/extensions/amazon-lambda-resteasy/runtime/src/test/java/io/quarkus/amazon/lambda/resteasy/adapter/test/provider/CustomExceptionMapper.java new file mode 100644 index 0000000000000..6b0d054a2e3de --- /dev/null +++ b/extensions/amazon-lambda-resteasy/runtime/src/test/java/io/quarkus/amazon/lambda/resteasy/adapter/test/provider/CustomExceptionMapper.java @@ -0,0 +1,22 @@ +package io.quarkus.amazon.lambda.resteasy.adapter.test.provider; + +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; + +@Provider +public class CustomExceptionMapper implements ExceptionMapper { + + public CustomExceptionMapper() { + System.out.println("Starting custom exception mapper"); + } + + @Override + public Response toResponse(UnsupportedOperationException throwable) { + // This class was modified a bit to work properly with RESTEasy + return Response.status(Response.Status.NOT_IMPLEMENTED) + .entity(throwable.getMessage()) + .type(MediaType.TEXT_PLAIN).build(); + } +} diff --git a/extensions/amazon-lambda-resteasy/runtime/src/test/java/io/quarkus/amazon/lambda/resteasy/adapter/test/provider/ServletRequestFilter.java b/extensions/amazon-lambda-resteasy/runtime/src/test/java/io/quarkus/amazon/lambda/resteasy/adapter/test/provider/ServletRequestFilter.java new file mode 100644 index 0000000000000..e312942fca3cb --- /dev/null +++ b/extensions/amazon-lambda-resteasy/runtime/src/test/java/io/quarkus/amazon/lambda/resteasy/adapter/test/provider/ServletRequestFilter.java @@ -0,0 +1,24 @@ +package io.quarkus.amazon.lambda.resteasy.adapter.test.provider; + +import java.io.IOException; + +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerRequestFilter; +import javax.ws.rs.core.Context; +import javax.ws.rs.ext.Provider; + +@Provider +public class ServletRequestFilter implements ContainerRequestFilter { + public static final String FILTER_ATTRIBUTE_NAME = "ServletFilter"; + public static final String FILTER_ATTRIBUTE_VALUE = "done"; + + @Context + HttpServletRequest request; + + @Override + public void filter(ContainerRequestContext ctx) throws IOException { + request.setAttribute(FILTER_ATTRIBUTE_NAME, FILTER_ATTRIBUTE_VALUE); + } + +} \ No newline at end of file diff --git a/extensions/amazon-lambda/deployment/pom.xml b/extensions/amazon-lambda/deployment/pom.xml index 658db80a4c77e..aa765886901fa 100644 --- a/extensions/amazon-lambda/deployment/pom.xml +++ b/extensions/amazon-lambda/deployment/pom.xml @@ -11,35 +11,33 @@ ../pom.xml - quarkus-amazon-lambda + quarkus-amazon-lambda-deployment Quarkus - Amazon Lambda - Deployment io.quarkus - quarkus-core + quarkus-core-deployment io.quarkus - quarkus-arc + quarkus-arc-deployment io.quarkus - quarkus-amazon-lambda-runtime + quarkus-amazon-lambda io.rest-assured rest-assured + test io.quarkus quarkus-junit5-internal - - - io.quarkus - quarkus-undertow + test diff --git a/extensions/amazon-lambda/deployment/src/main/java/io/quarkus/amazon/lambda/deployment/AmazonLambdaBuildItem.java b/extensions/amazon-lambda/deployment/src/main/java/io/quarkus/amazon/lambda/deployment/AmazonLambdaBuildItem.java index dba4019014b0f..649d2454d051f 100644 --- a/extensions/amazon-lambda/deployment/src/main/java/io/quarkus/amazon/lambda/deployment/AmazonLambdaBuildItem.java +++ b/extensions/amazon-lambda/deployment/src/main/java/io/quarkus/amazon/lambda/deployment/AmazonLambdaBuildItem.java @@ -1,22 +1,23 @@ package io.quarkus.amazon.lambda.deployment; -import org.jboss.builder.item.MultiBuildItem; +import io.quarkus.builder.item.MultiBuildItem; +import io.quarkus.runtime.RuntimeValue; public final class AmazonLambdaBuildItem extends MultiBuildItem { - private final String className; - private final String path; + private final String handlerClass; + private final RuntimeValue> targetType; - public AmazonLambdaBuildItem(String className, String path) { - this.className = className; - this.path = path; + public AmazonLambdaBuildItem(String handlerClass, RuntimeValue> targetType) { + this.handlerClass = handlerClass; + this.targetType = targetType; } - public String getClassName() { - return className; + public String getHandlerClass() { + return handlerClass; } - public String getPath() { - return path; + public RuntimeValue> getTargetType() { + return targetType; } } diff --git a/extensions/amazon-lambda/deployment/src/main/java/io/quarkus/amazon/lambda/deployment/AmazonLambdaClassNameBuildItem.java b/extensions/amazon-lambda/deployment/src/main/java/io/quarkus/amazon/lambda/deployment/AmazonLambdaClassNameBuildItem.java new file mode 100644 index 0000000000000..9a6ea5e59ed3d --- /dev/null +++ b/extensions/amazon-lambda/deployment/src/main/java/io/quarkus/amazon/lambda/deployment/AmazonLambdaClassNameBuildItem.java @@ -0,0 +1,23 @@ +package io.quarkus.amazon.lambda.deployment; + +import io.quarkus.builder.item.MultiBuildItem; + +public final class AmazonLambdaClassNameBuildItem extends MultiBuildItem { + + private final String className; + + public AmazonLambdaClassNameBuildItem(String className) { + this.className = className; + } + + public String getClassName() { + return className; + } + + @Override + public String toString() { + return "AmazonLambdaBuildItem{" + + "className='" + className + '\'' + + '}'; + } +} 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 1935243b68c84..36f818111a874 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 @@ -1,5 +1,8 @@ package io.quarkus.amazon.lambda.deployment; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; import java.util.ArrayList; import java.util.List; @@ -9,8 +12,8 @@ import com.amazonaws.services.lambda.runtime.RequestHandler; -import io.quarkus.amazon.lambda.runtime.AmazonLambdaServlet; import io.quarkus.amazon.lambda.runtime.AmazonLambdaTemplate; +import io.quarkus.amazon.lambda.runtime.FunctionError; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.deployment.BeanContainerBuildItem; import io.quarkus.deployment.annotations.BuildProducer; @@ -18,24 +21,28 @@ import io.quarkus.deployment.annotations.ExecutionTime; import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.builditem.CombinedIndexBuildItem; +import io.quarkus.deployment.builditem.GeneratedResourceBuildItem; +import io.quarkus.deployment.builditem.ShutdownContextBuildItem; +import io.quarkus.deployment.builditem.substrate.ReflectiveClassBuildItem; import io.quarkus.deployment.builditem.substrate.ReflectiveHierarchyBuildItem; import io.quarkus.deployment.recording.RecorderContext; -import io.quarkus.undertow.deployment.ServletBuildItem; +@SuppressWarnings("unchecked") public final class AmazonLambdaProcessor { + public static final String AWS_LAMBDA_EVENTS_ARCHIVE_MARKERS = "com/amazonaws/services/lambda/runtime/events"; + private static final DotName REQUEST_HANDLER = DotName.createSimple(RequestHandler.class.getName()); - @BuildStep - List discover(CombinedIndexBuildItem combinedIndexBuildItem, + @BuildStep(applicationArchiveMarkers = { AWS_LAMBDA_EVENTS_ARCHIVE_MARKERS }) + List discover(CombinedIndexBuildItem combinedIndexBuildItem, BuildProducer reflectiveClasses) { - List ret = new ArrayList<>(); + List ret = new ArrayList<>(); for (ClassInfo info : combinedIndexBuildItem.getIndex().getAllKnownImplementors(REQUEST_HANDLER)) { final DotName name = info.name(); final String lambda = name.toString(); - final String mapping = name.local(); - ret.add(new AmazonLambdaBuildItem(lambda, mapping)); + ret.add(new AmazonLambdaClassNameBuildItem(lambda)); ClassInfo current = info; boolean done = false; @@ -45,6 +52,7 @@ List discover(CombinedIndexBuildItem combinedIndexBuildIt && method.parameters().size() == 2 && !method.parameters().get(0).name().equals(DotName.createSimple(Object.class.getName()))) { reflectiveClasses.produce(new ReflectiveHierarchyBuildItem(method.parameters().get(0))); + reflectiveClasses.produce(new ReflectiveHierarchyBuildItem(method.returnType())); done = true; break; } @@ -56,30 +64,65 @@ List discover(CombinedIndexBuildItem combinedIndexBuildIt } @BuildStep - List beans(List lambdas) { - List ret = new ArrayList<>(); - for (AmazonLambdaBuildItem i : lambdas) { - ret.add(new AdditionalBeanBuildItem(false, i.getClassName())); + ReflectiveClassBuildItem functionError() { + return new ReflectiveClassBuildItem(true, true, FunctionError.class); + } + + @BuildStep + AdditionalBeanBuildItem beans(List lambdas) { + AdditionalBeanBuildItem.Builder builder = AdditionalBeanBuildItem.builder().setUnremovable(); + for (AmazonLambdaClassNameBuildItem i : lambdas) { + builder.addBeanClass(i.getClassName()); + } + return builder.build(); + } + + @BuildStep + @Record(ExecutionTime.STATIC_INIT) + List process(List items, + RecorderContext context, + AmazonLambdaTemplate template) { + List ret = new ArrayList<>(); + for (AmazonLambdaClassNameBuildItem i : items) { + ret.add(new AmazonLambdaBuildItem(i.getClassName(), + template.discoverParameterTypes( + (Class>) context.classProxy(i.getClassName())))); } return ret; } @BuildStep @Record(ExecutionTime.STATIC_INIT) + void bootstrap(BuildProducer generatedResources) throws IOException { + try (final InputStream stream = getClass().getResourceAsStream("/bootstrap"); + final ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + byte[] bytes = new byte[4096]; + int read; + while ((read = stream.read(bytes)) != -1) { + outputStream.write(bytes, 0, read); + } + generatedResources.produce(new GeneratedResourceBuildItem("bootstrap", outputStream.toByteArray())); + } + } + + @BuildStep + @Record(ExecutionTime.RUNTIME_INIT) public void servlets(List lambdas, - BuildProducer servletProducer, BeanContainerBuildItem beanContainerBuildItem, AmazonLambdaTemplate template, - RecorderContext context) { - - for (AmazonLambdaBuildItem info : lambdas) { - servletProducer.produce(ServletBuildItem.builder(info.getClassName(), AmazonLambdaServlet.class.getName()) - .setLoadOnStartup(1) - .setInstanceFactory(template.lambdaServletInstanceFactory( - (Class) context.classProxy(info.getClassName()), - beanContainerBuildItem.getValue())) - .addMapping("/" + info.getPath()) - .build()); + RecorderContext context, + ShutdownContextBuildItem shutdownContextBuildItem) throws IOException { + + if (lambdas.isEmpty()) { + return; + } else if (lambdas.size() != 1) { + throw new RuntimeException("More than one lambda discovered " + lambdas); } + AmazonLambdaBuildItem lambda = lambdas.get(0); + + template.start((Class>) context.classProxy(lambda.getHandlerClass()), + shutdownContextBuildItem, + lambda.getTargetType(), beanContainerBuildItem.getValue()); + } } diff --git a/extensions/amazon-lambda/deployment/src/main/resources/bootstrap b/extensions/amazon-lambda/deployment/src/main/resources/bootstrap new file mode 100755 index 0000000000000..f793cdcf9484d --- /dev/null +++ b/extensions/amazon-lambda/deployment/src/main/resources/bootstrap @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +RUNNER=$( find . -maxdepth 1 -name '*-runner' ) +if [[ ! -z "$RUNNER" ]] +then + export DISABLE_SIGNAL_HANDLERS=true + $RUNNER +else + java -jar *-runner.jar +fi \ No newline at end of file diff --git a/extensions/amazon-lambda/runtime/pom.xml b/extensions/amazon-lambda/runtime/pom.xml index 07407d21531d2..c2407d62644df 100644 --- a/extensions/amazon-lambda/runtime/pom.xml +++ b/extensions/amazon-lambda/runtime/pom.xml @@ -11,7 +11,7 @@ ../pom.xml - quarkus-amazon-lambda-runtime + quarkus-amazon-lambda Quarkus - Amazon Lambda - Runtime @@ -19,13 +19,17 @@ com.amazonaws aws-lambda-java-core + + com.amazonaws + aws-lambda-java-events + io.quarkus - quarkus-core-runtime + quarkus-core io.quarkus - quarkus-undertow-runtime + quarkus-arc com.fasterxml.jackson.core @@ -36,7 +40,8 @@ - maven-dependency-plugin + io.quarkus + quarkus-bootstrap-maven-plugin maven-compiler-plugin diff --git a/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/AmazonLambdaApi.java b/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/AmazonLambdaApi.java new file mode 100644 index 0000000000000..c8b85751ded8d --- /dev/null +++ b/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/AmazonLambdaApi.java @@ -0,0 +1,74 @@ +package io.quarkus.amazon.lambda.runtime; + +import java.net.MalformedURLException; +import java.net.URL; + +/** + * Various constants and util methods used for communication with the AWS API. + */ +public class AmazonLambdaApi { + + // Response Headers + public static final String LAMBDA_RUNTIME_AWS_REQUEST_ID = "Lambda-Runtime-Aws-Request-Id"; + public static final String LAMBDA_RUNTIME_INVOKED_FUNCTION_ARN = "Lambda-Runtime-Invoked-Function-Arn"; + public static final String LAMBDA_RUNTIME_COGNITO_IDENTITY = "Lambda-Runtime-Cognito-Identity"; + public static final String LAMBDA_RUNTIME_CLIENT_CONTEXT = "Lambda-Runtime-Client-Context"; + public static final String LAMBDA_RUNTIME_DEADLINE_MS = "Lambda-Runtime-Deadline-Ms"; + + // Test API + public static final String QUARKUS_INTERNAL_AWS_LAMBDA_TEST_API = "quarkus-internal.aws-lambda.test-api"; + + // API paths + public static final String API_PROTOCOL = "http://"; + public static final String API_PATH_RUNTIME = "/2018-06-01/runtime/"; + public static final String API_PATH_INVOCATION = API_PATH_RUNTIME + "invocation/"; + public static final String API_PATH_INVOCATION_NEXT = API_PATH_INVOCATION + "next"; + public static final String API_PATH_INIT_ERROR = API_PATH_RUNTIME + "init/error"; + public static final String API_PATH_ERROR = "/error"; + public static final String API_PATH_RESPONSE = "/response"; + + static URL invocationNext() throws MalformedURLException { + return new URL(API_PROTOCOL + runtimeApi() + API_PATH_INVOCATION_NEXT); + } + + static URL invocationError(String requestId) throws MalformedURLException { + return new URL(API_PROTOCOL + runtimeApi() + API_PATH_INVOCATION + requestId + API_PATH_ERROR); + } + + static URL invocationResponse(String requestId) throws MalformedURLException { + return new URL(API_PROTOCOL + runtimeApi() + API_PATH_INVOCATION + requestId + API_PATH_RESPONSE); + } + + static URL initError() throws MalformedURLException { + return new URL(API_PROTOCOL + runtimeApi() + API_PATH_INIT_ERROR); + } + + static String logGroupName() { + return System.getenv("AWS_LAMBDA_LOG_GROUP_NAME"); + } + + static String functionMemorySize() { + return System.getenv("AWS_LAMBDA_FUNCTION_MEMORY_SIZE"); + } + + static String logStreamName() { + return System.getenv("AWS_LAMBDA_LOG_STREAM_NAME"); + } + + static String functionName() { + return System.getenv("AWS_LAMBDA_FUNCTION_NAME"); + } + + static String functionVersion() { + return System.getenv("AWS_LAMBDA_FUNCTION_VERSION"); + } + + private static String runtimeApi() { + String testApi = System.getProperty(QUARKUS_INTERNAL_AWS_LAMBDA_TEST_API); + if (testApi != null) { + return testApi; + } + return System.getenv("AWS_LAMBDA_RUNTIME_API"); + } + +} diff --git a/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/AmazonLambdaContext.java b/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/AmazonLambdaContext.java new file mode 100644 index 0000000000000..a84d7fe0dca5c --- /dev/null +++ b/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/AmazonLambdaContext.java @@ -0,0 +1,121 @@ +package io.quarkus.amazon.lambda.runtime; + +import static io.quarkus.amazon.lambda.runtime.AmazonLambdaApi.LAMBDA_RUNTIME_AWS_REQUEST_ID; +import static io.quarkus.amazon.lambda.runtime.AmazonLambdaApi.LAMBDA_RUNTIME_CLIENT_CONTEXT; +import static io.quarkus.amazon.lambda.runtime.AmazonLambdaApi.LAMBDA_RUNTIME_COGNITO_IDENTITY; +import static io.quarkus.amazon.lambda.runtime.AmazonLambdaApi.LAMBDA_RUNTIME_DEADLINE_MS; +import static io.quarkus.amazon.lambda.runtime.AmazonLambdaApi.LAMBDA_RUNTIME_INVOKED_FUNCTION_ARN; +import static io.quarkus.amazon.lambda.runtime.AmazonLambdaApi.functionMemorySize; +import static io.quarkus.amazon.lambda.runtime.AmazonLambdaApi.functionName; +import static io.quarkus.amazon.lambda.runtime.AmazonLambdaApi.functionVersion; +import static io.quarkus.amazon.lambda.runtime.AmazonLambdaApi.logGroupName; +import static io.quarkus.amazon.lambda.runtime.AmazonLambdaApi.logStreamName; + +import java.io.IOException; +import java.net.HttpURLConnection; + +import com.amazonaws.services.lambda.runtime.ClientContext; +import com.amazonaws.services.lambda.runtime.CognitoIdentity; +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.LambdaLogger; +import com.amazonaws.services.lambda.runtime.LambdaRuntime; +import com.fasterxml.jackson.databind.ObjectReader; + +public class AmazonLambdaContext implements Context { + + private String awsRequestId; + private String logGroupName; + private String logStreamName; + private String functionName; + private String functionVersion; + private String invokedFunctionArn; + private CognitoIdentity cognitoIdentity; + private ClientContext clientContext; + private long runtimeDeadlineMs = 0; + private int memoryLimitInMB; + private LambdaLogger logger; + + public AmazonLambdaContext(HttpURLConnection request, ObjectReader cognitoReader, ObjectReader clientCtxReader) + throws IOException { + awsRequestId = request.getHeaderField(LAMBDA_RUNTIME_AWS_REQUEST_ID); + logGroupName = logGroupName(); + logStreamName = logStreamName(); + functionName = functionName(); + functionVersion = functionVersion(); + invokedFunctionArn = request.getHeaderField(LAMBDA_RUNTIME_INVOKED_FUNCTION_ARN); + + String cognitoIdentityHeader = request.getHeaderField(LAMBDA_RUNTIME_COGNITO_IDENTITY); + if (cognitoIdentityHeader != null) { + cognitoIdentity = cognitoReader.readValue(cognitoIdentityHeader); + } + + String clientContextHeader = request.getHeaderField(LAMBDA_RUNTIME_CLIENT_CONTEXT); + if (clientContextHeader != null) { + clientContext = clientCtxReader.readValue(clientContextHeader); + } + + String functionMemorySize = functionMemorySize(); + memoryLimitInMB = functionMemorySize != null ? Integer.valueOf(functionMemorySize) : 0; + + String runtimeDeadline = request.getHeaderField(LAMBDA_RUNTIME_DEADLINE_MS); + if (runtimeDeadline != null) { + runtimeDeadlineMs = Long.valueOf(runtimeDeadline); + } + logger = LambdaRuntime.getLogger(); + } + + @Override + public String getAwsRequestId() { + return awsRequestId; + } + + @Override + public String getLogGroupName() { + return logGroupName; + } + + @Override + public String getLogStreamName() { + return logStreamName; + } + + @Override + public String getFunctionName() { + return functionName; + } + + @Override + public String getFunctionVersion() { + return functionVersion; + } + + @Override + public String getInvokedFunctionArn() { + return invokedFunctionArn; + } + + @Override + public CognitoIdentity getIdentity() { + return cognitoIdentity; + } + + @Override + public ClientContext getClientContext() { + return clientContext; + } + + @Override + public int getRemainingTimeInMillis() { + return (int) (runtimeDeadlineMs - Math.round(System.nanoTime() / 1_000_000d)); + } + + @Override + public int getMemoryLimitInMB() { + return memoryLimitInMB; + } + + @Override + public LambdaLogger getLogger() { + return logger; + } +} diff --git a/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/AmazonLambdaServlet.java b/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/AmazonLambdaServlet.java deleted file mode 100644 index a1f97dfefbe3d..0000000000000 --- a/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/AmazonLambdaServlet.java +++ /dev/null @@ -1,55 +0,0 @@ -package io.quarkus.amazon.lambda.runtime; - -import java.io.IOException; -import java.util.stream.Collectors; - -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import com.amazonaws.services.lambda.runtime.RequestHandler; -import com.fasterxml.jackson.databind.ObjectMapper; - -import io.quarkus.arc.runtime.BeanContainer; - -public class AmazonLambdaServlet extends HttpServlet { - private static final String ALLOWED_METHODS = "POST, OPTIONS"; - private final BeanContainer.Instance handler; - private final ObjectMapper mapper = new ObjectMapper(); - private final Class targetType; - - public AmazonLambdaServlet(final BeanContainer.Instance instance, Class targetType) { - this.handler = instance; - this.targetType = targetType; - } - - @Override - protected void doOptions(HttpServletRequest req, HttpServletResponse resp) { - addCorsResponseHeaders(resp); - resp.addHeader("Allow", ALLOWED_METHODS); - } - - @Override - @SuppressWarnings("unchecked") - protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { - addCorsResponseHeaders(resp); - String body = req.getReader().lines().collect(Collectors.joining(System.lineSeparator())); - - final Object value = mapper.readValue(body, targetType); - - resp.getOutputStream().print(mapper.writeValueAsString(handler.get().handleRequest(value, null))); - } - - @Override - public void destroy() { - handler.close(); - } - - private static void addCorsResponseHeaders(HttpServletResponse response) { - response.addHeader("Access-Control-Allow-Origin", "*"); - response.addHeader("Access-Control-Allow-Credentials", "true"); - response.addHeader("Access-Control-Allow-Methods", ALLOWED_METHODS); - response.addHeader("Access-Control-Allow-Headers", "Content-Type, Authorization"); - response.addHeader("Access-Control-Max-Age", "86400"); - } -} diff --git a/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/AmazonLambdaTemplate.java b/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/AmazonLambdaTemplate.java index 82fb847df697d..f4df78eb555f0 100644 --- a/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/AmazonLambdaTemplate.java +++ b/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/AmazonLambdaTemplate.java @@ -1,44 +1,123 @@ package io.quarkus.amazon.lambda.runtime; +import java.io.IOException; import java.lang.reflect.Method; -import java.util.Objects; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.concurrent.atomic.AtomicBoolean; +import org.jboss.logging.Logger; + +import com.amazonaws.services.lambda.runtime.ClientContext; +import com.amazonaws.services.lambda.runtime.CognitoIdentity; import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectReader; import io.quarkus.arc.runtime.BeanContainer; +import io.quarkus.runtime.Application; +import io.quarkus.runtime.RuntimeValue; +import io.quarkus.runtime.ShutdownContext; import io.quarkus.runtime.annotations.Template; -import io.undertow.servlet.api.InstanceFactory; -import io.undertow.servlet.api.InstanceHandle; @Template public class AmazonLambdaTemplate { - public InstanceFactory lambdaServletInstanceFactory(Class handlerClass, + private static final Logger log = Logger.getLogger(AmazonLambdaTemplate.class); + + @SuppressWarnings("rawtypes") + public void start(Class> handlerClass, + ShutdownContext context, + RuntimeValue> handlerType, BeanContainer beanContainer) { - BeanContainer.Factory factory = beanContainer.instanceFactory(handlerClass); - Class paramType = discoverParameterTypes(handlerClass); - Objects.requireNonNull(paramType, "Unable to discover parameter type"); - return new InstanceFactory() { + + RequestHandler handler = beanContainer.instance(handlerClass); + + final ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + AtomicBoolean running = new AtomicBoolean(true); + ObjectReader objectReader = mapper.readerFor(handlerType.getValue()); + ObjectReader cognitoIdReader = mapper.readerFor(CognitoIdentity.class); + ObjectReader clientCtxReader = mapper.readerFor(ClientContext.class); + + context.addShutdownTask(new Runnable() { @Override - public InstanceHandle createInstance() throws InstantiationException { - BeanContainer.Instance instance = factory.create(); - AmazonLambdaServlet servlet = new AmazonLambdaServlet(instance, paramType); - return new InstanceHandle() { - @Override - public AmazonLambdaServlet getInstance() { - return servlet; + public void run() { + running.set(false); + } + }); + + Thread t = new Thread(new Runnable() { + @SuppressWarnings("unchecked") + @Override + public void run() { + + try { + URL requestUrl = AmazonLambdaApi.invocationNext(); + while (running.get()) { + + HttpURLConnection requestConnection = (HttpURLConnection) requestUrl.openConnection(); + try { + String requestId = requestConnection.getHeaderField(AmazonLambdaApi.LAMBDA_RUNTIME_AWS_REQUEST_ID); + Object response; + try { + Object val = objectReader.readValue(requestConnection.getInputStream()); + response = handler.handleRequest(val, + new AmazonLambdaContext(requestConnection, cognitoIdReader, clientCtxReader)); + } catch (Exception e) { + log.error("Failed to run lambda", e); + + postResponse(AmazonLambdaApi.invocationError(requestId), + new FunctionError(e.getClass().getName(), e.getMessage()), mapper); + continue; + } + + postResponse(AmazonLambdaApi.invocationResponse(requestId), response, mapper); + } catch (Exception e) { + log.error("Error running lambda", e); + Application app = Application.currentApplication(); + if (app != null) { + app.stop(); + } + return; + } finally { + requestConnection.getInputStream().close(); + } + } - @Override - public void release() { - instance.close(); + } catch (Exception e) { + try { + log.error("Lambda init error", e); + postResponse(AmazonLambdaApi.initError(), new FunctionError(e.getClass().getName(), e.getMessage()), + mapper); + } catch (Exception ex) { + log.error("Failed to report init error", ex); + } finally { + //our main loop is done, time to shutdown + Application app = Application.currentApplication(); + if (app != null) { + app.stop(); + } } - }; + } } - }; + }, "Lambda Thread"); + t.start(); + } - private static Class discoverParameterTypes(Class handlerClass) { + private void postResponse(URL url, Object response, ObjectMapper mapper) throws IOException { + HttpURLConnection responseConnection = (HttpURLConnection) url.openConnection(); + responseConnection.setDoOutput(true); + responseConnection.setRequestMethod("POST"); + mapper.writeValue(responseConnection.getOutputStream(), response); + while (responseConnection.getInputStream().read() != -1) { + // Read data + } + } + + public RuntimeValue> discoverParameterTypes(Class> handlerClass) { final Method[] methods = handlerClass.getMethods(); Method method = null; for (int i = 0; i < methods.length && method == null; i++) { @@ -52,6 +131,7 @@ private static Class discoverParameterTypes(Class h if (method == null) { method = methods[0]; } - return method.getParameterTypes()[0]; + return new RuntimeValue<>(method.getParameterTypes()[0]); } + } diff --git a/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/FunctionError.java b/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/FunctionError.java new file mode 100644 index 0000000000000..0c51b2011ba1e --- /dev/null +++ b/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/FunctionError.java @@ -0,0 +1,33 @@ +package io.quarkus.amazon.lambda.runtime; + +public class FunctionError { + + private String errorType; + private String errorMessage; + + public FunctionError(String errorType, String errorMessage) { + this.errorType = errorType; + this.errorMessage = errorMessage; + } + + public FunctionError() { + } + + public String getErrorType() { + return errorType; + } + + public FunctionError setErrorType(String errorType) { + this.errorType = errorType; + return this; + } + + public String getErrorMessage() { + return errorMessage; + } + + public FunctionError setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + return this; + } +} diff --git a/extensions/arc/deployment/pom.xml b/extensions/arc/deployment/pom.xml index 28d943df9c7f4..b35647038922e 100644 --- a/extensions/arc/deployment/pom.xml +++ b/extensions/arc/deployment/pom.xml @@ -26,23 +26,23 @@ 4.0.0 - quarkus-arc + quarkus-arc-deployment Quarkus - ArC - Deployment io.quarkus - quarkus-core + quarkus-core-deployment io.quarkus - quarkus-arc-runtime + quarkus-arc io.quarkus.arc arc-processor - + io.quarkus quarkus-junit5-internal diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/AdditionalBeanBuildItem.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/AdditionalBeanBuildItem.java index de1a42edbe161..93d5cc3c53d55 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/AdditionalBeanBuildItem.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/AdditionalBeanBuildItem.java @@ -16,51 +16,127 @@ package io.quarkus.arc.deployment; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; -import org.jboss.builder.item.MultiBuildItem; +import org.jboss.jandex.DotName; + +import io.quarkus.builder.item.MultiBuildItem; /** - * This build item is used to specify additional bean classes to be analyzed. + * This build item is used to specify one or more additional bean classes to be analyzed. *

* By default, the resulting beans may be removed if they are considered unused and {@link ArcConfig#removeUnusedBeans} is * enabled. */ public final class AdditionalBeanBuildItem extends MultiBuildItem { + public static Builder builder() { + return new Builder(); + } + + /** + * Convenient factory method to create an unremovable build item for a single bean class. + * + * @param beanClass + * @return a new build item + */ + public static AdditionalBeanBuildItem unremovableOf(Class beanClass) { + return new AdditionalBeanBuildItem(Collections.singletonList(beanClass.getName()), false, null); + } + private final List beanClasses; private final boolean removable; + private final DotName defaultScope; public AdditionalBeanBuildItem(String... beanClasses) { - this(true, beanClasses); - } - - public AdditionalBeanBuildItem(boolean removable, String... beanClasses) { - this(Arrays.asList(beanClasses), removable); + this(Arrays.asList(beanClasses), true, null); } public AdditionalBeanBuildItem(Class... beanClasses) { - this(true, beanClasses); + this(Arrays.stream(beanClasses).map(Class::getName).collect(Collectors.toList()), true, null); } - public AdditionalBeanBuildItem(boolean removable, Class... beanClasses) { - this(Arrays.stream(beanClasses).map(Class::getName).collect(Collectors.toList()), removable); - } - - AdditionalBeanBuildItem(List beanClasses, boolean removable) { + AdditionalBeanBuildItem(List beanClasses, boolean removable, DotName defaultScope) { this.beanClasses = beanClasses; this.removable = removable; + this.defaultScope = defaultScope; } public List getBeanClasses() { return Collections.unmodifiableList(beanClasses); } + public boolean contains(String beanClass) { + return beanClasses.contains(beanClass); + } + public boolean isRemovable() { return removable; } + public DotName getDefaultScope() { + return defaultScope; + } + + public static class Builder { + + private final List beanClasses; + private boolean removable = true; + private DotName defaultScope; + + public Builder() { + this.beanClasses = new ArrayList<>(); + } + + public Builder addBeanClasses(Class... beanClasses) { + Arrays.stream(beanClasses).map(Class::getName).forEach(this.beanClasses::add); + return this; + } + + public Builder addBeanClasses(String... beanClasses) { + Collections.addAll(this.beanClasses, beanClasses); + return this; + } + + public Builder addBeanClasses(Collection beanClasses) { + this.beanClasses.addAll(beanClasses); + return this; + } + + public Builder addBeanClass(String beanClass) { + this.beanClasses.add(beanClass); + return this; + } + + public Builder addBeanClass(Class beanClass) { + this.beanClasses.add(beanClass.getName()); + return this; + } + + public Builder setRemovable() { + this.removable = true; + return this; + } + + public Builder setUnremovable() { + this.removable = false; + return this; + } + + public Builder setDefaultScope(DotName defaultScope) { + this.defaultScope = defaultScope; + return this; + } + + public AdditionalBeanBuildItem build() { + return new AdditionalBeanBuildItem(new ArrayList<>(beanClasses), removable, defaultScope); + } + + } + } diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/AdditionalStereotypeBuildItem.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/AdditionalStereotypeBuildItem.java index e2dd47637483b..a399d2bf9e540 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/AdditionalStereotypeBuildItem.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/AdditionalStereotypeBuildItem.java @@ -19,10 +19,11 @@ import java.util.Collection; import java.util.Map; -import org.jboss.builder.item.MultiBuildItem; import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.DotName; +import io.quarkus.builder.item.MultiBuildItem; + /** * A map of additional stereotype classes to their instances that we want to process. */ diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/AnnotationsTransformerBuildItem.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/AnnotationsTransformerBuildItem.java index 3e4b4a0f11ff7..b32af62191436 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/AnnotationsTransformerBuildItem.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/AnnotationsTransformerBuildItem.java @@ -1,8 +1,7 @@ package io.quarkus.arc.deployment; -import org.jboss.builder.item.MultiBuildItem; - import io.quarkus.arc.processor.AnnotationsTransformer; +import io.quarkus.builder.item.MultiBuildItem; public final class AnnotationsTransformerBuildItem extends MultiBuildItem { diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcAnnotationProcessor.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcAnnotationProcessor.java index 80a2e0d92c6d8..a77e119ccb6d0 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcAnnotationProcessor.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcAnnotationProcessor.java @@ -19,26 +19,23 @@ import static io.quarkus.deployment.annotations.ExecutionTime.STATIC_INIT; import java.io.IOException; -import java.util.ArrayList; import java.util.Collection; 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.Predicate; import java.util.stream.Collectors; -import javax.enterprise.context.Dependent; import javax.inject.Inject; import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationTarget; -import org.jboss.jandex.CompositeIndex; +import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; import org.jboss.jandex.FieldInfo; import org.jboss.jandex.IndexView; -import org.jboss.jandex.Indexer; import org.jboss.jandex.MethodInfo; import org.jboss.logging.Logger; @@ -48,10 +45,11 @@ import io.quarkus.arc.processor.AnnotationsTransformer; import io.quarkus.arc.processor.BeanDefiningAnnotation; import io.quarkus.arc.processor.BeanDeployment; +import io.quarkus.arc.processor.BeanInfo; import io.quarkus.arc.processor.BeanProcessor; -import io.quarkus.arc.processor.BeanProcessor.Builder; import io.quarkus.arc.processor.ReflectionRegistration; import io.quarkus.arc.processor.ResourceOutput; +import io.quarkus.arc.runtime.AdditionalBean; import io.quarkus.arc.runtime.ArcDeploymentTemplate; import io.quarkus.arc.runtime.BeanContainer; import io.quarkus.arc.runtime.LifecycleEventRunner; @@ -60,19 +58,21 @@ import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.builditem.ApplicationArchivesBuildItem; +import io.quarkus.deployment.builditem.ApplicationClassPredicateBuildItem; import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.deployment.builditem.GeneratedClassBuildItem; import io.quarkus.deployment.builditem.GeneratedResourceBuildItem; import io.quarkus.deployment.builditem.ShutdownContextBuildItem; -import io.quarkus.deployment.builditem.substrate.ReflectiveClassBuildItem; +import io.quarkus.deployment.builditem.TestClassPredicateBuildItem; import io.quarkus.deployment.builditem.substrate.ReflectiveFieldBuildItem; import io.quarkus.deployment.builditem.substrate.ReflectiveMethodBuildItem; -import io.quarkus.deployment.index.IndexingUtil; public class ArcAnnotationProcessor { private static final Logger log = Logger.getLogger("io.quarkus.arc.deployment.processor"); + static final DotName ADDITIONAL_BEAN = DotName.createSimple(AdditionalBean.class.getName()); + @Inject BeanArchiveIndexBuildItem beanArchiveIndex; @@ -82,9 +82,6 @@ public class ArcAnnotationProcessor { @Inject BuildProducer generatedResource; - @Inject - BuildProducer reflectiveClass; - @Inject List additionalBeans; @@ -97,6 +94,9 @@ public class ArcAnnotationProcessor { @Inject List beanRegistrars; + @Inject + List contextRegistrars; + @Inject List beanDeploymentValidators; @@ -109,57 +109,32 @@ public class ArcAnnotationProcessor { @Inject List removalExclusions; + @Inject + Optional testClassPredicate; + /** * The configuration for ArC, the CDI-based injection facility. */ ArcConfig arc; - @BuildStep - public FullArchiveIndexBuildItem buildArchive(List generatedBeans) { - List additionalBeans = new ArrayList<>(); - for (AdditionalBeanBuildItem i : this.additionalBeans) { - additionalBeans.addAll(i.getBeanClasses()); - } - additionalBeans.add(LifecycleEventRunner.class.getName()); - - // Index bean classes registered by quarkus - Indexer indexer = new Indexer(); - Set additionalIndex = new HashSet<>(); - for (String beanClass : additionalBeans) { - IndexingUtil.indexClass(beanClass, indexer, beanArchiveIndex.getIndex(), additionalIndex, - ArcAnnotationProcessor.class.getClassLoader()); - } - Set generatedClassNames = new HashSet<>(); - for (GeneratedBeanBuildItem beanClass : generatedBeans) { - IndexingUtil.indexClass(beanClass.getName(), indexer, beanArchiveIndex.getIndex(), additionalIndex, - ArcAnnotationProcessor.class.getClassLoader(), beanClass.getData()); - generatedClassNames.add(DotName.createSimple(beanClass.getName().replace('/', '.'))); - generatedClass.produce(new GeneratedClassBuildItem(true, beanClass.getName(), beanClass.getData())); - } - - final IndexView index = BeanProcessor.addBuiltinClasses( - CompositeIndex.create(indexer.complete(), beanArchiveIndex.getIndex())); - return new FullArchiveIndexBuildItem(index, generatedClassNames, additionalBeans); - } - @BuildStep(providesCapabilities = Capabilities.CDI_ARC, applicationArchiveMarkers = { "META-INF/beans.xml", "META-INF/services/javax.enterprise.inject.spi.Extension" }) @Record(STATIC_INIT) public BeanContainerBuildItem build(ArcDeploymentTemplate arcTemplate, List beanContainerListenerBuildItems, - ApplicationArchivesBuildItem applicationArchivesBuildItem, FullArchiveIndexBuildItem fullArchiveIndexBuildItem, - List annotationTransformers, ShutdownContextBuildItem shutdown, - List additionalStereotypeBuildItems, - BuildProducer feature, - List contextRegistrars) + ApplicationArchivesBuildItem applicationArchivesBuildItem, + List annotationTransformers, + ShutdownContextBuildItem shutdown, List additionalStereotypeBuildItems, + List applicationClassPredicates, + BuildProducer feature) throws Exception { feature.produce(new FeatureBuildItem(FeatureBuildItem.CDI)); - List additionalBeans = fullArchiveIndexBuildItem.getAdditionalBeans(); - Set generatedClassNames = fullArchiveIndexBuildItem.getGeneratedClassNames(); - IndexView index = fullArchiveIndexBuildItem.getIndex(); - Builder builder = BeanProcessor.builder(); + List additionalBeans = beanArchiveIndex.getAdditionalBeans(); + Set generatedClassNames = beanArchiveIndex.getGeneratedClassNames(); + IndexView index = beanArchiveIndex.getIndex(); + BeanProcessor.Builder builder = BeanProcessor.builder(); builder.setApplicationClassPredicate(new Predicate() { @Override public boolean test(DotName dotName) { @@ -169,6 +144,14 @@ public boolean test(DotName dotName) { if (generatedClassNames.contains(dotName)) { return true; } + if (!applicationClassPredicates.isEmpty()) { + String className = dotName.toString(); + for (ApplicationClassPredicateBuildItem predicate : applicationClassPredicates) { + if (predicate.test(className)) { + return true; + } + } + } return false; } }); @@ -181,25 +164,35 @@ public boolean appliesTo(AnnotationTarget.Kind kind) { @Override public void transform(TransformationContext transformationContext) { - if (additionalBeans.contains(transformationContext.getTarget().asClass().name().toString())) { - transformationContext.transform().add(Dependent.class).done(); + ClassInfo beanClass = transformationContext.getTarget().asClass(); + String beanClassName = beanClass.name().toString(); + if (additionalBeans.contains(beanClassName)) { + // This is an additional bean - try to determine the default scope + DotName defaultScope = ArcAnnotationProcessor.this.additionalBeans.stream() + .filter(ab -> ab.contains(beanClassName)).findFirst().map(AdditionalBeanBuildItem::getDefaultScope) + .orElse(null); + if (defaultScope == null && !beanClass.annotations().containsKey(ADDITIONAL_BEAN)) { + // Add special stereotype so that @Dependent is automatically used even if no scope is declared + transformationContext.transform().add(ADDITIONAL_BEAN).done(); + } else { + transformationContext.transform().add(defaultScope).done(); + } } } }); builder.setIndex(index); - - builder.setAdditionalBeanDefiningAnnotations(additionalBeanDefiningAnnotations.stream() - .map((s) -> new BeanDefiningAnnotation(s.getName(), s.getDefaultScope())) - .collect(Collectors.toList())); + List beanDefiningAnnotations = additionalBeanDefiningAnnotations.stream() + .map((s) -> new BeanDefiningAnnotation(s.getName(), s.getDefaultScope())).collect(Collectors.toList()); + beanDefiningAnnotations.add(new BeanDefiningAnnotation(ADDITIONAL_BEAN, null)); + builder.setAdditionalBeanDefiningAnnotations(beanDefiningAnnotations); final Map> additionalStereotypes = new HashMap<>(); for (final AdditionalStereotypeBuildItem item : additionalStereotypeBuildItems) { additionalStereotypes.putAll(item.getStereotypes()); } builder.setAdditionalStereotypes(additionalStereotypes); builder.setSharedAnnotationLiterals(true); - builder.addResourceAnnotations(resourceAnnotations.stream() - .map(ResourceAnnotationBuildItem::getName) - .collect(Collectors.toList())); + builder.addResourceAnnotations( + resourceAnnotations.stream().map(ResourceAnnotationBuildItem::getName).collect(Collectors.toList())); builder.setReflectionRegistration(new ReflectionRegistration() { @Override public void registerMethod(MethodInfo methodInfo) { @@ -259,22 +252,23 @@ public void writeResource(Resource resource) throws IOException { for (UnremovableBeanBuildItem exclusion : removalExclusions) { builder.addRemovalExclusion(exclusion.getPredicate()); } + if (testClassPredicate.isPresent()) { + builder.addRemovalExclusion(new Predicate() { + @Override + public boolean test(BeanInfo bean) { + return testClassPredicate.get().getPredicate().test(bean.getBeanClass().toString()); + } + }); + } BeanProcessor beanProcessor = builder.build(); BeanDeployment beanDeployment = beanProcessor.process(); ArcContainer container = arcTemplate.getContainer(shutdown); - BeanContainer beanContainer = arcTemplate.initBeanContainer( - container, - beanContainerListenerBuildItems - .stream() - .map(BeanContainerListenerBuildItem::getBeanContainerListener) + BeanContainer beanContainer = arcTemplate.initBeanContainer(container, + beanContainerListenerBuildItems.stream().map(BeanContainerListenerBuildItem::getBeanContainerListener) .collect(Collectors.toList()), - beanDeployment - .getRemovedBeans() - .stream() - .flatMap(b -> b.getTypes().stream()) - .map(t -> t.name().toString()) + beanDeployment.getRemovedBeans().stream().flatMap(b -> b.getTypes().stream()).map(t -> t.name().toString()) .collect(Collectors.toSet())); return new BeanContainerBuildItem(beanContainer); diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcConfig.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcConfig.java index d86c6531d2dd1..1e8f563d5b3af 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcConfig.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcConfig.java @@ -27,4 +27,11 @@ public class ArcConfig { @ConfigItem(defaultValue = "true") public boolean removeUnusedBeans; + /** + * If set to true {@code @Inject} is automatically added to all non-static fields that are annotated with + * one of the annotations defined by {@link AutoInjectAnnotationBuildItem}. + */ + @ConfigItem(defaultValue = "true") + public boolean autoInjectFields; + } diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcTestRequestScopeProvider.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcTestRequestScopeProvider.java new file mode 100644 index 0000000000000..08e68fb13cd95 --- /dev/null +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcTestRequestScopeProvider.java @@ -0,0 +1,21 @@ +package io.quarkus.arc.deployment; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.ArcContainer; +import io.quarkus.deployment.test.TestScopeSetup; + +public class ArcTestRequestScopeProvider implements TestScopeSetup { + + @Override + public void setup() { + ArcContainer container = Arc.container(); + container.requestContext().activate(); + + } + + @Override + public void tearDown() { + ArcContainer container = Arc.container(); + container.requestContext().terminate(); + } +} diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcTestResourceProvider.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcTestResourceProvider.java index 117da1e81a415..6f899598d8c94 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcTestResourceProvider.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcTestResourceProvider.java @@ -2,17 +2,17 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.List; +import java.util.HashSet; import java.util.Set; -import javax.enterprise.context.spi.CreationalContext; -import javax.enterprise.inject.spi.Bean; import javax.enterprise.inject.spi.BeanManager; import javax.inject.Inject; import javax.inject.Qualifier; +import org.eclipse.microprofile.config.inject.ConfigProperty; + import io.quarkus.arc.Arc; +import io.quarkus.arc.CurrentInjectionPointProvider.InjectionPointImpl; import io.quarkus.deployment.test.TestResourceProvider; public class ArcTestResourceProvider implements TestResourceProvider { @@ -20,34 +20,37 @@ public class ArcTestResourceProvider implements TestResourceProvider { @Override public void inject(Object test) { Class c = test.getClass(); + BeanManager beanManager = Arc.container().beanManager(); while (c != Object.class) { - for (Field f : c.getDeclaredFields()) { - if (f.isAnnotationPresent(Inject.class)) { + for (Field field : c.getDeclaredFields()) { + if (field.isAnnotationPresent(Inject.class) || field.isAnnotationPresent(ConfigProperty.class)) { try { - BeanManager beanManager = Arc.container().beanManager(); - List qualifiers = new ArrayList<>(); - for (Annotation a : f.getAnnotations()) { + Set qualifiers = new HashSet<>(); + Set annotations = new HashSet<>(); + for (Annotation a : field.getAnnotations()) { + annotations.add(a); if (a.annotationType().isAnnotationPresent(Qualifier.class)) { qualifiers.add(a); } } - Set> beans = beanManager.getBeans(f.getType(), - qualifiers.toArray(new Annotation[qualifiers.size()])); - Bean bean = beanManager.resolve(beans); - CreationalContext ctx = beanManager.createCreationalContext(bean); - Object instance = beanManager.getReference(bean, f.getType(), ctx); - f.setAccessible(true); + Object instance = beanManager.getInjectableReference( + new InjectionPointImpl(field.getGenericType(), field.getGenericType(), + qualifiers, null, annotations, field, -1), + beanManager.createCreationalContext(null)); + // Set the field value + field.setAccessible(true); try { - f.set(test, instance); + field.set(test, instance); } catch (IllegalAccessException e) { throw new RuntimeException(e); } } catch (Throwable t) { - throw new RuntimeException("Failed to inject field " + f, t); + throw new RuntimeException("Failed to inject field " + field, t); } } } c = c.getSuperclass(); } } + } diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/AutoInjectAnnotationBuildItem.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/AutoInjectAnnotationBuildItem.java new file mode 100644 index 0000000000000..b19a1a97fac16 --- /dev/null +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/AutoInjectAnnotationBuildItem.java @@ -0,0 +1,34 @@ +package io.quarkus.arc.deployment; + +import java.util.Arrays; +import java.util.List; + +import javax.inject.Inject; + +import org.jboss.jandex.DotName; + +import io.quarkus.builder.item.MultiBuildItem; + +/** + * This build item can be used to define annotations that will turn a non-static field into an injection point even if no + * {@link Inject} is declared. + * + * @see AutoInjectFieldProcessor + */ +public final class AutoInjectAnnotationBuildItem extends MultiBuildItem { + + private final List annotationNames; + + public AutoInjectAnnotationBuildItem(DotName... annotationNames) { + this.annotationNames = Arrays.asList(annotationNames); + } + + public AutoInjectAnnotationBuildItem(List annotationNames) { + this.annotationNames = annotationNames; + } + + public List getAnnotationNames() { + return annotationNames; + } + +} diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/AutoInjectFieldProcessor.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/AutoInjectFieldProcessor.java new file mode 100644 index 0000000000000..4c9c68c27083b --- /dev/null +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/AutoInjectFieldProcessor.java @@ -0,0 +1,74 @@ +package io.quarkus.arc.deployment; + +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationTarget; +import org.jboss.jandex.DotName; +import org.jboss.jandex.FieldInfo; +import org.jboss.logging.Logger; + +import io.quarkus.arc.processor.AnnotationsTransformer; +import io.quarkus.arc.processor.DotNames; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; + +public class AutoInjectFieldProcessor { + + private static final Logger LOGGER = Logger.getLogger(AutoInjectFieldProcessor.class); + + @BuildStep + void autoInjectQualifiers(BeanArchiveIndexBuildItem beanArchiveIndex, + BuildProducer autoInjectAnnotations) { + List qualifiers = new ArrayList<>(); + for (AnnotationInstance qualifier : beanArchiveIndex.getIndex().getAnnotations(DotNames.QUALIFIER)) { + qualifiers.add(qualifier.target().asClass().name()); + } + autoInjectAnnotations.produce(new AutoInjectAnnotationBuildItem(qualifiers)); + } + + /** + * Uses {@link AnnotationsTransformer} to automatically add {@code @Inject} to all non-static fields that are annotated with + * one of the specified annotations. + */ + @BuildStep + void annotationTransformer(ArcConfig config, List autoInjectAnnotations, + BuildProducer annotationsTransformer) throws Exception { + if (!config.autoInjectFields) { + return; + } + Set annotations = new HashSet<>(); + for (AutoInjectAnnotationBuildItem autoInjectAnnotation : autoInjectAnnotations) { + annotations.addAll(autoInjectAnnotation.getAnnotationNames()); + } + if (annotations.isEmpty()) { + return; + } + LOGGER.debugf("Add missing @Inject to fields annotated with %s", annotations); + annotationsTransformer.produce(new AnnotationsTransformerBuildItem(new AnnotationsTransformer() { + @Override + public boolean appliesTo(AnnotationTarget.Kind kind) { + return kind == AnnotationTarget.Kind.FIELD; + } + + @Override + public void transform(TransformationContext transformationContext) { + FieldInfo field = transformationContext.getTarget().asField(); + if (Modifier.isStatic(field.flags()) || field.hasAnnotation(DotNames.INJECT)) { + return; + } + for (DotName annotation : annotations) { + if (field.hasAnnotation(annotation)) { + transformationContext.transform().add(DotNames.INJECT).done(); + return; + } + } + } + })); + } + +} diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanArchiveIndexBuildItem.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanArchiveIndexBuildItem.java index 5c4ee48bde4ef..2416117fe53f3 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanArchiveIndexBuildItem.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanArchiveIndexBuildItem.java @@ -16,19 +16,37 @@ package io.quarkus.arc.deployment; -import org.jboss.builder.item.SimpleBuildItem; +import java.util.List; +import java.util.Set; + +import org.jboss.jandex.DotName; import org.jboss.jandex.IndexView; +import io.quarkus.builder.item.SimpleBuildItem; + public final class BeanArchiveIndexBuildItem extends SimpleBuildItem { private final IndexView index; + private final Set generatedClassNames; + private final List additionalBeans; - public BeanArchiveIndexBuildItem(IndexView index) { + public BeanArchiveIndexBuildItem(IndexView index, Set generatedClassNames, + List additionalBeans) { this.index = index; + this.generatedClassNames = generatedClassNames; + this.additionalBeans = additionalBeans; } public IndexView getIndex() { return index; } + public Set getGeneratedClassNames() { + return generatedClassNames; + } + + public List getAdditionalBeans() { + return additionalBeans; + } + } diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanArchiveProcessor.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanArchiveProcessor.java index 1d5b307d08c7d..dc5a587915fce 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanArchiveProcessor.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanArchiveProcessor.java @@ -30,28 +30,71 @@ import org.jboss.jandex.CompositeIndex; import org.jboss.jandex.DotName; import org.jboss.jandex.IndexView; +import org.jboss.jandex.Indexer; +import io.quarkus.arc.processor.BeanArchives; import io.quarkus.arc.processor.BeanDefiningAnnotation; import io.quarkus.arc.processor.BeanDeployment; import io.quarkus.arc.processor.DotNames; +import io.quarkus.arc.runtime.LifecycleEventRunner; import io.quarkus.deployment.ApplicationArchive; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.ApplicationArchivesBuildItem; +import io.quarkus.deployment.builditem.GeneratedClassBuildItem; +import io.quarkus.deployment.index.IndexingUtil; public class BeanArchiveProcessor { - @Inject - BuildProducer beanArchiveIndexBuildProducer; - @Inject ApplicationArchivesBuildItem applicationArchivesBuildItem; @Inject List additionalBeanDefiningAnnotations; + @Inject + List additionalBeans; + + @Inject + List generatedBeans; + + @Inject + BuildProducer generatedClass; + @BuildStep - public void build() throws Exception { + public BeanArchiveIndexBuildItem build() throws Exception { + + // First build an index from application archives + IndexView applicationIndex = buildApplicationIndex(); + + // Then build additional index for beans added by extensions + Indexer additionalBeanIndexer = new Indexer(); + List additionalBeans = new ArrayList<>(); + for (AdditionalBeanBuildItem i : this.additionalBeans) { + additionalBeans.addAll(i.getBeanClasses()); + } + additionalBeans.add(LifecycleEventRunner.class.getName()); + Set additionalIndex = new HashSet<>(); + for (String beanClass : additionalBeans) { + IndexingUtil.indexClass(beanClass, additionalBeanIndexer, applicationIndex, additionalIndex, + ArcAnnotationProcessor.class.getClassLoader()); + } + Set generatedClassNames = new HashSet<>(); + for (GeneratedBeanBuildItem beanClass : generatedBeans) { + IndexingUtil.indexClass(beanClass.getName(), additionalBeanIndexer, applicationIndex, additionalIndex, + ArcAnnotationProcessor.class.getClassLoader(), + beanClass.getData()); + generatedClassNames.add(DotName.createSimple(beanClass.getName().replace('/', '.'))); + generatedClass.produce(new GeneratedClassBuildItem(true, beanClass.getName(), beanClass.getData())); + } + + // Finally, index ArC/CDI API built-in classes + return new BeanArchiveIndexBuildItem( + BeanArchives.buildBeanArchiveIndex(applicationIndex, additionalBeanIndexer.complete()), generatedClassNames, + additionalBeans); + } + + private IndexView buildApplicationIndex() { Set archives = applicationArchivesBuildItem.getAllApplicationArchives(); @@ -88,7 +131,7 @@ && containsBeanDefiningAnnotation(index, beanDefiningAnnotations))) { } } indexes.add(applicationArchivesBuildItem.getRootArchive().getIndex()); - beanArchiveIndexBuildProducer.produce(new BeanArchiveIndexBuildItem(CompositeIndex.create(indexes))); + return CompositeIndex.create(indexes); } boolean containsBeanDefiningAnnotation(IndexView index, Collection beanDefiningAnnotations) { diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanContainerBuildItem.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanContainerBuildItem.java index 9b16d89aa3af2..6472db47bfd29 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanContainerBuildItem.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanContainerBuildItem.java @@ -16,9 +16,8 @@ package io.quarkus.arc.deployment; -import org.jboss.builder.item.SimpleBuildItem; - import io.quarkus.arc.runtime.BeanContainer; +import io.quarkus.builder.item.SimpleBuildItem; public final class BeanContainerBuildItem extends SimpleBuildItem { diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanContainerListenerBuildItem.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanContainerListenerBuildItem.java index 3be38f3b3dbf3..8252f35b77183 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanContainerListenerBuildItem.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanContainerListenerBuildItem.java @@ -16,9 +16,8 @@ package io.quarkus.arc.deployment; -import org.jboss.builder.item.MultiBuildItem; - import io.quarkus.arc.runtime.BeanContainerListener; +import io.quarkus.builder.item.MultiBuildItem; public final class BeanContainerListenerBuildItem extends MultiBuildItem { diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanDefiningAnnotationBuildItem.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanDefiningAnnotationBuildItem.java index 2b7b3c22c2199..549af87f73dd7 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanDefiningAnnotationBuildItem.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanDefiningAnnotationBuildItem.java @@ -16,9 +16,10 @@ package io.quarkus.arc.deployment; -import org.jboss.builder.item.MultiBuildItem; import org.jboss.jandex.DotName; +import io.quarkus.builder.item.MultiBuildItem; + /** * This build item is used to specify additional bean defining annotations. See also * 2.5.1. Bean defining annotations. diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanDeploymentValidatorBuildItem.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanDeploymentValidatorBuildItem.java index 08f39ea573bc7..262ef3d05777e 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanDeploymentValidatorBuildItem.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanDeploymentValidatorBuildItem.java @@ -1,8 +1,7 @@ package io.quarkus.arc.deployment; -import org.jboss.builder.item.MultiBuildItem; - import io.quarkus.arc.processor.BeanDeploymentValidator; +import io.quarkus.builder.item.MultiBuildItem; public final class BeanDeploymentValidatorBuildItem extends MultiBuildItem { diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanRegistrarBuildItem.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanRegistrarBuildItem.java index 852730a90f648..463ce4637aa54 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanRegistrarBuildItem.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanRegistrarBuildItem.java @@ -16,9 +16,8 @@ package io.quarkus.arc.deployment; -import org.jboss.builder.item.MultiBuildItem; - import io.quarkus.arc.processor.BeanRegistrar; +import io.quarkus.builder.item.MultiBuildItem; public final class BeanRegistrarBuildItem extends MultiBuildItem { 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 2ab3505088814..fa8cdf52dc097 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigBuildStep.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigBuildStep.java @@ -1,110 +1,174 @@ package io.quarkus.arc.deployment; -import org.eclipse.microprofile.config.Config; -import org.eclipse.microprofile.config.ConfigProvider; +import static io.quarkus.deployment.annotations.ExecutionTime.RUNTIME_INIT; +import static java.util.stream.Collectors.groupingBy; +import static java.util.stream.Collectors.mapping; +import static java.util.stream.Collectors.toSet; + +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + import org.eclipse.microprofile.config.inject.ConfigProperty; import org.jboss.jandex.AnnotationInstance; 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 org.jboss.jandex.Type; +import org.jboss.jandex.Type.Kind; -import io.quarkus.arc.processor.BeanDeploymentValidator; +import io.quarkus.arc.processor.BeanRegistrar; import io.quarkus.arc.processor.DotNames; import io.quarkus.arc.processor.InjectionPointInfo; +import io.quarkus.arc.runtime.ConfigBeanCreator; +import io.quarkus.arc.runtime.ConfigDeploymentTemplate; +import io.quarkus.arc.runtime.QuarkusConfigProducer; +import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; -import io.smallrye.config.inject.ConfigProducer; +import io.quarkus.deployment.annotations.Record; +import io.quarkus.deployment.builditem.substrate.ReflectiveClassBuildItem; /** - * + * MicroProfile Config related build steps. */ public class ConfigBuildStep { + private static final DotName CONFIG_PROPERTY_NAME = DotName.createSimple(ConfigProperty.class.getName()); + private static final DotName SET_NAME = DotName.createSimple(Set.class.getName()); + private static final DotName LIST_NAME = DotName.createSimple(List.class.getName()); + @BuildStep AdditionalBeanBuildItem bean() { - return new AdditionalBeanBuildItem(ConfigProducer.class.getName()); + return new AdditionalBeanBuildItem(QuarkusConfigProducer.class); } @BuildStep - BeanDeploymentValidatorBuildItem beanDeploymentValidator() { - return new BeanDeploymentValidatorBuildItem(new ConfigBeanDeploymentValidator()); - } + BeanRegistrarBuildItem analyzeConfigPropertyInjectionPoints(BuildProducer configProperties, + BuildProducer reflectiveClass) { - static class ConfigBeanDeploymentValidator implements BeanDeploymentValidator { + return new BeanRegistrarBuildItem(new BeanRegistrar() { - private static final DotName CONFIG_PROPERTY_NAME = DotName.createSimple(ConfigProperty.class.getName()); + @Override + public void register(RegistrationContext context) { + Set customBeanTypes = new HashSet<>(); - @Override - public void validate(ValidationContext validationContext) { - - Config config = ConfigProvider.getConfig(); - ClassLoader tccl = Thread.currentThread().getContextClassLoader(); - - for (InjectionPointInfo injectionPoint : validationContext.get(Key.INJECTION_POINTS)) { - if (injectionPoint.hasDefaultedQualifier()) { - continue; - } - AnnotationInstance configProperty = injectionPoint.getRequiredQualifiers() - .stream() - .filter(a -> a.name() - .equals(CONFIG_PROPERTY_NAME)) - .findFirst() - .orElse(null); - if (configProperty != null) { - if (DotNames.OPTIONAL.equals(injectionPoint.getRequiredType().name())) { + for (InjectionPointInfo injectionPoint : context.get(Key.INJECTION_POINTS)) { + if (injectionPoint.hasDefaultedQualifier()) { + // Defaulted qualifier means no @ConfigProperty continue; } - String key = configProperty.value("name").asString(); - // TODO: collection types - Class type; - try { - if (injectionPoint.getRequiredType().kind() == Type.Kind.PRIMITIVE) { - switch (injectionPoint.getRequiredType().asPrimitiveType().primitive()) { - case BOOLEAN: - type = Boolean.TYPE; - break; - case BYTE: - type = Byte.TYPE; - break; - case CHAR: - type = Character.TYPE; - break; - case DOUBLE: - type = Double.TYPE; - break; - case INT: - type = Integer.TYPE; - break; - case FLOAT: - type = Float.TYPE; - break; - case LONG: - type = Long.TYPE; - break; - case SHORT: - type = Short.TYPE; - break; - default: - throw new IllegalArgumentException("Not a supported primitive type: " - + injectionPoint.getRequiredType().asPrimitiveType().primitive()); - } + AnnotationInstance configProperty = injectionPoint.getRequiredQualifier(CONFIG_PROPERTY_NAME); + if (configProperty != null) { + AnnotationValue nameValue = configProperty.value("name"); + AnnotationValue defaultValue = configProperty.value("defaultValue"); + String propertyName; + if (nameValue != null) { + propertyName = nameValue.asString(); } else { - type = tccl.loadClass(injectionPoint.getRequiredType().name().toString()); - } - if (!config.getOptionalValue(key, type).isPresent()) { - AnnotationValue defaultValue = configProperty.value("defaultValue"); - if (defaultValue == null || ConfigProperty.UNCONFIGURED_VALUE.equals(defaultValue.asString())) { - validationContext - .addDeploymentProblem(new IllegalStateException("No config value exists for: " + key)); + // org.acme.Foo.config + if (injectionPoint.isField()) { + FieldInfo field = injectionPoint.getTarget().asField(); + propertyName = getPropertyName(field.name(), field.declaringClass()); + } else if (injectionPoint.isParam()) { + MethodInfo method = injectionPoint.getTarget().asMethod(); + propertyName = getPropertyName(method.parameterName(injectionPoint.getPosition()), + method.declaringClass()); + } else { + throw new IllegalStateException("Unsupported injection point target: " + injectionPoint); } } - } catch (ClassNotFoundException e) { - throw new IllegalStateException("Unable to verify config injection point: " + injectionPoint, e); + + // Register a custom bean for injection points that are not handled by ConfigProducer + Type requiredType = injectionPoint.getRequiredType(); + if (!isHandledByProducers(requiredType)) { + customBeanTypes.add(requiredType); + } + + if (DotNames.OPTIONAL.equals(requiredType.name())) { + // Never validate Optional values + continue; + } + if (defaultValue != null && !ConfigProperty.UNCONFIGURED_VALUE.equals(defaultValue.asString())) { + // No need to validate properties with default values + continue; + } + String propertyType = requiredType.name().toString(); + if (requiredType.kind() != Kind.ARRAY && requiredType.kind() != Kind.PRIMITIVE) { + reflectiveClass.produce(new ReflectiveClassBuildItem(false, false, propertyType)); + } + configProperties.produce(new ConfigPropertyBuildItem(propertyName, propertyType)); } } + + for (Type type : customBeanTypes) { + if (type.kind() != Kind.ARRAY) { + // Implicit converters are most likely used + reflectiveClass.produce(new ReflectiveClassBuildItem(true, false, type.name().toString())); + } + context.configure( + type.kind() == Kind.ARRAY ? DotName.createSimple(ConfigBeanCreator.class.getName()) : type.name()) + .creator(ConfigBeanCreator.class) + .providerType(type) + .types(type) + .qualifiers(AnnotationInstance.create(CONFIG_PROPERTY_NAME, null, Collections.emptyList())) + .param("requiredType", type.name().toString()).done(); + } } + }); + } + + @BuildStep + AutoInjectAnnotationBuildItem autoInjectConfigProperty() { + return new AutoInjectAnnotationBuildItem(CONFIG_PROPERTY_NAME); + } + + @BuildStep + @Record(RUNTIME_INIT) + void validateConfigProperties(ConfigDeploymentTemplate template, List configProperties, + BeanContainerBuildItem beanContainer) { + // IMPL NOTE: we do depend on BeanContainerBuildItem to make sure that the BeanDeploymentValidator finished its processing + + Map> propNamesToClasses = configProperties.stream().collect( + groupingBy(ConfigPropertyBuildItem::getPropertyName, + mapping(ConfigPropertyBuildItem::getPropertyType, toSet()))); + template.validateConfigProperties(propNamesToClasses); + } + + private String getPropertyName(String name, ClassInfo declaringClass) { + StringBuilder builder = new StringBuilder(); + if (declaringClass.enclosingClass() == null) { + builder.append(declaringClass.name()); + } else { + builder.append(declaringClass.enclosingClass()).append(".").append(declaringClass.simpleName()); } + return builder.append(".").append(name).toString(); + } + private boolean isHandledByProducers(Type type) { + if (type.kind() == Kind.ARRAY) { + return false; + } + if (type.kind() == Kind.PRIMITIVE) { + switch (type.asPrimitiveType().primitive()) { + case BOOLEAN: + case DOUBLE: + case FLOAT: + case LONG: + case INT: + return true; + default: + return false; + } + } + return DotNames.STRING.equals(type.name()) || DotNames.OPTIONAL.equals(type.name()) || SET_NAME.equals(type.name()) + || LIST_NAME.equals(type.name()) || DotNames.LONG.equals(type.name()) || DotNames.FLOAT.equals(type.name()) + || DotNames.INTEGER.equals(type.name()) || DotNames.BOOLEAN.equals(type.name()) + || DotNames.DOUBLE.equals(type.name()); } } diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigPropertyBuildItem.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigPropertyBuildItem.java new file mode 100644 index 0000000000000..73f16ff179906 --- /dev/null +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigPropertyBuildItem.java @@ -0,0 +1,27 @@ +package io.quarkus.arc.deployment; + +import io.quarkus.builder.item.MultiBuildItem; + +/** + * Represents a mandatory config property that needs to be validated at runtime. + */ +public final class ConfigPropertyBuildItem extends MultiBuildItem { + + private final String propertyName; + + private final String propertyType; + + public ConfigPropertyBuildItem(String propertyName, String propertyType) { + this.propertyName = propertyName; + this.propertyType = propertyType; + } + + public String getPropertyName() { + return propertyName; + } + + public String getPropertyType() { + return propertyType; + } + +} diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ContextRegistrarBuildItem.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ContextRegistrarBuildItem.java index 90acde6f13c41..7cca6b6e2ea16 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ContextRegistrarBuildItem.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ContextRegistrarBuildItem.java @@ -16,10 +16,8 @@ package io.quarkus.arc.deployment; -import org.jboss.builder.item.MultiBuildItem; - -import io.quarkus.arc.processor.BeanRegistrar; import io.quarkus.arc.processor.ContextRegistrar; +import io.quarkus.builder.item.MultiBuildItem; public final class ContextRegistrarBuildItem extends MultiBuildItem { diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/FullArchiveIndexBuildItem.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/FullArchiveIndexBuildItem.java deleted file mode 100644 index f1030aa7895a9..0000000000000 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/FullArchiveIndexBuildItem.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2018 Red Hat, Inc. - * - * Licensed 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.arc.deployment; - -import java.util.List; -import java.util.Set; - -import org.jboss.builder.item.SimpleBuildItem; -import org.jboss.jandex.DotName; -import org.jboss.jandex.IndexView; - -public final class FullArchiveIndexBuildItem extends SimpleBuildItem { - - private final IndexView index; - private final Set generatedClassNames; - private final List additionalBeans; - - public FullArchiveIndexBuildItem(final IndexView index, final Set generatedClassNames, - final List additionalBeans) { - this.index = index; - this.generatedClassNames = generatedClassNames; - this.additionalBeans = additionalBeans; - } - - public IndexView getIndex() { - return index; - } - - public Set getGeneratedClassNames() { - return generatedClassNames; - } - - public List getAdditionalBeans() { - return additionalBeans; - } -} diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/GeneratedBeanBuildItem.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/GeneratedBeanBuildItem.java index fef0eba4af7b0..81dd7c09ddb92 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/GeneratedBeanBuildItem.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/GeneratedBeanBuildItem.java @@ -16,7 +16,7 @@ package io.quarkus.arc.deployment; -import org.jboss.builder.item.MultiBuildItem; +import io.quarkus.builder.item.MultiBuildItem; /** * A generated CDI bean. If this is produced then a {@link io.quarkus.deployment.builditem.GeneratedClassBuildItem} diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ResourceAnnotationBuildItem.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ResourceAnnotationBuildItem.java index 2c6b754208fd8..9c339d28c6e97 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ResourceAnnotationBuildItem.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ResourceAnnotationBuildItem.java @@ -16,9 +16,10 @@ package io.quarkus.arc.deployment; -import org.jboss.builder.item.MultiBuildItem; import org.jboss.jandex.DotName; +import io.quarkus.builder.item.MultiBuildItem; + /** * This build item is used to specify resource annotations that makes it possible to resolve non-CDI injection points, such as * Java EE resources. diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/RuntimeBeanBuildItem.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/RuntimeBeanBuildItem.java new file mode 100644 index 0000000000000..176741341d982 --- /dev/null +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/RuntimeBeanBuildItem.java @@ -0,0 +1,133 @@ +package io.quarkus.arc.deployment; + +import java.lang.annotation.Annotation; +import java.util.NavigableMap; +import java.util.Objects; +import java.util.TreeMap; +import java.util.function.Supplier; + +import javax.enterprise.context.Dependent; + +import io.quarkus.builder.item.MultiBuildItem; +import io.quarkus.runtime.RuntimeValue; + +/** + * Represents a bean that can be easily produced through a template (or other runtime Supplier implementation) + */ +public final class RuntimeBeanBuildItem extends MultiBuildItem { + + final String scope; + final String type; + final Supplier supplier; + final RuntimeValue runtimeValue; + final NavigableMap> qualifiers; + final boolean removable; + + RuntimeBeanBuildItem(String scope, String type, Supplier supplier, + NavigableMap> qualifiers, boolean removable, + RuntimeValue runtimeValue) { + if (supplier != null && runtimeValue != null) { + throw new IllegalArgumentException("It is not possible to specify both - a supplier and a runtime value"); + } + this.scope = scope; + this.type = type; + this.supplier = supplier; + this.qualifiers = qualifiers; + this.removable = removable; + this.runtimeValue = runtimeValue; + } + + public String getScope() { + return scope; + } + + public String getType() { + return type; + } + + public Supplier getSupplier() { + return supplier; + } + + public RuntimeValue getRuntimeValue() { + return runtimeValue; + } + + public boolean isRemovable() { + return removable; + } + + public NavigableMap> getQualifiers() { + return qualifiers; + } + + public static Builder builder(Class type) { + return builder(type.getName()); + } + + public static Builder builder(String type) { + Objects.requireNonNull(type); + return new Builder(type); + } + + public static class Builder { + + String scope = Dependent.class.getName(); + boolean removable = true; + final String type; + Supplier supplier; + RuntimeValue value; + final NavigableMap> qualifiers = new TreeMap<>(); + + public Builder(String type) { + this.type = type; + } + + public Builder setScope(String scope) { + this.scope = scope; + return this; + } + + public Builder setScope(Class type) { + this.scope = type.getName(); + return this; + } + + public Builder addQualifier(String type) { + qualifiers.put(type, new TreeMap<>()); + return this; + } + + public Builder addQualifier(String type, NavigableMap values) { + qualifiers.put(type, new TreeMap<>(values)); + return this; + } + + public Builder addQualifier(Class type) { + return addQualifier(type.getName()); + } + + public Builder addQualifier(Class type, NavigableMap values) { + return addQualifier(type.getName(), values); + } + + public Builder setRemovable(boolean removable) { + this.removable = removable; + return this; + } + + public Builder setSupplier(Supplier supplier) { + this.supplier = Objects.requireNonNull(supplier); + return this; + } + + public Builder setRuntimeValue(RuntimeValue runtimeValue) { + this.value = Objects.requireNonNull(runtimeValue); + return this; + } + + public RuntimeBeanBuildItem build() { + return new RuntimeBeanBuildItem(scope, type, supplier, qualifiers, removable, value); + } + } +} diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/RuntimeBeanProcessor.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/RuntimeBeanProcessor.java new file mode 100644 index 0000000000000..2e097f758896a --- /dev/null +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/RuntimeBeanProcessor.java @@ -0,0 +1,87 @@ +package io.quarkus.arc.deployment; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.NavigableMap; +import java.util.function.Predicate; +import java.util.function.Supplier; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Produces; + +import io.quarkus.arc.processor.BeanInfo; +import io.quarkus.arc.runtime.ArcDeploymentTemplate; +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.util.HashUtil; +import io.quarkus.gizmo.AnnotationCreator; +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; + +public class RuntimeBeanProcessor { + + @BuildStep + @Record(ExecutionTime.STATIC_INIT) + void build(List beans, + BuildProducer generatedBean, + ArcDeploymentTemplate template, + BuildProducer unremovableBeans) { + String beanName = "io.quarkus.arc.runtimebean.RuntimeBeanProducers"; + + ClassCreator c = new ClassCreator(new ClassOutput() { + @Override + public void write(String name, byte[] data) { + generatedBean.produce(new GeneratedBeanBuildItem(name, data)); + } + }, beanName, null, Object.class.getName()); + + c.addAnnotation(ApplicationScoped.class); + Map> map = new HashMap<>(); + for (RuntimeBeanBuildItem bean : beans) { + //deterministic name + //as we know the maps are sorted this will result in the same hash for the same bean + String name = bean.type.replace(".", "_") + "_" + HashUtil.sha1(bean.qualifiers.toString()); + if (bean.runtimeValue != null) { + map.put(name, template.createSupplier(bean.runtimeValue)); + } else { + map.put(name, bean.supplier); + } + + MethodCreator producer = c.getMethodCreator("produce_" + name, bean.type); + producer.addAnnotation(Produces.class); + producer.addAnnotation(bean.scope); + for (Map.Entry> qualifierEntry : bean.qualifiers.entrySet()) { + AnnotationCreator builder = producer.addAnnotation(qualifierEntry.getKey()); + for (Map.Entry valueEntry : qualifierEntry.getValue().entrySet()) { + builder.addValue(valueEntry.getKey(), valueEntry.getValue()); + } + } + + if (!bean.removable) { + unremovableBeans.produce(new UnremovableBeanBuildItem(new Predicate() { + @Override + public boolean test(BeanInfo bean) { + return bean.isProducerMethod() && bean.getTarget().get().asMethod().name().equals(name); + } + })); + } + + ResultHandle staticMap = producer + .readStaticField(FieldDescriptor.of(ArcDeploymentTemplate.class, "supplierMap", Map.class)); + ResultHandle supplier = producer.invokeInterfaceMethod( + MethodDescriptor.ofMethod(Map.class, "get", Object.class, Object.class), staticMap, producer.load(name)); + ResultHandle result = producer.invokeInterfaceMethod(MethodDescriptor.ofMethod(Supplier.class, "get", Object.class), + supplier); + producer.returnValue(result); + } + c.close(); + template.initSupplierBeans(map); + } +} diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/TestsAsBeanDefiningAnnotationsProcessor.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/TestsAsBeanDefiningAnnotationsProcessor.java new file mode 100644 index 0000000000000..846d687cd19f0 --- /dev/null +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/TestsAsBeanDefiningAnnotationsProcessor.java @@ -0,0 +1,20 @@ +package io.quarkus.arc.deployment; + +import java.util.List; + +import org.jboss.jandex.DotName; + +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.TestAnnotationBuildItem; + +public class TestsAsBeanDefiningAnnotationsProcessor { + + @BuildStep + public void produce(List items, BuildProducer producer) { + for (TestAnnotationBuildItem item : items) { + producer.produce(new BeanDefiningAnnotationBuildItem(DotName.createSimple(item.getAnnotationClassName()))); + } + } + +} diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/UnremovableBeanBuildItem.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/UnremovableBeanBuildItem.java index c45dc3470c2cf..5326124f349aa 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/UnremovableBeanBuildItem.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/UnremovableBeanBuildItem.java @@ -5,11 +5,11 @@ import java.util.Objects; import java.util.function.Predicate; -import org.jboss.builder.item.MultiBuildItem; import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.DotName; import io.quarkus.arc.processor.BeanInfo; +import io.quarkus.builder.item.MultiBuildItem; /** * This build item is used to exclude beans that would be normally removed if the config property diff --git a/extensions/arc/deployment/src/main/resources/META-INF/services/io.quarkus.deployment.test.TestScopeSetup b/extensions/arc/deployment/src/main/resources/META-INF/services/io.quarkus.deployment.test.TestScopeSetup new file mode 100644 index 0000000000000..183baa6e2bf49 --- /dev/null +++ b/extensions/arc/deployment/src/main/resources/META-INF/services/io.quarkus.deployment.test.TestScopeSetup @@ -0,0 +1 @@ +io.quarkus.arc.deployment.ArcTestRequestScopeProvider \ No newline at end of file diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/SimpleBean.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/SimpleBean.java index c1fd6b18af342..02815c1df1fe5 100644 --- a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/SimpleBean.java +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/SimpleBean.java @@ -21,6 +21,7 @@ import javax.enterprise.context.ApplicationScoped; import javax.enterprise.event.Observes; import javax.inject.Inject; +import javax.inject.Provider; import org.eclipse.microprofile.config.inject.ConfigProperty; @@ -45,6 +46,10 @@ public class SimpleBean { @ConfigProperty(name = "simpleBean.baz") Optional bazOptional; + @Inject + @ConfigProperty(name = "simpleBean.baz") + Provider bazProvider; + void onStart(@Observes StartupEvent event) { startupEvent.set(event); } @@ -65,4 +70,8 @@ Optional getBazOptional() { return bazOptional; } + public Provider getBazProvider() { + return bazProvider; + } + } diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/SimpleBeanTest.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/SimpleBeanTest.java index 769a99a1b7866..00dec102e0fac 100644 --- a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/SimpleBeanTest.java +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/SimpleBeanTest.java @@ -47,6 +47,7 @@ public void testSimpleBean() { assertEquals(SimpleBean.DEFAULT, simpleBean.getFoo()); assertFalse(simpleBean.getFooOptional().isPresent()); assertEquals("1", simpleBean.getBazOptional().get()); + assertEquals("1", simpleBean.getBazProvider().get()); } } diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/autoinject/AutoFieldInjectionTest.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/autoinject/AutoFieldInjectionTest.java new file mode 100644 index 0000000000000..879297f74aa3c --- /dev/null +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/autoinject/AutoFieldInjectionTest.java @@ -0,0 +1,67 @@ +package io.quarkus.arc.test.autoinject; + +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.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import javax.enterprise.context.Dependent; +import javax.enterprise.inject.Produces; +import javax.inject.Inject; +import javax.inject.Qualifier; + +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 AutoFieldInjectionTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(AutoFieldInjectionTest.class, Client.class, Producer.class)); + + @Inject + Client bean; + + @Test + public void testConfigWasInjected() { + Assertions.assertEquals("ok", bean.foo); + } + + @Dependent + static class Client { + + // @Inject is added automatically + @MyQualifier + String foo; + + } + + static class Producer { + + @MyQualifier + @Produces + String produceString() { + return "ok"; + } + + } + + @Qualifier + @Inherited + @Target({ TYPE, METHOD, FIELD, PARAMETER }) + @Retention(RUNTIME) + @interface MyQualifier { + + } +} diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/ConfigArrayConverterTest.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/ConfigArrayConverterTest.java new file mode 100644 index 0000000000000..886be29e9f261 --- /dev/null +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/ConfigArrayConverterTest.java @@ -0,0 +1,75 @@ +/* + * Copyright 2018 Red Hat, Inc. + * + * Licensed 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.arc.test.config; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import org.eclipse.microprofile.config.inject.ConfigProperty; +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; + +public class ConfigArrayConverterTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(Configured.class) + .addAsResource(new StringAsset("foos=1,2,bar\nbools=true,false"), "application.properties")); + + @Inject + Configured configured; + + @Test + public void testFoo() { + assertEquals(3, configured.foos.length); + assertEquals("1", configured.foos[0]); + assertEquals("2", configured.foos[1]); + assertEquals("bar", configured.foos[2]); + // Boolean[] + assertEquals(2, configured.bools.length); + assertEquals(false, configured.bools[1]); + // boolean[] + assertEquals(2, configured.boolsPrimitives.length); + assertEquals(true, configured.boolsPrimitives[0]); + } + + @Singleton + static class Configured { + + @Inject + @ConfigProperty(name = "foos") + String[] foos; + + @Inject + @ConfigProperty(name = "bools_primitives", defaultValue = "true,true") + boolean[] boolsPrimitives; + + @Inject + @ConfigProperty(name = "bools") + Boolean[] bools; + + } + +} diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/ConfigImplicitConverterTest.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/ConfigImplicitConverterTest.java new file mode 100644 index 0000000000000..d9ca212cae95e --- /dev/null +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/ConfigImplicitConverterTest.java @@ -0,0 +1,71 @@ +/* + * Copyright 2018 Red Hat, Inc. + * + * Licensed 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.arc.test.config; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; + +import org.eclipse.microprofile.config.inject.ConfigProperty; +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; + +public class ConfigImplicitConverterTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(Configured.class) + .addAsResource(new StringAsset("foo=1"), "application.properties")); + + @Inject + Configured configured; + + @Test + public void testFoo() { + assertEquals("1", configured.getFooValue()); + } + + @ApplicationScoped + static class Configured { + + @Inject + @ConfigProperty(name = "foo") + Foo foo; + + String getFooValue() { + return foo != null ? foo.value : null; + } + } + + public static class Foo { + + String value; + + public Foo(String value) { + this.value = value; + } + + } + +} diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/ConfigPropertyInjectionWithoutInjectAnnotationTest.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/ConfigPropertyInjectionWithoutInjectAnnotationTest.java new file mode 100644 index 0000000000000..9a6b84fde51d4 --- /dev/null +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/ConfigPropertyInjectionWithoutInjectAnnotationTest.java @@ -0,0 +1,31 @@ +package io.quarkus.arc.test.config; + +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.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +public class ConfigPropertyInjectionWithoutInjectAnnotationTest { + + private static final String configValue = "someValue"; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(ConfigPropertyInjectionWithoutInjectAnnotationTest.class, SomeBeanUsingConfig.class) + .addAsResource(new StringAsset("something=" + configValue), "application.properties")); + + @Inject + SomeBeanUsingConfig bean; + + @Test + public void testConfigWasInjected() { + Assertions.assertEquals(configValue, bean.getFoo()); + } +} diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/SomeBeanUsingConfig.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/SomeBeanUsingConfig.java new file mode 100644 index 0000000000000..36eaee3a6f50c --- /dev/null +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/SomeBeanUsingConfig.java @@ -0,0 +1,17 @@ +package io.quarkus.arc.test.config; + +import javax.enterprise.context.ApplicationScoped; + +import org.eclipse.microprofile.config.inject.ConfigProperty; + +@ApplicationScoped +public class SomeBeanUsingConfig { + + // deliberately missing @Inject + @ConfigProperty(name = "something") + String foo; + + public String getFoo() { + return foo; + } +} diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/metadata/InjectionPointMetadataTest.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/metadata/InjectionPointMetadataTest.java new file mode 100644 index 0000000000000..b2b786e946d08 --- /dev/null +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/metadata/InjectionPointMetadataTest.java @@ -0,0 +1,61 @@ +/* + * Copyright 2018 Red Hat, Inc. + * + * Licensed 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.arc.test.metadata; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.spi.DefinitionException; +import javax.enterprise.inject.spi.DeploymentException; +import javax.enterprise.inject.spi.InjectionPoint; +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.test.QuarkusUnitTest; + +public class InjectionPointMetadataTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(InvalidInjection.class)) + .assertException(t -> { + assertEquals(DeploymentException.class, t.getClass()); + // There should be only one deployment problem + assertEquals(DefinitionException.class, t.getCause().getClass()); + }); + + @Test + public void testValidationFailed() { + // This method should not be invoked + Assertions.fail(); + } + + @ApplicationScoped + static class InvalidInjection { + + @Inject + InjectionPoint injectionPoint; + + } + +} diff --git a/extensions/arc/runtime/pom.xml b/extensions/arc/runtime/pom.xml index 9862b4d6750bd..98e50c063d07d 100644 --- a/extensions/arc/runtime/pom.xml +++ b/extensions/arc/runtime/pom.xml @@ -26,24 +26,25 @@ 4.0.0 - quarkus-arc-runtime + quarkus-arc Quarkus - ArC - Runtime io.quarkus.arc - arc-runtime + arc io.quarkus - quarkus-core-runtime + quarkus-core - maven-dependency-plugin + io.quarkus + quarkus-bootstrap-maven-plugin maven-compiler-plugin diff --git a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/AdditionalBean.java b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/AdditionalBean.java new file mode 100644 index 0000000000000..54747f84b708f --- /dev/null +++ b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/AdditionalBean.java @@ -0,0 +1,19 @@ +package io.quarkus.arc.runtime; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import javax.enterprise.inject.Stereotype; + +/** + * This built-in stereotype is automatically added to all additional beans. + */ +@Stereotype +@Target({ TYPE }) +@Retention(RUNTIME) +public @interface AdditionalBean { + +} diff --git a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ArcDeploymentTemplate.java b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ArcDeploymentTemplate.java index a63cc6a845544..f912972a9b23a 100644 --- a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ArcDeploymentTemplate.java +++ b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ArcDeploymentTemplate.java @@ -20,6 +20,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.function.Supplier; import org.jboss.logging.Logger; @@ -28,15 +29,20 @@ import io.quarkus.arc.ArcContainer; import io.quarkus.arc.InstanceHandle; import io.quarkus.arc.ManagedContext; +import io.quarkus.runtime.RuntimeValue; import io.quarkus.runtime.ShutdownContext; import io.quarkus.runtime.annotations.Template; /** - * @author Martin Kouba */ @Template public class ArcDeploymentTemplate { + /** + * Used to hold the Supplier instances used for synthetic bean declarations. + */ + public static volatile Map> supplierMap; + private static final Logger LOGGER = Logger.getLogger(ArcDeploymentTemplate.class.getName()); public ArcContainer getContainer(ShutdownContext shutdown) throws Exception { @@ -50,11 +56,14 @@ public void run() { return container; } + public void initSupplierBeans(Map> beans) { + supplierMap = beans; + } + public BeanContainer initBeanContainer(ArcContainer container, List listeners, Collection removedBeanTypes) throws Exception { BeanContainer beanContainer = new BeanContainer() { - @SuppressWarnings("unchecked") @Override public Factory instanceFactory(Class type, Annotation... qualifiers) { Supplier> handleSupplier = container.instanceSupplier(type, qualifiers); @@ -112,6 +121,15 @@ public void run() { }); } + public Supplier createSupplier(RuntimeValue value) { + return new Supplier() { + @Override + public Object get() { + return value.getValue(); + } + }; + } + private static final class DefaultInstanceFactory implements BeanContainer.Factory { final Class type; diff --git a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/BeanContainer.java b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/BeanContainer.java index 6894081dee174..5cc610997653d 100644 --- a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/BeanContainer.java +++ b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/BeanContainer.java @@ -16,7 +16,6 @@ package io.quarkus.arc.runtime; -import java.io.Closeable; import java.lang.annotation.Annotation; import io.quarkus.arc.ManagedContext; 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 new file mode 100644 index 0000000000000..5fe3306fe3294 --- /dev/null +++ b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ConfigBeanCreator.java @@ -0,0 +1,73 @@ +package io.quarkus.arc.runtime; + +import java.lang.annotation.Annotation; +import java.util.Map; +import java.util.Optional; + +import javax.enterprise.context.spi.CreationalContext; +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.quarkus.arc.BeanCreator; +import io.quarkus.arc.InjectionPointProvider; +import io.smallrye.config.SmallRyeConfig; + +public class ConfigBeanCreator implements BeanCreator { + + @Override + public Object create(CreationalContext creationalContext, Map params) { + String requiredType = params.get("requiredType").toString(); + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + if (cl == null) { + cl = ConfigBeanCreator.class.getClassLoader(); + } + Class clazz; + try { + clazz = Class.forName(requiredType, true, cl); + } catch (ClassNotFoundException e) { + throw new IllegalStateException("Cannot load required type: " + requiredType); + } + + InjectionPoint injectionPoint = InjectionPointProvider.get(); + if (injectionPoint == null) { + throw new IllegalStateException("No current injection point found"); + } + + ConfigProperty configProperty = getConfigProperty(injectionPoint); + if (configProperty == null) { + throw new IllegalStateException("@ConfigProperty not found"); + } + + String key = configProperty.name(); + String defaultValue = configProperty.defaultValue(); + + if (defaultValue.isEmpty() || ConfigProperty.UNCONFIGURED_VALUE.equals(defaultValue)) { + return getConfig().getValue(key, clazz); + } else { + Config config = getConfig(); + Optional value = config.getOptionalValue(key, clazz); + if (value.isPresent()) { + return value.get(); + } else { + return ((SmallRyeConfig) config).convert(defaultValue, clazz); + } + } + } + + private Config getConfig() { + return ConfigProviderResolver.instance().getConfig(); + } + + private ConfigProperty getConfigProperty(InjectionPoint injectionPoint) { + for (Annotation qualifier : injectionPoint.getQualifiers()) { + if (qualifier.annotationType().equals(ConfigProperty.class)) { + return (ConfigProperty) qualifier; + } + } + return null; + } + +} diff --git a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ConfigDeploymentTemplate.java b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ConfigDeploymentTemplate.java new file mode 100644 index 0000000000000..840ba1c8af081 --- /dev/null +++ b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/ConfigDeploymentTemplate.java @@ -0,0 +1,86 @@ +/* + * Copyright 2018 Red Hat, Inc. + * + * Licensed 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.arc.runtime; + +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import javax.enterprise.inject.spi.DeploymentException; + +import org.eclipse.microprofile.config.Config; +import org.eclipse.microprofile.config.spi.ConfigProviderResolver; + +import io.quarkus.runtime.annotations.Template; + +/** + * @author Martin Kouba + */ +@Template +public class ConfigDeploymentTemplate { + + public void validateConfigProperties(Map> properties) { + Config config = ConfigProviderResolver.instance().getConfig(); + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + if (cl == null) { + ConfigDeploymentTemplate.class.getClassLoader(); + } + for (Entry> entry : properties.entrySet()) { + Set propertyTypes = entry.getValue(); + for (String propertyType : propertyTypes) { + Class propertyClass = load(propertyType, cl); + // For parameterized types and arrays, we only check if the property config exists without trying to convert it + if (propertyClass.isArray() || propertyClass.getTypeParameters().length > 0) { + propertyClass = String.class; + } + if (!config.getOptionalValue(entry.getKey(), propertyClass).isPresent()) { + throw new DeploymentException( + "No config value of type " + entry.getValue() + " exists for: " + entry.getKey()); + } + } + } + } + + private Class load(String className, ClassLoader cl) { + switch (className) { + case "boolean": + return boolean.class; + case "byte": + return byte.class; + case "short": + return short.class; + case "int": + return int.class; + case "long": + return long.class; + case "float": + return float.class; + case "double": + return double.class; + case "char": + return char.class; + case "void": + return void.class; + } + try { + return Class.forName(className, true, cl); + } catch (ClassNotFoundException e) { + throw new IllegalStateException("Unable to load the config property type: " + className); + } + } + +} 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 new file mode 100644 index 0000000000000..548fb96ce7f05 --- /dev/null +++ b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/QuarkusConfigProducer.java @@ -0,0 +1,95 @@ +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()); + } + +} diff --git a/extensions/caffeine/deployment/pom.xml b/extensions/caffeine/deployment/pom.xml index ca471c32c4c34..d4c4f31e3a0b3 100644 --- a/extensions/caffeine/deployment/pom.xml +++ b/extensions/caffeine/deployment/pom.xml @@ -25,17 +25,17 @@ 4.0.0 - quarkus-caffeine + quarkus-caffeine-deployment Quarkus - Caffeine - Deployment io.quarkus - quarkus-core + quarkus-core-deployment io.quarkus - quarkus-caffeine-runtime + quarkus-caffeine diff --git a/extensions/caffeine/runtime/pom.xml b/extensions/caffeine/runtime/pom.xml index 9284167bf9442..32fd48fc93d6f 100644 --- a/extensions/caffeine/runtime/pom.xml +++ b/extensions/caffeine/runtime/pom.xml @@ -25,7 +25,7 @@ 4.0.0 - quarkus-caffeine-runtime + quarkus-caffeine Quarkus - Caffeine - Runtime @@ -36,15 +36,14 @@ com.oracle.substratevm svm - compile - - maven-dependency-plugin + io.quarkus + quarkus-bootstrap-maven-plugin maven-compiler-plugin diff --git a/extensions/camel/camel-aws-s3/deployment/pom.xml b/extensions/camel/camel-aws-s3/deployment/pom.xml new file mode 100644 index 0000000000000..87bc74997d37a --- /dev/null +++ b/extensions/camel/camel-aws-s3/deployment/pom.xml @@ -0,0 +1,59 @@ + + + + quarkus-camel-aws-parent + io.quarkus + 999-SNAPSHOT + ../ + + 4.0.0 + + quarkus-camel-aws-s3-deployment + Quarkus - Camel - AWS S3 - Deployment + + + + + + io.quarkus + quarkus-core-deployment + + + io.quarkus + quarkus-arc-deployment + + + + + io.quarkus + quarkus-camel-aws-s3 + + + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + + true + + + + + \ No newline at end of file diff --git a/extensions/camel/camel-aws-s3/deployment/src/main/java/io/quarkus/camel/component/aws/s3/deployment/CamelAwsS3Processor.java b/extensions/camel/camel-aws-s3/deployment/src/main/java/io/quarkus/camel/component/aws/s3/deployment/CamelAwsS3Processor.java new file mode 100644 index 0000000000000..4beef207df347 --- /dev/null +++ b/extensions/camel/camel-aws-s3/deployment/src/main/java/io/quarkus/camel/component/aws/s3/deployment/CamelAwsS3Processor.java @@ -0,0 +1,95 @@ +package io.quarkus.camel.component.aws.s3.deployment; + +import java.util.Collection; +import java.util.stream.Collectors; + +import org.apache.commons.logging.impl.Jdk14Logger; +import org.apache.commons.logging.impl.LogFactoryImpl; +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.DotName; +import org.jboss.jandex.IndexView; + +import com.amazonaws.partitions.model.CredentialScope; +import com.amazonaws.partitions.model.Endpoint; +import com.amazonaws.partitions.model.Partition; +import com.amazonaws.partitions.model.Partitions; +import com.amazonaws.partitions.model.Region; +import com.amazonaws.partitions.model.Service; +import com.amazonaws.services.s3.internal.AWSS3V4Signer; +import com.amazonaws.services.s3.model.CryptoConfiguration; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonSerializer; + +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.ApplicationArchivesBuildItem; +import io.quarkus.deployment.builditem.CombinedIndexBuildItem; +import io.quarkus.deployment.builditem.FeatureBuildItem; +import io.quarkus.deployment.builditem.substrate.ReflectiveClassBuildItem; +import io.quarkus.deployment.builditem.substrate.ReflectiveMethodBuildItem; +import io.quarkus.deployment.builditem.substrate.RuntimeInitializedClassBuildItem; +import io.quarkus.deployment.builditem.substrate.ServiceProviderBuildItem; +import io.quarkus.deployment.builditem.substrate.SubstrateProxyDefinitionBuildItem; +import io.quarkus.deployment.builditem.substrate.SubstrateResourceBuildItem; +import io.quarkus.deployment.builditem.substrate.SubstrateResourceBundleBuildItem; + +class CamelAwsS3Processor { + + public static final String AWS_S3_APPLICATION_ARCHIVE_MARKERS = "com/amazonaws"; + + @BuildStep + FeatureBuildItem feature() { + return new FeatureBuildItem(FeatureBuildItem.CAMEL_AWS_S3); + } + + @BuildStep + RuntimeInitializedClassBuildItem cryptoConfiguration() { + return new RuntimeInitializedClassBuildItem(CryptoConfiguration.class.getCanonicalName()); + } + + @BuildStep + SubstrateProxyDefinitionBuildItem httpProxies() { + return new SubstrateProxyDefinitionBuildItem("org.apache.http.conn.HttpClientConnectionManager", + "org.apache.http.pool.ConnPoolControl", "com.amazonaws.http.conn.Wrapped"); + } + + @BuildStep(applicationArchiveMarkers = { AWS_S3_APPLICATION_ARCHIVE_MARKERS }) + void process(CombinedIndexBuildItem combinedIndexBuildItem, + BuildProducer reflectiveClass, + BuildProducer reflectiveMethod, + BuildProducer resource, + BuildProducer resourceBundle, + BuildProducer serviceProvider, + ApplicationArchivesBuildItem applicationArchivesBuildItem) { + + IndexView view = combinedIndexBuildItem.getIndex(); + + resource.produce(new SubstrateResourceBuildItem("com/amazonaws/partitions/endpoints.json")); + for (String s : getImplementations(view, JsonDeserializer.class)) { + reflectiveClass.produce(new ReflectiveClassBuildItem(true, false, s)); + } + for (String s : getImplementations(view, JsonSerializer.class)) { + reflectiveClass.produce(new ReflectiveClassBuildItem(true, false, s)); + } + reflectiveClass.produce(new ReflectiveClassBuildItem(true, false, + Partitions.class.getCanonicalName(), + Partition.class.getCanonicalName(), + Endpoint.class.getCanonicalName(), + Region.class.getCanonicalName(), + Service.class.getCanonicalName(), + CredentialScope.class.getCanonicalName(), + LogFactoryImpl.class.getCanonicalName(), + Jdk14Logger.class.getCanonicalName(), + AWSS3V4Signer.class.getCanonicalName(), + "com.sun.org.apache.xerces.internal.parsers.SAXParser", + "com.sun.xml.internal.stream.XMLInputFactoryImpl", + "org.apache.camel.converter.jaxp.XmlConverter")); + } + + protected Collection getImplementations(IndexView view, Class type) { + return view.getAllKnownImplementors(DotName.createSimple(type.getName())).stream() + .map(ClassInfo::toString) + .collect(Collectors.toList()); + } + +} diff --git a/extensions/camel/camel-aws-s3/pom.xml b/extensions/camel/camel-aws-s3/pom.xml new file mode 100644 index 0000000000000..9cda6ba89ba9d --- /dev/null +++ b/extensions/camel/camel-aws-s3/pom.xml @@ -0,0 +1,23 @@ + + + + + quarkus-camel-parent + io.quarkus + 999-SNAPSHOT + ../ + + + 4.0.0 + + quarkus-camel-aws-parent + Quarkus - Camel - AWS + pom + + deployment + runtime + + + diff --git a/extensions/camel/camel-aws-s3/runtime/pom.xml b/extensions/camel/camel-aws-s3/runtime/pom.xml new file mode 100644 index 0000000000000..8a60f6670cb8c --- /dev/null +++ b/extensions/camel/camel-aws-s3/runtime/pom.xml @@ -0,0 +1,87 @@ + + + + quarkus-camel-aws-parent + io.quarkus + 999-SNAPSHOT + ../ + + 4.0.0 + + quarkus-camel-aws-s3 + Quarkus - Camel - AWS S3 - Runtime + + + + + + io.quarkus + quarkus-arc + + + io.quarkus + quarkus-core + + + com.oracle.substratevm + svm + + + + + org.apache.camel + camel-aws-s3 + + + com.fasterxml.jackson.dataformat + jackson-dataformat-cbor + + + com.fasterxml.jackson.core + jackson-databind + + + + + + com.fasterxml.jackson.dataformat + jackson-dataformat-cbor + + + + com.fasterxml.jackson.core + jackson-databind + + + + + + + + io.quarkus + quarkus-bootstrap-maven-plugin + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + + true + + + + + \ No newline at end of file diff --git a/extensions/camel/camel-core/deployment/pom.xml b/extensions/camel/camel-core/deployment/pom.xml index f494b039899e0..cb5c7dd12b3bd 100644 --- a/extensions/camel/camel-core/deployment/pom.xml +++ b/extensions/camel/camel-core/deployment/pom.xml @@ -1,56 +1,54 @@ - - quarkus-camel-core-parent - io.quarkus - 999-SNAPSHOT - ../ - - 4.0.0 - - quarkus-camel-core - Quarkus - Camel - Core - Deployment - - - - - - io.quarkus - quarkus-core - - - io.quarkus - quarkus-arc - - - - - io.quarkus - quarkus-camel-core-runtime - - - io.quarkus - quarkus-jaxb - - - - - - - - maven-compiler-plugin - - - - io.quarkus - quarkus-extension-processor - ${project.version} - - - - - - + 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-camel-core-parent + io.quarkus + 999-SNAPSHOT + ../ + + 4.0.0 + quarkus-camel-core-deployment + Quarkus - Camel - Core - Deployment + + + + io.quarkus + quarkus-core-deployment + + + io.quarkus + quarkus-arc-deployment + + + + io.quarkus + quarkus-camel-core + + + io.quarkus + quarkus-jaxb-deployment + + + io.quarkus + quarkus-caffeine-deployment + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + diff --git a/extensions/camel/camel-core/deployment/src/main/java/io/quarkus/camel/core/deployment/CamelInitProcessor.java b/extensions/camel/camel-core/deployment/src/main/java/io/quarkus/camel/core/deployment/CamelInitProcessor.java index 72e125e6a0f73..165453ed86b2d 100644 --- a/extensions/camel/camel-core/deployment/src/main/java/io/quarkus/camel/core/deployment/CamelInitProcessor.java +++ b/extensions/camel/camel-core/deployment/src/main/java/io/quarkus/camel/core/deployment/CamelInitProcessor.java @@ -3,10 +3,10 @@ import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.Properties; import java.util.Set; import java.util.function.BiConsumer; @@ -22,13 +22,18 @@ import org.eclipse.microprofile.config.ConfigProvider; import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; +import org.slf4j.LoggerFactory; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; +import io.quarkus.arc.deployment.BeanContainerBuildItem; import io.quarkus.arc.deployment.BeanContainerListenerBuildItem; +import io.quarkus.arc.deployment.RuntimeBeanBuildItem; +import io.quarkus.camel.core.runtime.CamelConfig; +import io.quarkus.camel.core.runtime.CamelConfig.BuildTime; +import io.quarkus.camel.core.runtime.CamelProducers; import io.quarkus.camel.core.runtime.CamelRuntime; -import io.quarkus.camel.core.runtime.CamelRuntimeProducer; import io.quarkus.camel.core.runtime.CamelTemplate; -import io.quarkus.camel.core.runtime.RuntimeRegistry; +import io.quarkus.camel.core.runtime.support.RuntimeRegistry; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.annotations.ExecutionTime; @@ -38,39 +43,20 @@ import io.quarkus.deployment.builditem.ShutdownContextBuildItem; import io.quarkus.deployment.recording.RecorderContext; import io.quarkus.runtime.RuntimeValue; -import io.quarkus.runtime.annotations.ConfigItem; -import io.quarkus.runtime.annotations.ConfigPhase; -import io.quarkus.runtime.annotations.ConfigRoot; class CamelInitProcessor { + @Inject ApplicationArchivesBuildItem applicationArchivesBuildItem; @Inject CombinedIndexBuildItem combinedIndexBuildItem; - - @ConfigRoot(phase = ConfigPhase.BUILD_TIME) - static class CamelConfig { - - /** - * The class of the CamelRuntime implementation - */ - @ConfigItem - Optional runtime; - } - - CamelConfig config; - - @Record(ExecutionTime.STATIC_INIT) - @BuildStep - AdditionalBeanBuildItem camelRuntimeProducer(BuildProducer listener, CamelTemplate template, - CamelRuntimeBuildItem runtimeBuildItem) { - listener.produce(new BeanContainerListenerBuildItem(template.initRuntimeInjection(runtimeBuildItem.getRuntime()))); - return new AdditionalBeanBuildItem(CamelRuntimeProducer.class); - } + @Inject + BuildTime buildTimeConfig; @Record(ExecutionTime.STATIC_INIT) - @BuildStep(applicationArchiveMarkers = CamelSupport.CAMEL_SERVICE_BASE_PATH) - CamelRuntimeBuildItem createInitTask(RecorderContext recorderContext, CamelTemplate template) { + @BuildStep(applicationArchiveMarkers = { CamelSupport.CAMEL_SERVICE_BASE_PATH, CamelSupport.CAMEL_ROOT_PACKAGE_DIRECTORY }) + CamelRuntimeBuildItem createInitTask(RecorderContext recorderContext, CamelTemplate template, + BuildProducer runtimeBeans) { Properties properties = new Properties(); Config configProvider = ConfigProvider.getConfig(); for (String property : configProvider.getPropertyNames()) { @@ -82,25 +68,61 @@ CamelRuntimeBuildItem createInitTask(RecorderContext recorderContext, CamelTempl } } - String clazz = config.runtime.orElse(CamelRuntime.class.getName()); - RuntimeValue runtime = recorderContext.newInstance(clazz); RuntimeRegistry registry = new RuntimeRegistry(); - List> builders = getInitRouteBuilderClasses().map(recorderContext::newInstance) + List> builders = getBuildTimeRouteBuilderClasses().map(recorderContext::newInstance) .collect(Collectors.toList()); - visitServices((name, type) -> registry.bind(name, type, recorderContext.newInstance(type.getName()))); + visitServices((name, type) -> { + LoggerFactory.getLogger(CamelInitProcessor.class).debug("Binding camel service {} with type {}", name, type); + registry.bind(name, type, + recorderContext.newInstance(type.getName())); + }); + + RuntimeValue camelRuntime = template.create(registry, properties, builders); + + runtimeBeans + .produce(RuntimeBeanBuildItem.builder(CamelRuntime.class).setRuntimeValue(camelRuntime).build()); - return new CamelRuntimeBuildItem(template.init(runtime, registry, properties, builders)); + return new CamelRuntimeBuildItem(camelRuntime); + } + + @Record(ExecutionTime.STATIC_INIT) + @BuildStep(applicationArchiveMarkers = { CamelSupport.CAMEL_SERVICE_BASE_PATH, CamelSupport.CAMEL_ROOT_PACKAGE_DIRECTORY }) + AdditionalBeanBuildItem createCamelProducers( + RecorderContext recorderContext, + CamelRuntimeBuildItem runtime, + CamelTemplate template, + BuildProducer listeners) { + + listeners + .produce(new BeanContainerListenerBuildItem(template.initRuntimeInjection(runtime.getRuntime()))); + + return AdditionalBeanBuildItem.unremovableOf(CamelProducers.class); + } + + @Record(ExecutionTime.STATIC_INIT) + @BuildStep(applicationArchiveMarkers = { CamelSupport.CAMEL_SERVICE_BASE_PATH, CamelSupport.CAMEL_ROOT_PACKAGE_DIRECTORY }) + void createInitTask( + BeanContainerBuildItem beanContainerBuildItem, + CamelRuntimeBuildItem runtime, + CamelTemplate template) throws Exception { + + template.init(beanContainerBuildItem.getValue(), runtime.getRuntime(), buildTimeConfig); } @Record(ExecutionTime.RUNTIME_INIT) - @BuildStep(applicationArchiveMarkers = CamelSupport.CAMEL_SERVICE_BASE_PATH) - void createRuntimeInitTask(CamelTemplate template, CamelRuntimeBuildItem runtime, ShutdownContextBuildItem shutdown) + @BuildStep(applicationArchiveMarkers = { CamelSupport.CAMEL_SERVICE_BASE_PATH, CamelSupport.CAMEL_ROOT_PACKAGE_DIRECTORY }) + void createRuntimeInitTask( + CamelTemplate template, + CamelRuntimeBuildItem runtime, + ShutdownContextBuildItem shutdown, + CamelConfig.Runtime runtimeConfig) throws Exception { - template.start(shutdown, runtime.getRuntime()); + + template.start(shutdown, runtime.getRuntime(), runtimeConfig); } - protected Stream getInitRouteBuilderClasses() { + protected Stream getBuildTimeRouteBuilderClasses() { Set allKnownImplementors = new HashSet<>(); allKnownImplementors.addAll( combinedIndexBuildItem.getIndex().getAllKnownImplementors(DotName.createSimple(RoutesBuilder.class.getName()))); diff --git a/extensions/camel/camel-core/deployment/src/main/java/io/quarkus/camel/core/deployment/CamelProcessor.java b/extensions/camel/camel-core/deployment/src/main/java/io/quarkus/camel/core/deployment/CamelProcessor.java index 50e5c7dd8296a..f4f94b6508589 100644 --- a/extensions/camel/camel-core/deployment/src/main/java/io/quarkus/camel/core/deployment/CamelProcessor.java +++ b/extensions/camel/camel-core/deployment/src/main/java/io/quarkus/camel/core/deployment/CamelProcessor.java @@ -9,6 +9,7 @@ import java.util.List; import java.util.Map; import java.util.Properties; +import java.util.stream.Collectors; import javax.inject.Inject; @@ -22,22 +23,32 @@ import org.apache.camel.component.file.strategy.GenericFileProcessStrategyFactory; import org.apache.camel.spi.ExchangeFormatter; 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.MethodInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import io.quarkus.camel.core.runtime.CamelConfig.BuildTime; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.ApplicationArchivesBuildItem; import io.quarkus.deployment.builditem.CombinedIndexBuildItem; import io.quarkus.deployment.builditem.FeatureBuildItem; +import io.quarkus.deployment.builditem.HotDeploymentConfigFileBuildItem; import io.quarkus.deployment.builditem.substrate.ReflectiveClassBuildItem; import io.quarkus.deployment.builditem.substrate.ReflectiveMethodBuildItem; +import io.quarkus.deployment.builditem.substrate.SubstrateConfigBuildItem; import io.quarkus.deployment.builditem.substrate.SubstrateResourceBuildItem; import io.quarkus.deployment.builditem.substrate.SubstrateResourceBundleBuildItem; +import io.quarkus.jaxb.deployment.JaxbEnabledBuildItem; import io.quarkus.jaxb.deployment.JaxbFileRootBuildItem; class CamelProcessor { + private static final List> CAMEL_REFLECTIVE_CLASSES = Arrays.asList( Endpoint.class, Consumer.class, @@ -45,8 +56,10 @@ class CamelProcessor { TypeConverter.class, ExchangeFormatter.class, GenericFileProcessStrategy.class); - private static final List> CAMEL_REFLECTIVE_ANNOTATIONS = Arrays.asList( - Converter.class); + + private static final List> CAMEL_REFLECTIVE_ANNOTATIONS = Arrays.asList(); + + private static final Class CAMEL_CONVERTER_ANNOTATION = Converter.class; @Inject BuildProducer reflectiveClass; @@ -60,10 +73,31 @@ class CamelProcessor { ApplicationArchivesBuildItem applicationArchivesBuildItem; @Inject CombinedIndexBuildItem combinedIndexBuildItem; + @Inject + BuildTime buildTimeConfig; @BuildStep JaxbFileRootBuildItem fileRoot() { - return new JaxbFileRootBuildItem("org/apache/camel"); + return new JaxbFileRootBuildItem(CamelSupport.CAMEL_ROOT_PACKAGE_DIRECTORY); + } + + @BuildStep + JaxbEnabledBuildItem handleJaxbSupport() { + return buildTimeConfig.disableJaxb ? null : new JaxbEnabledBuildItem(); + } + + @BuildStep + List handleXmlSupport() { + if (buildTimeConfig.disableXml) { + return null; + } else { + return Arrays.asList( + new ReflectiveClassBuildItem(false, false, + "com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl"), + new ReflectiveClassBuildItem(false, false, + "com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl"), + new ReflectiveClassBuildItem(false, false, "com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl")); + } } @BuildStep @@ -71,7 +105,24 @@ FeatureBuildItem feature() { return new FeatureBuildItem(FeatureBuildItem.CAMEL_CORE); } - @BuildStep(applicationArchiveMarkers = CamelSupport.CAMEL_SERVICE_BASE_PATH) + @BuildStep + SubstrateConfigBuildItem processSystemProperties() { + return SubstrateConfigBuildItem.builder() + .addNativeImageSystemProperty("CamelSimpleLRUCacheFactory", "true") + .build(); + } + + @BuildStep + List configFile() { + return buildTimeConfig.routesUris.stream() + .map(String::trim) + .filter(s -> s.startsWith("file:")) + .map(s -> s.substring("file:".length())) + .map(HotDeploymentConfigFileBuildItem::new) + .collect(Collectors.toList()); + } + + @BuildStep(applicationArchiveMarkers = { CamelSupport.CAMEL_SERVICE_BASE_PATH, CamelSupport.CAMEL_ROOT_PACKAGE_DIRECTORY }) void process() { IndexView view = combinedIndexBuildItem.getIndex(); @@ -97,9 +148,45 @@ void process() { } }); + Logger log = LoggerFactory.getLogger(CamelProcessor.class); + DotName converter = DotName.createSimple(Converter.class.getName()); + List converterClasses = view.getAnnotations(converter) + .stream() + .filter(ai -> ai.target().kind() == Kind.CLASS) + .filter(ai -> { + AnnotationValue av = ai.value("loader"); + boolean isLoader = av != null && av.asBoolean(); + // filter out camel-base converters which are automatically inlined in the CoreStaticTypeConverterLoader + // need to revisit with Camel 3.0.0-M3 which should improve this area + if (ai.target().asClass().name().toString().startsWith("org.apache.camel.converter.")) { + log.debug("Ignoring core " + ai + " " + ai.target().asClass().name()); + return false; + } else if (isLoader) { + log.debug("Ignoring " + ai + " " + ai.target().asClass().name()); + return false; + } else { + log.debug("Accepting " + ai + " " + ai.target().asClass().name()); + return true; + } + }) + .map(ai -> ai.target().asClass()) + .collect(Collectors.toList()); + log.debug("Converter classes: " + converterClasses); + converterClasses.forEach(ci -> addReflectiveClass(false, ci.name().toString())); + + view.getAnnotations(converter) + .stream() + .filter(ai -> ai.target().kind() == Kind.METHOD) + .filter(ai -> converterClasses.contains(ai.target().asMethod().declaringClass())) + .map(ai -> ai.target().asMethod()) + .forEach(this::addReflectiveMethod); + addReflectiveClass(false, GenericFile.class.getName()); addReflectiveClass(true, GenericFileProcessStrategyFactory.class.getName()); + CamelSupport.resources(applicationArchivesBuildItem, "META-INF/maven/org.apache.camel/camel-core") + .forEach(this::addResource); + addCamelServices(); } diff --git a/extensions/camel/camel-core/deployment/src/main/java/io/quarkus/camel/core/deployment/CamelRuntimeBuildItem.java b/extensions/camel/camel-core/deployment/src/main/java/io/quarkus/camel/core/deployment/CamelRuntimeBuildItem.java index 375430ca5f5da..70db2118113f9 100644 --- a/extensions/camel/camel-core/deployment/src/main/java/io/quarkus/camel/core/deployment/CamelRuntimeBuildItem.java +++ b/extensions/camel/camel-core/deployment/src/main/java/io/quarkus/camel/core/deployment/CamelRuntimeBuildItem.java @@ -1,17 +1,18 @@ package io.quarkus.camel.core.deployment; -import org.jboss.builder.item.SimpleBuildItem; - +import io.quarkus.builder.item.SimpleBuildItem; import io.quarkus.camel.core.runtime.CamelRuntime; +import io.quarkus.runtime.RuntimeValue; public final class CamelRuntimeBuildItem extends SimpleBuildItem { - private final CamelRuntime runtime; - public CamelRuntimeBuildItem(CamelRuntime runtime) { + private final RuntimeValue runtime; + + public CamelRuntimeBuildItem(RuntimeValue runtime) { this.runtime = runtime; } - public CamelRuntime getRuntime() { + public RuntimeValue getRuntime() { return runtime; } } diff --git a/extensions/camel/camel-core/deployment/src/main/java/io/quarkus/camel/core/deployment/CamelSupport.java b/extensions/camel/camel-core/deployment/src/main/java/io/quarkus/camel/core/deployment/CamelSupport.java index bad44aab12912..6163d82a45ed0 100644 --- a/extensions/camel/camel-core/deployment/src/main/java/io/quarkus/camel/core/deployment/CamelSupport.java +++ b/extensions/camel/camel-core/deployment/src/main/java/io/quarkus/camel/core/deployment/CamelSupport.java @@ -12,8 +12,11 @@ import io.quarkus.deployment.builditem.ApplicationArchivesBuildItem; public final class CamelSupport { + public static final String CAMEL_SERVICE_BASE_PATH = "META-INF/services/org/apache/camel"; + public static final String CAMEL_ROOT_PACKAGE_DIRECTORY = "org/apache/camel"; + private CamelSupport() { } diff --git a/extensions/camel/camel-core/deployment/src/main/java/io/quarkus/camel/core/deployment/devmode/CamelHotReplacementSetup.java b/extensions/camel/camel-core/deployment/src/main/java/io/quarkus/camel/core/deployment/devmode/CamelHotReplacementSetup.java new file mode 100644 index 0000000000000..c1a8570ac28ed --- /dev/null +++ b/extensions/camel/camel-core/deployment/src/main/java/io/quarkus/camel/core/deployment/devmode/CamelHotReplacementSetup.java @@ -0,0 +1,29 @@ +package io.quarkus.camel.core.deployment.devmode; + +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.TimeUnit; + +import io.quarkus.deployment.devmode.HotReplacementContext; +import io.quarkus.deployment.devmode.HotReplacementSetup; + +public class CamelHotReplacementSetup implements HotReplacementSetup { + + private static final long TWO_SECS = TimeUnit.SECONDS.toMillis(2); + + @Override + public void setupHotDeployment(HotReplacementContext context) { + Timer timer = new Timer(true); + timer.schedule(new TimerTask() { + @Override + public void run() { + try { + context.doScan(); + } catch (Exception e) { + e.printStackTrace(); + } + } + }, TWO_SECS, TWO_SECS); + } + +} diff --git a/extensions/camel/camel-core/deployment/src/main/resources/META-INF/services/io.quarkus.deployment.devmode.HotReplacementSetup b/extensions/camel/camel-core/deployment/src/main/resources/META-INF/services/io.quarkus.deployment.devmode.HotReplacementSetup new file mode 100644 index 0000000000000..2d89d8156fa1d --- /dev/null +++ b/extensions/camel/camel-core/deployment/src/main/resources/META-INF/services/io.quarkus.deployment.devmode.HotReplacementSetup @@ -0,0 +1 @@ +io.quarkus.camel.core.deployment.devmode.CamelHotReplacementSetup \ No newline at end of file diff --git a/extensions/camel/camel-core/runtime/pom.xml b/extensions/camel/camel-core/runtime/pom.xml index 390d9b5210b36..a8f033cdd6463 100644 --- a/extensions/camel/camel-core/runtime/pom.xml +++ b/extensions/camel/camel-core/runtime/pom.xml @@ -10,7 +10,7 @@ 4.0.0 - quarkus-camel-core-runtime + quarkus-camel-core Quarkus - Camel - Core - Runtime @@ -18,11 +18,11 @@ io.quarkus - quarkus-core-runtime + quarkus-core io.quarkus - quarkus-arc-runtime + quarkus-arc com.oracle.substratevm @@ -35,22 +35,20 @@ camel-core - org.apache.camel - camel-headersmap - - - org.apache.logging.log4j - * - - + io.quarkus + quarkus-jaxb + + + io.quarkus + quarkus-caffeine - - maven-dependency-plugin + io.quarkus + quarkus-bootstrap-maven-plugin maven-compiler-plugin diff --git a/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/CamelConfig.java b/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/CamelConfig.java new file mode 100644 index 0000000000000..3d2e457a5c08a --- /dev/null +++ b/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/CamelConfig.java @@ -0,0 +1,55 @@ +package io.quarkus.camel.core.runtime; + +import java.util.List; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; + +public class CamelConfig { + + @ConfigRoot(name = "camel", phase = ConfigPhase.BUILD_AND_RUN_TIME_FIXED) + public static class BuildTime { + + /** + * Uri to an xml containing camel routes to be loaded and initialized at build time. + */ + @ConfigItem + public List routesUris; + + /** + * Defer Camel context initialization phase until runtime. + */ + @ConfigItem(defaultValue = "false") + public boolean deferInitPhase; + + /** + * Camel jaxb support is enabled by default, but in order to trim + * down the size of applications, it is possible to disable jaxb support + * at runtime. This is useful when routes at loaded at build time and + * thus the camel route model is not used at runtime anymore. + */ + @ConfigItem + public boolean disableJaxb; + + /** + * Disable XML support in various parts of Camel. + * Because xml parsing using xerces/xalan libraries can consume + * a lot of code space in the native binary (and a lot of cpu resources + * when building), this allows to disable both libraries. + */ + @ConfigItem + public boolean disableXml; + } + + @ConfigRoot(name = "camel", phase = ConfigPhase.RUN_TIME) + public static class Runtime { + + /** + * Dump loaded routes when starting + */ + @ConfigItem(defaultValue = "false") + public boolean dumpRoutes; + } + +} diff --git a/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/CamelProducers.java b/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/CamelProducers.java new file mode 100644 index 0000000000000..d5b9c36caf9d1 --- /dev/null +++ b/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/CamelProducers.java @@ -0,0 +1,38 @@ +package io.quarkus.camel.core.runtime; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Produces; + +import org.apache.camel.CamelContext; +import org.apache.camel.spi.Registry; + +@ApplicationScoped +public class CamelProducers { + + CamelRuntime camelRuntime; + + @Produces + public CamelContext getCamelContext() { + return camelRuntime.getContext(); + } + + @Produces + public Registry getCamelRegistry() { + return camelRuntime.getRegistry(); + } + + @Produces + public CamelConfig.BuildTime getCamelBuildTimeConfig() { + return camelRuntime.getBuildTimeConfig(); + } + + @Produces + public CamelConfig.Runtime getCamelRuntimeConfig() { + return camelRuntime.getRuntimeConfig(); + } + + public void setCamelRuntime(CamelRuntime camelRuntime) { + this.camelRuntime = camelRuntime; + } + +} diff --git a/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/CamelRuntime.java b/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/CamelRuntime.java index 10185002e1e8f..eacbd1eaf845e 100644 --- a/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/CamelRuntime.java +++ b/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/CamelRuntime.java @@ -17,162 +17,33 @@ package io.quarkus.camel.core.runtime; -import java.io.InputStream; -import java.util.List; import java.util.Properties; import org.apache.camel.CamelContext; -import org.apache.camel.Route; -import org.apache.camel.RoutesBuilder; -import org.apache.camel.RuntimeCamelException; -import org.apache.camel.component.properties.PropertiesComponent; -import org.apache.camel.impl.AbstractCamelContext; -import org.apache.camel.impl.DefaultCamelContext; -import org.apache.camel.model.ModelCamelContext; -import org.apache.camel.model.ModelHelper; -import org.apache.camel.model.RouteDefinition; -import org.apache.camel.support.ResourceHelper; -import org.apache.camel.support.service.ServiceSupport; -import org.apache.camel.util.ObjectHelper; +import org.apache.camel.spi.Registry; -public class CamelRuntime extends ServiceSupport { +public interface CamelRuntime { - public static final String PFX_CAMEL = "camel."; - public static final String PFX_CAMEL_PROPERTIES = PFX_CAMEL + "component.properties."; - public static final String PFX_CAMEL_CONTEXT = PFX_CAMEL + "context."; + String PFX_CAMEL = "camel."; + String PFX_CAMEL_PROPERTIES = PFX_CAMEL + "component.properties."; + String PFX_CAMEL_CONTEXT = PFX_CAMEL + "context."; - public static final String PROP_CAMEL_RUNTIME = PFX_CAMEL + "runtime"; - public static final String PROP_CAMEL_ROUTES = PFX_CAMEL + "routes."; - public static final String PROP_CAMEL_ROUTES_DUMP = PROP_CAMEL_ROUTES + "dump"; - public static final String PROP_CAMEL_ROUTES_LOCATIONS = PROP_CAMEL_ROUTES + "locations"; + CamelContext getContext(); - protected RuntimeRegistry registry; - protected Properties properties; - protected AbstractCamelContext context; - protected List builders; + Registry getRegistry(); - public void bind(String name, Object object) { - registry.bind(name, object); - } + CamelConfig.BuildTime getBuildTimeConfig(); - public void bind(String name, Class type, Object object) { - registry.bind(name, type, object); - } + CamelConfig.Runtime getRuntimeConfig(); - public void doInit() { - try { - this.context = createContext(); - this.context.setRegistry(registry); + void init(CamelConfig.BuildTime buildTimeConfig); - // Configure the camel context using properties in the form: - // - // camel.context.${name} = ${value} - // - RuntimeSupport.bindProperties(properties, context, PFX_CAMEL_CONTEXT); + void start(CamelConfig.Runtime runtimeConfig) throws Exception; - context.setLoadTypeConverters(false); - context.getModelJAXBContextFactory().newJAXBContext(); - } catch (Exception e) { - throw RuntimeCamelException.wrapRuntimeCamelException(e); - } - } + void stop() throws Exception; - public void doStart() throws Exception { - log.info("Apache Camel {} (CamelContext: {}) is starting", context.getVersion(), context.getName()); + void addProperties(Properties properties); - PropertiesComponent pc = createPropertiesComponent(properties); - RuntimeSupport.bindProperties(pc.getInitialProperties(), pc, PFX_CAMEL_PROPERTIES); - context.addComponent("properties", pc); - - configureContext(context); - loadRoutes(context); - - context.start(); - - if (Boolean.parseBoolean(getProperty(PROP_CAMEL_ROUTES_DUMP))) { - dumpRoutes(); - } - } - - @Override - protected void doStop() throws Exception { - context.shutdown(); - } - - protected void loadRoutes(CamelContext context) throws Exception { - for (RoutesBuilder b : builders) { - if (b instanceof RouteBuilderExt) { - ((RouteBuilderExt) b).setRegistry(registry); - } - context.addRoutes(b); - } - - String routesUri = getProperty(PROP_CAMEL_ROUTES_LOCATIONS); - if (ObjectHelper.isNotEmpty(routesUri)) { - log.info("routesUri: {}", routesUri); - - ModelCamelContext mcc = context.adapt(ModelCamelContext.class); - - try (InputStream is = ResourceHelper.resolveMandatoryResourceAsInputStream(getContext(), routesUri)) { - mcc.addRouteDefinitions(mcc.loadRoutesDefinition(is).getRoutes()); - } - } - } - - protected String getProperty(String name) throws Exception { - return context.resolvePropertyPlaceholders(context.getPropertyPrefixToken() + name + context.getPropertySuffixToken()); - } - - protected DefaultCamelContext createContext() { - return new FastCamelContext(); - } - - public void setRegistry(RuntimeRegistry registry) { - this.registry = registry; - } - - public void setProperties(Properties properties) { - this.properties = properties; - } - - public void setBuilders(List builders) { - this.builders = builders; - } - - public CamelContext getContext() { - return context; - } - - protected PropertiesComponent createPropertiesComponent(Properties initialPoperties) { - PropertiesComponent pc = new PropertiesComponent(); - pc.setInitialProperties(initialPoperties); - - RuntimeSupport.bindProperties(properties, pc, PFX_CAMEL_PROPERTIES); - - return pc; - } - - protected void configureContext(CamelContext context) { - } - - protected void dumpRoutes() { - long t0 = System.nanoTime(); - try { - for (Route route : getContext().getRoutes()) { - RouteDefinition def = (RouteDefinition) route.getRouteContext().getRoute(); - System.err.println("Route: " + def); - String xml = ModelHelper.dumpModelAsXml(getContext(), def); - System.err.println("Xml: " + xml); - } - } catch (Throwable t) { - // ignore - System.err.println("Error dumping route xml: " + t.getClass().getName() + ": " + t.getMessage()); - for (StackTraceElement e : t.getStackTrace()) { - System.err.println(" " + e.getClassName() + " " + e.getMethodName() + " " + e.getLineNumber()); - } - } - long t1 = System.nanoTime(); - System.err.println("Dump routes: " + (t1 - t0) + " ns"); - } + void addProperty(String key, Object value); } diff --git a/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/CamelRuntimeProducer.java b/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/CamelRuntimeProducer.java deleted file mode 100644 index 19c1e5be2796f..0000000000000 --- a/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/CamelRuntimeProducer.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.quarkus.camel.core.runtime; - -import javax.enterprise.context.ApplicationScoped; -import javax.enterprise.inject.Produces; - -@ApplicationScoped -public class CamelRuntimeProducer { - - CamelRuntime camelRuntime; - - @Produces - public CamelRuntime getCamelRuntime() { - return camelRuntime; - } - - public void setCamelRuntime(CamelRuntime camelRuntime) { - this.camelRuntime = camelRuntime; - } -} diff --git a/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/CamelTemplate.java b/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/CamelTemplate.java index 2b8d873be9a11..3004190cd70df 100644 --- a/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/CamelTemplate.java +++ b/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/CamelTemplate.java @@ -5,9 +5,11 @@ import java.util.stream.Collectors; import org.apache.camel.RoutesBuilder; +import org.apache.camel.spi.Registry; import io.quarkus.arc.runtime.BeanContainer; import io.quarkus.arc.runtime.BeanContainerListener; +import io.quarkus.camel.core.runtime.support.FastCamelRuntime; import io.quarkus.runtime.RuntimeValue; import io.quarkus.runtime.ShutdownContext; import io.quarkus.runtime.annotations.Template; @@ -15,31 +17,45 @@ @Template public class CamelTemplate { - public CamelRuntime init( - RuntimeValue iruntime, - RuntimeRegistry registry, + public RuntimeValue create( + Registry registry, Properties properties, List> builders) { - CamelRuntime runtime = CamelRuntime.class.cast(iruntime.getValue()); + + FastCamelRuntime runtime = new FastCamelRuntime(); + runtime.setRegistry(registry); runtime.setProperties(properties); runtime.setBuilders(builders.stream() .map(RuntimeValue::getValue) .map(RoutesBuilder.class::cast) .collect(Collectors.toList())); - runtime.init(); - return runtime; + + return new RuntimeValue<>(runtime); + } + + public void init( + BeanContainer beanContainer, + RuntimeValue runtime, + CamelConfig.BuildTime buildTimeConfig) throws Exception { + + ((FastCamelRuntime) runtime.getValue()).setBeanContainer(beanContainer); + runtime.getValue().init(buildTimeConfig); } - public void start(final ShutdownContext shutdown, final CamelRuntime runtime) throws Exception { - runtime.start(); + public void start( + ShutdownContext shutdown, + RuntimeValue runtime, + CamelConfig.Runtime runtimeConfig) throws Exception { + + runtime.getValue().start(runtimeConfig); //in development mode undertow is started eagerly shutdown.addShutdownTask(new Runnable() { @Override public void run() { try { - runtime.stop(); + runtime.getValue().stop(); } catch (Exception e) { throw new RuntimeException(e); } @@ -47,12 +63,8 @@ public void run() { }); } - public BeanContainerListener initRuntimeInjection(CamelRuntime runtime) { - return new BeanContainerListener() { - @Override - public void created(BeanContainer container) { - container.instance(CamelRuntimeProducer.class).setCamelRuntime(runtime); - } - }; + public BeanContainerListener initRuntimeInjection(RuntimeValue runtime) { + return container -> container.instance(CamelProducers.class).setCamelRuntime(runtime.getValue()); } + } diff --git a/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/InitializedEvent.java b/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/InitializedEvent.java new file mode 100644 index 0000000000000..f84418930b1cb --- /dev/null +++ b/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/InitializedEvent.java @@ -0,0 +1,5 @@ +package io.quarkus.camel.core.runtime; + +public class InitializedEvent { + +} diff --git a/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/InitializingEvent.java b/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/InitializingEvent.java new file mode 100644 index 0000000000000..9e3adb6d1133f --- /dev/null +++ b/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/InitializingEvent.java @@ -0,0 +1,5 @@ +package io.quarkus.camel.core.runtime; + +public class InitializingEvent { + +} diff --git a/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/RouteBuilderExt.java b/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/RouteBuilderExt.java deleted file mode 100644 index 724fb2a96ebf4..0000000000000 --- a/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/RouteBuilderExt.java +++ /dev/null @@ -1,20 +0,0 @@ -package io.quarkus.camel.core.runtime; - -import org.apache.camel.builder.RouteBuilder; - -public abstract class RouteBuilderExt extends RouteBuilder { - - private RuntimeRegistry registry; - - public void setRegistry(RuntimeRegistry registry) { - this.registry = registry; - } - - public void bind(String name, Object object) { - registry.bind(name, object); - } - - public void bind(String name, Class clazz, Object object) { - registry.bind(name, clazz, object); - } -} diff --git a/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/StartedEvent.java b/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/StartedEvent.java new file mode 100644 index 0000000000000..0734fd0f0ce76 --- /dev/null +++ b/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/StartedEvent.java @@ -0,0 +1,5 @@ +package io.quarkus.camel.core.runtime; + +public class StartedEvent { + +} diff --git a/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/StartingEvent.java b/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/StartingEvent.java new file mode 100644 index 0000000000000..a56e38157a1c1 --- /dev/null +++ b/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/StartingEvent.java @@ -0,0 +1,5 @@ +package io.quarkus.camel.core.runtime; + +public class StartingEvent { + +} diff --git a/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/StoppedEvent.java b/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/StoppedEvent.java new file mode 100644 index 0000000000000..4ab1fb9bfe617 --- /dev/null +++ b/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/StoppedEvent.java @@ -0,0 +1,5 @@ +package io.quarkus.camel.core.runtime; + +public class StoppedEvent { + +} diff --git a/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/StoppingEvent.java b/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/StoppingEvent.java new file mode 100644 index 0000000000000..987bcf8ac8d71 --- /dev/null +++ b/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/StoppingEvent.java @@ -0,0 +1,5 @@ +package io.quarkus.camel.core.runtime; + +public class StoppingEvent { + +} diff --git a/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/graal/CamelSubstitutions.java b/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/graal/CamelSubstitutions.java index 1dd564943a561..95ea5306daa75 100644 --- a/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/graal/CamelSubstitutions.java +++ b/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/graal/CamelSubstitutions.java @@ -1,14 +1,114 @@ package io.quarkus.camel.core.runtime.graal; +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; import java.net.InetAddress; import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.WeakHashMap; +import org.apache.camel.Producer; +import org.apache.camel.builder.xml.XPathBuilder; +import org.apache.camel.converter.jaxp.DomConverter; +import org.apache.camel.converter.jaxp.StaxConverter; +import org.apache.camel.converter.jaxp.XmlConverter; +import org.apache.camel.support.IntrospectionSupport.ClassInfo; +import org.apache.camel.support.LRUCacheFactory; + +import com.oracle.svm.core.annotate.Alias; +import com.oracle.svm.core.annotate.RecomputeFieldValue; import com.oracle.svm.core.annotate.Substitute; import com.oracle.svm.core.annotate.TargetClass; class CamelSubstitutions { } +@TargetClass(className = "com.sun.beans.WeakCache") +@Substitute +final class Target_com_sun_beans_WeakCache { + + private Map> map = new WeakHashMap<>(); + + @Substitute + public Target_com_sun_beans_WeakCache() { + } + + @Substitute + public V get(K key) { + Reference reference = this.map.get(key); + if (reference == null) { + return null; + } + V value = reference.get(); + if (value == null) { + this.map.remove(key); + } + return value; + } + + @Substitute + public void put(K key, V value) { + if (value != null) { + this.map.put(key, new WeakReference(value)); + } else { + this.map.remove(key); + } + } + + @Substitute + public void clear() { + this.map.clear(); + } + +} + +@TargetClass(className = "java.beans.Introspector") +final class Target_java_beans_Introspector { + + @Alias + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FromAlias) + private static Target_com_sun_beans_WeakCache, Method[]> declaredMethodCache = new Target_com_sun_beans_WeakCache<>(); + +} + +@TargetClass(className = "org.apache.camel.support.IntrospectionSupport") +final class Target_org_apache_camel_support_IntrospectionSupport { + + @Alias + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FromAlias) + private static Map, ClassInfo> CACHE = LRUCacheFactory.newLRUWeakCache(256); + +} + +@TargetClass(className = "org.apache.camel.component.bean.BeanInfo") +final class Target_org_apache_camel_component_bean_BeanInfo { + + @Alias + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FromAlias) + private static List EXCLUDED_METHODS; + + static { + EXCLUDED_METHODS = new ArrayList<>(); + // exclude all java.lang.Object methods as we dont want to invoke them + EXCLUDED_METHODS.addAll(Arrays.asList(Object.class.getMethods())); + // exclude all java.lang.reflect.Proxy methods as we dont want to invoke them + EXCLUDED_METHODS.addAll(Arrays.asList(Proxy.class.getMethods())); + try { + // but keep toString as this method is okay + EXCLUDED_METHODS.remove(Object.class.getDeclaredMethod("toString")); + EXCLUDED_METHODS.remove(Proxy.class.getDeclaredMethod("toString")); + } catch (Throwable e) { + // ignore + } + } + +} + @TargetClass(className = "org.apache.camel.util.HostUtils") final class Target_org_apache_camel_util_HostUtils { @@ -17,3 +117,46 @@ private static InetAddress chooseAddress() throws UnknownHostException { return InetAddress.getByName("0.0.0.0"); } } + +@TargetClass(className = "org.apache.camel.builder.xml.XPathBuilder", onlyWith = XmlDisabled.class) +final class Target_org_apache_camel_builder_xml_XPathBuilder { + + @Substitute + public static XPathBuilder xpath(String text) { + throw new UnsupportedOperationException(); + } + + @Substitute + public static XPathBuilder xpath(String text, Class resultType) { + throw new UnsupportedOperationException(); + } + +} + +@TargetClass(className = "org.apache.camel.component.validator.ValidatorEndpoint", onlyWith = XmlDisabled.class) +final class Target_org_apache_camel_component_validator_ValidatorEndpoint { + + @Substitute + public Producer createProducer() throws Exception { + throw new UnsupportedOperationException(); + } +} + +@TargetClass(className = "org.apache.camel.impl.converter.CoreStaticTypeConverterLoader", onlyWith = XmlDisabled.class) +final class Target_org_apache_camel_impl_converter_CoreStaticTypeConverterLoader { + + @Substitute + private XmlConverter getXmlConverter() { + throw new UnsupportedOperationException(); + } + + @Substitute + private DomConverter getDomConverter() { + throw new UnsupportedOperationException(); + } + + @Substitute + private StaxConverter getStaxConverter() { + throw new UnsupportedOperationException(); + } +} diff --git a/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/graal/JaxbDisabled.java b/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/graal/JaxbDisabled.java new file mode 100644 index 0000000000000..2b619c214869c --- /dev/null +++ b/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/graal/JaxbDisabled.java @@ -0,0 +1,15 @@ +package io.quarkus.camel.core.runtime.graal; + +import java.util.function.BooleanSupplier; + +import org.eclipse.microprofile.config.ConfigProvider; + +public final class JaxbDisabled implements BooleanSupplier { + + @Override + public boolean getAsBoolean() { + String val = ConfigProvider.getConfig().getValue("quarkus.camel.disable-jaxb", String.class); + return Boolean.parseBoolean(val); + } + +} diff --git a/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/graal/XmlDisabled.java b/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/graal/XmlDisabled.java new file mode 100644 index 0000000000000..6f1a28d07ed7a --- /dev/null +++ b/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/graal/XmlDisabled.java @@ -0,0 +1,15 @@ +package io.quarkus.camel.core.runtime.graal; + +import java.util.function.BooleanSupplier; + +import org.eclipse.microprofile.config.ConfigProvider; + +public final class XmlDisabled implements BooleanSupplier { + + @Override + public boolean getAsBoolean() { + String val = ConfigProvider.getConfig().getValue("quarkus.camel.disable-xml", String.class); + return Boolean.parseBoolean(val); + } + +} diff --git a/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/support/BeanManagerHelper.java b/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/support/BeanManagerHelper.java new file mode 100644 index 0000000000000..34dcccd54446e --- /dev/null +++ b/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/support/BeanManagerHelper.java @@ -0,0 +1,78 @@ +/* + * 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.camel.core.runtime.support; + +import static java.util.stream.Collectors.toSet; + +import java.lang.annotation.Annotation; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.enterprise.inject.Vetoed; +import javax.enterprise.inject.spi.Bean; +import javax.enterprise.inject.spi.BeanAttributes; +import javax.enterprise.inject.spi.BeanManager; + +import io.quarkus.arc.Arc; + +@Vetoed +final class BeanManagerHelper { + + private BeanManagerHelper() { + } + + static Set getReferencesByType(Class type, Annotation... qualifiers) { + BeanManager manager = Arc.container().beanManager(); + return getReferencesByType(manager, type, qualifiers); + } + + static Optional getReferenceByName(String name, Class type) { + BeanManager manager = Arc.container().beanManager(); + return getReferenceByName(manager, name, type); + } + + static Map getReferencesByTypeWithName(Class type, Annotation... qualifiers) { + BeanManager manager = Arc.container().beanManager(); + return getReferencesByTypeWithName(manager, type, qualifiers); + } + + static Set getReferencesByType(BeanManager manager, Class type, Annotation... qualifiers) { + return manager.getBeans(type, qualifiers).stream() + .map(bean -> getReference(manager, type, bean)) + .collect(toSet()); + } + + static Optional getReferenceByName(BeanManager manager, String name, Class type) { + return Optional.of(manager.getBeans(name)) + .> map(manager::resolve) + .map(bean -> getReference(manager, type, bean)); + } + + static T getReference(BeanManager manager, Class type, Bean bean) { + return type.cast(manager.getReference(bean, type, manager.createCreationalContext(bean))); + } + + static Map getReferencesByTypeWithName(BeanManager manager, Class type, Annotation... qualifiers) { + return manager.getBeans(type, qualifiers).stream() + .collect(Collectors.toMap( + BeanAttributes::getName, + b -> getReference(manager, type, b))); + } + +} diff --git a/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/FastCamelContext.java b/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/support/FastCamelContext.java similarity index 80% rename from extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/FastCamelContext.java rename to extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/support/FastCamelContext.java index 2767dd90ca8e8..b7e4d22ea78cb 100644 --- a/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/FastCamelContext.java +++ b/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/support/FastCamelContext.java @@ -1,16 +1,16 @@ -package io.quarkus.camel.core.runtime; +package io.quarkus.camel.core.runtime.support; +import java.util.Collection; import java.util.Properties; import org.apache.camel.CamelContext; import org.apache.camel.CamelContextAware; import org.apache.camel.Component; -import org.apache.camel.component.headersmap.FastHeadersMapFactory; import org.apache.camel.impl.DefaultCamelContext; +import org.apache.camel.model.RouteDefinition; import org.apache.camel.spi.ComponentResolver; import org.apache.camel.spi.DataFormat; import org.apache.camel.spi.DataFormatResolver; -import org.apache.camel.spi.HeadersMapFactory; import org.apache.camel.spi.Language; import org.apache.camel.spi.LanguageResolver; import org.apache.camel.spi.ManagementNameStrategy; @@ -19,6 +19,8 @@ import org.apache.camel.spi.ShutdownStrategy; import org.apache.camel.spi.UuidGenerator; +import io.quarkus.camel.core.runtime.CamelRuntime; + public class FastCamelContext extends DefaultCamelContext { public FastCamelContext() { @@ -46,11 +48,6 @@ protected UuidGenerator createUuidGenerator() { return new FastUuidGenerator(); } - @Override - protected HeadersMapFactory createHeadersMapFactory() { - return new FastHeadersMapFactory(); - } - @Override protected ComponentResolver createComponentResolver() { return (name, context) -> resolve(Component.class, "component", name, context); @@ -82,14 +79,30 @@ protected T resolve(Class clazz, String type, String name, CamelContext c ((CamelContextAware) result).setCamelContext(context); } PropertiesComponent comp = getPropertiesComponent(); - if (comp != null && comp instanceof org.apache.camel.component.properties.PropertiesComponent) { + if (comp instanceof org.apache.camel.component.properties.PropertiesComponent) { Properties props = ((org.apache.camel.component.properties.PropertiesComponent) comp).getInitialProperties(); if (props != null) { String pfx = CamelRuntime.PFX_CAMEL + type + "." + name; - log.info("Binding {} {} with prefix {}", type, name, pfx); + log.debug("Binding {} {} with prefix {}", type, name, pfx); RuntimeSupport.bindProperties(this, props, result, pfx); } } return result; } + + public void reifyRoutes() throws Exception { + for (RouteDefinition rd : getRouteDefinitions()) { + startRoute(rd); + } + } + + protected void startRouteDefinitions(Collection list) throws Exception { + } + + @Override + public void doInit() { + super.doInit(); + + forceLazyInitialization(); + } } diff --git a/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/support/FastCamelRuntime.java b/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/support/FastCamelRuntime.java new file mode 100644 index 0000000000000..939f69562fe82 --- /dev/null +++ b/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/support/FastCamelRuntime.java @@ -0,0 +1,229 @@ +package io.quarkus.camel.core.runtime.support; + +import java.io.InputStream; +import java.util.List; +import java.util.Properties; +import java.util.stream.Collectors; + +import org.apache.camel.CamelContext; +import org.apache.camel.Route; +import org.apache.camel.RoutesBuilder; +import org.apache.camel.RuntimeCamelException; +import org.apache.camel.ShutdownableService; +import org.apache.camel.component.properties.PropertiesComponent; +import org.apache.camel.model.ModelCamelContext; +import org.apache.camel.model.RouteDefinition; +import org.apache.camel.spi.Registry; +import org.apache.camel.support.ResourceHelper; +import org.apache.camel.util.ObjectHelper; +import org.graalvm.nativeimage.ImageInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.runtime.BeanContainer; +import io.quarkus.camel.core.runtime.CamelConfig.BuildTime; +import io.quarkus.camel.core.runtime.CamelConfig.Runtime; +import io.quarkus.camel.core.runtime.CamelRuntime; +import io.quarkus.camel.core.runtime.InitializedEvent; +import io.quarkus.camel.core.runtime.InitializingEvent; +import io.quarkus.camel.core.runtime.StartedEvent; +import io.quarkus.camel.core.runtime.StartingEvent; +import io.quarkus.camel.core.runtime.StoppedEvent; +import io.quarkus.camel.core.runtime.StoppingEvent; + +public class FastCamelRuntime implements CamelRuntime { + + private static final Logger log = LoggerFactory.getLogger(FastCamelRuntime.class); + + protected CamelContext context; + protected BeanContainer beanContainer; + protected Registry registry; + protected Properties properties; + protected List builders; + protected BuildTime buildTimeConfig; + protected Runtime runtimeConfig; + + @Override + public void init(BuildTime buildTimeConfig) { + this.buildTimeConfig = buildTimeConfig; + if (!buildTimeConfig.deferInitPhase) { + doInit(); + } + } + + @Override + public void start(Runtime runtimeConfig) throws Exception { + this.runtimeConfig = runtimeConfig; + if (buildTimeConfig.deferInitPhase) { + doInit(); + } + doStart(); + } + + @Override + public void stop() throws Exception { + doStop(); + } + + public void doInit() { + try { + this.context = createContext(); + + // Configure the camel context using properties in the form: + // + // camel.context.${name} = ${value} + // + RuntimeSupport.bindProperties(properties, context, PFX_CAMEL_CONTEXT); + + context.setLoadTypeConverters(false); + + PropertiesComponent pc = createPropertiesComponent(properties); + RuntimeSupport.bindProperties(pc.getInitialProperties(), pc, PFX_CAMEL_PROPERTIES); + context.addComponent("properties", pc); + + this.context.getTypeConverterRegistry().setInjector(this.context.getInjector()); + fireEvent(InitializingEvent.class, new InitializingEvent()); + if (buildTimeConfig.disableJaxb) { + this.context.setModelJAXBContextFactory(() -> { + throw new UnsupportedOperationException(); + }); + } else { + // The creation of the JAXB context is very time consuming, so always prepare it + // when running in native mode, but lazy create it in java mode so that we don't + // waste time if using java routes + if (ImageInfo.inImageBuildtimeCode()) { + context.adapt(ModelCamelContext.class).getModelJAXBContextFactory().newJAXBContext(); + } + } + this.context.init(); + fireEvent(InitializedEvent.class, new InitializedEvent()); + + loadRoutes(context); + } catch (Exception e) { + throw RuntimeCamelException.wrapRuntimeCamelException(e); + } + } + + public void doStart() throws Exception { + fireEvent(StartingEvent.class, new StartingEvent()); + context.start(); + fireEvent(StartedEvent.class, new StartedEvent()); + + if (runtimeConfig.dumpRoutes) { + dumpRoutes(); + } + } + + protected void doStop() throws Exception { + fireEvent(StoppingEvent.class, new StoppingEvent()); + context.stop(); + fireEvent(StoppedEvent.class, new StoppedEvent()); + if (context instanceof ShutdownableService) { + ((ShutdownableService) context).shutdown(); + } + } + + protected void loadRoutes(CamelContext context) throws Exception { + for (RoutesBuilder b : builders) { + context.addRoutes(b); + } + + List routesUris = buildTimeConfig.routesUris.stream() + .filter(ObjectHelper::isNotEmpty) + .collect(Collectors.toList()); + if (ObjectHelper.isNotEmpty(routesUris)) { + log.debug("Loading xml routes from {}", routesUris); + ModelCamelContext mcc = context.adapt(ModelCamelContext.class); + for (String routesUri : routesUris) { + // TODO: if pointing to a directory, we should load all xmls in it + // (maybe with glob support in it to be complete) + try (InputStream is = ResourceHelper.resolveMandatoryResourceAsInputStream(mcc, routesUri.trim())) { + mcc.addRouteDefinitions(is); + } + } + } else { + log.debug("No xml routes configured"); + } + + context.adapt(FastCamelContext.class).reifyRoutes(); + } + + protected CamelContext createContext() { + FastCamelContext context = new FastCamelContext(); + context.setRegistry(registry); + return context; + } + + protected void fireEvent(Class clazz, T event) { + Arc.container().beanManager().getEvent().select(clazz).fire(event); + } + + public void setBeanContainer(BeanContainer beanContainer) { + this.beanContainer = beanContainer; + } + + public void setRegistry(Registry registry) { + this.registry = registry; + } + + public void setProperties(Properties properties) { + this.properties = properties; + } + + @Override + public void addProperties(Properties properties) { + this.properties.putAll(properties); + } + + @Override + public void addProperty(String key, Object value) { + this.properties.put(key, value); + } + + public void setBuilders(List builders) { + this.builders = builders; + } + + public CamelContext getContext() { + return context; + } + + @Override + public Registry getRegistry() { + return registry; + } + + @Override + public BuildTime getBuildTimeConfig() { + return buildTimeConfig; + } + + @Override + public Runtime getRuntimeConfig() { + return runtimeConfig; + } + + protected PropertiesComponent createPropertiesComponent(Properties initialPoperties) { + PropertiesComponent pc = new PropertiesComponent(); + pc.setInitialProperties(initialPoperties); + + RuntimeSupport.bindProperties(properties, pc, PFX_CAMEL_PROPERTIES); + + return pc; + } + + protected void dumpRoutes() { + List routes = getContext().getRoutes(); + if (routes.isEmpty()) { + log.info("No route definitions"); + } else { + log.info("Route definitions:"); + for (Route route : routes) { + RouteDefinition def = (RouteDefinition) route.getRouteContext().getRoute(); + log.info(def.toString()); + } + } + } + +} diff --git a/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/FastUuidGenerator.java b/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/support/FastUuidGenerator.java similarity index 95% rename from extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/FastUuidGenerator.java rename to extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/support/FastUuidGenerator.java index e977c4125d979..2cc8f898f2208 100644 --- a/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/FastUuidGenerator.java +++ b/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/support/FastUuidGenerator.java @@ -1,4 +1,4 @@ -package io.quarkus.camel.core.runtime; +package io.quarkus.camel.core.runtime.support; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicLong; diff --git a/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/NoShutdownStrategy.java b/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/support/NoShutdownStrategy.java similarity index 98% rename from extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/NoShutdownStrategy.java rename to extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/support/NoShutdownStrategy.java index 24614b4891afe..8acc04e02274a 100644 --- a/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/NoShutdownStrategy.java +++ b/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/support/NoShutdownStrategy.java @@ -1,4 +1,4 @@ -package io.quarkus.camel.core.runtime; +package io.quarkus.camel.core.runtime.support; import java.util.List; import java.util.concurrent.TimeUnit; diff --git a/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/RuntimeRegistry.java b/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/support/RuntimeRegistry.java similarity index 86% rename from extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/RuntimeRegistry.java rename to extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/support/RuntimeRegistry.java index 0bbb6d26b0ce1..7c6a2dceeebd3 100644 --- a/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/RuntimeRegistry.java +++ b/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/support/RuntimeRegistry.java @@ -14,18 +14,24 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.quarkus.camel.core.runtime; +package io.quarkus.camel.core.runtime.support; import java.util.HashMap; import java.util.HashSet; import java.util.Map; +import java.util.Optional; import java.util.Set; +import javax.enterprise.inject.spi.Bean; + import org.apache.camel.NoSuchBeanException; import org.apache.camel.spi.Registry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import io.quarkus.arc.Arc; +import io.quarkus.arc.ArcContainer; +import io.quarkus.arc.runtime.BeanContainer; import io.quarkus.runtime.RuntimeValue; /** @@ -48,6 +54,10 @@ public Object lookupByName(String name) { } public T lookupByNameAndType(String name, Class type) { + Optional t = BeanManagerHelper.getReferenceByName(name, type); + if (t.isPresent()) { + return t.get(); + } Map, Object> map = this.get(name); if (map == null) { return null; @@ -62,7 +72,7 @@ public T lookupByNameAndType(String name, Class type) { } } if (answer instanceof RuntimeValue) { - log.info("Creating {} for name {}", type.toString(), name); + log.debug("Creating {} for name {}", type.toString(), name); answer = ((RuntimeValue) answer).getValue(); } try { @@ -86,6 +96,7 @@ public Map findByTypeWithName(Class type) { } } } + result.putAll(BeanManagerHelper.getReferencesByTypeWithName(type)); return result; } @@ -101,6 +112,7 @@ public Set findByType(Class type) { } } } + result.addAll(BeanManagerHelper.getReferencesByType(type)); return result; } diff --git a/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/RuntimeSupport.java b/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/support/RuntimeSupport.java similarity index 97% rename from extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/RuntimeSupport.java rename to extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/support/RuntimeSupport.java index b470b5061a290..e70e1aa330bc3 100644 --- a/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/RuntimeSupport.java +++ b/extensions/camel/camel-core/runtime/src/main/java/io/quarkus/camel/core/runtime/support/RuntimeSupport.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.quarkus.camel.core.runtime; +package io.quarkus.camel.core.runtime.support; import java.util.Properties; diff --git a/extensions/camel/camel-infinispan/deployment/pom.xml b/extensions/camel/camel-infinispan/deployment/pom.xml index 45edcf37e3996..d6ad2e7ba5d9e 100644 --- a/extensions/camel/camel-infinispan/deployment/pom.xml +++ b/extensions/camel/camel-infinispan/deployment/pom.xml @@ -10,29 +10,29 @@ 4.0.0 - quarkus-camel-infinispan - Quarkus - Camel - Infinispan - Deployment + quarkus-camel-infinispan-deployment + Quarkus - Camel - Infinispan - Deployment io.quarkus - quarkus-core + quarkus-core-deployment io.quarkus - quarkus-arc + quarkus-arc-deployment io.quarkus - quarkus-infinispan-client + quarkus-infinispan-client-deployment io.quarkus - quarkus-camel-infinispan-runtime + quarkus-camel-infinispan diff --git a/extensions/camel/camel-infinispan/runtime/pom.xml b/extensions/camel/camel-infinispan/runtime/pom.xml index d7b406cb90d93..be38dde3440ad 100644 --- a/extensions/camel/camel-infinispan/runtime/pom.xml +++ b/extensions/camel/camel-infinispan/runtime/pom.xml @@ -10,7 +10,7 @@ 4.0.0 - quarkus-camel-infinispan-runtime + quarkus-camel-infinispan Quarkus - Camel - Infinispan - Runtime @@ -18,15 +18,15 @@ io.quarkus - quarkus-arc-runtime + quarkus-arc io.quarkus - quarkus-core-runtime + quarkus-core io.quarkus - quarkus-infinispan-client-runtime + quarkus-infinispan-client org.infinispan @@ -48,7 +48,8 @@ - maven-dependency-plugin + io.quarkus + quarkus-bootstrap-maven-plugin maven-compiler-plugin diff --git a/extensions/camel/camel-netty4-http/deployment/pom.xml b/extensions/camel/camel-netty4-http/deployment/pom.xml index c03a611a65798..5ca026d4eb1c4 100644 --- a/extensions/camel/camel-netty4-http/deployment/pom.xml +++ b/extensions/camel/camel-netty4-http/deployment/pom.xml @@ -10,7 +10,7 @@ 4.0.0 - quarkus-camel-netty4-http + quarkus-camel-netty4-http-deployment Quarkus - Camel - Netty 4 HTTP - Deployment @@ -18,21 +18,21 @@ io.quarkus - quarkus-core + quarkus-core-deployment io.quarkus - quarkus-arc + quarkus-arc-deployment io.quarkus - quarkus-netty + quarkus-netty-deployment io.quarkus - quarkus-camel-netty4-http-runtime + quarkus-camel-netty4-http diff --git a/extensions/camel/camel-netty4-http/runtime/pom.xml b/extensions/camel/camel-netty4-http/runtime/pom.xml index f516c5fd31bd1..bdc71429acf3e 100644 --- a/extensions/camel/camel-netty4-http/runtime/pom.xml +++ b/extensions/camel/camel-netty4-http/runtime/pom.xml @@ -10,7 +10,7 @@ 4.0.0 - quarkus-camel-netty4-http-runtime + quarkus-camel-netty4-http Quarkus - Camel - Netty 4 HTTP - Runtime @@ -18,11 +18,11 @@ io.quarkus - quarkus-arc-runtime + quarkus-arc io.quarkus - quarkus-core-runtime + quarkus-core com.oracle.substratevm @@ -40,7 +40,8 @@ - maven-dependency-plugin + io.quarkus + quarkus-bootstrap-maven-plugin maven-compiler-plugin diff --git a/extensions/camel/camel-salesforce/deployment/pom.xml b/extensions/camel/camel-salesforce/deployment/pom.xml index 9d3c9e238bd13..7f3122c438825 100644 --- a/extensions/camel/camel-salesforce/deployment/pom.xml +++ b/extensions/camel/camel-salesforce/deployment/pom.xml @@ -10,7 +10,7 @@ 4.0.0 - quarkus-camel-salesforce + quarkus-camel-salesforce-deployment Quarkus - Camel - Salesforce - Deployment @@ -18,17 +18,17 @@ io.quarkus - quarkus-core + quarkus-core-deployment io.quarkus - quarkus-arc + quarkus-arc-deployment io.quarkus - quarkus-camel-salesforce-runtime + quarkus-camel-salesforce diff --git a/extensions/camel/camel-salesforce/runtime/pom.xml b/extensions/camel/camel-salesforce/runtime/pom.xml index 00c80b4f47288..e9cfe73da87ae 100644 --- a/extensions/camel/camel-salesforce/runtime/pom.xml +++ b/extensions/camel/camel-salesforce/runtime/pom.xml @@ -10,7 +10,7 @@ 4.0.0 - quarkus-camel-salesforce-runtime + quarkus-camel-salesforce Quarkus - Camel - Salesforce - Runtime @@ -18,11 +18,11 @@ io.quarkus - quarkus-arc-runtime + quarkus-arc io.quarkus - quarkus-core-runtime + quarkus-core com.oracle.substratevm @@ -40,7 +40,8 @@ - maven-dependency-plugin + io.quarkus + quarkus-bootstrap-maven-plugin maven-compiler-plugin diff --git a/extensions/camel/pom.xml b/extensions/camel/pom.xml index 7f60044aecacb..ab367b16fbc43 100644 --- a/extensions/camel/pom.xml +++ b/extensions/camel/pom.xml @@ -17,6 +17,7 @@ camel-core + camel-aws-s3 camel-salesforce camel-netty4-http camel-infinispan diff --git a/extensions/elasticsearch-rest-client/deployment/pom.xml b/extensions/elasticsearch-rest-client/deployment/pom.xml new file mode 100644 index 0000000000000..2b14e4ee87db3 --- /dev/null +++ b/extensions/elasticsearch-rest-client/deployment/pom.xml @@ -0,0 +1,59 @@ + + + + + + quarkus-elasticsearch-rest-client-parent + io.quarkus + 999-SNAPSHOT + ../ + + 4.0.0 + + quarkus-elasticsearch-rest-client-deployment + Quarkus - Elasticsearch REST client - Deployment + + + + io.quarkus + quarkus-core-deployment + + + io.quarkus + quarkus-elasticsearch-rest-client + + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + diff --git a/extensions/elasticsearch-rest-client/deployment/src/main/java/io/quarkus/elasticsearch/restclient/deployment/ElasticsearchRestClientProcessor.java b/extensions/elasticsearch-rest-client/deployment/src/main/java/io/quarkus/elasticsearch/restclient/deployment/ElasticsearchRestClientProcessor.java new file mode 100644 index 0000000000000..98494572732fd --- /dev/null +++ b/extensions/elasticsearch-rest-client/deployment/src/main/java/io/quarkus/elasticsearch/restclient/deployment/ElasticsearchRestClientProcessor.java @@ -0,0 +1,37 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Licensed 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.elasticsearch.restclient.deployment; + +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.ExtensionSslNativeSupportBuildItem; +import io.quarkus.deployment.builditem.FeatureBuildItem; +import io.quarkus.deployment.builditem.substrate.ReflectiveClassBuildItem; + +class ElasticsearchRestClientProcessor { + + @BuildStep + public void build(BuildProducer reflectiveClass, + BuildProducer extensionSslNativeSupport) throws Exception { + reflectiveClass.produce(new ReflectiveClassBuildItem(false, false, + org.apache.commons.logging.impl.LogFactoryImpl.class.getName(), + org.apache.commons.logging.impl.Jdk14Logger.class.getName())); + + // Indicates that this extension would like the SSL support to be enabled + extensionSslNativeSupport.produce(new ExtensionSslNativeSupportBuildItem(FeatureBuildItem.ELASTICSEARCH_REST_CLIENT)); + } +} diff --git a/extensions/elasticsearch-rest-client/pom.xml b/extensions/elasticsearch-rest-client/pom.xml new file mode 100644 index 0000000000000..e1c4ae28322a1 --- /dev/null +++ b/extensions/elasticsearch-rest-client/pom.xml @@ -0,0 +1,36 @@ + + + + + + quarkus-build-parent + io.quarkus + 999-SNAPSHOT + ../../build-parent/pom.xml + + 4.0.0 + + quarkus-elasticsearch-rest-client-parent + Quarkus - Elasticsearch client + pom + + deployment + runtime + + diff --git a/extensions/elasticsearch-rest-client/runtime/pom.xml b/extensions/elasticsearch-rest-client/runtime/pom.xml new file mode 100644 index 0000000000000..efd0c71e290d9 --- /dev/null +++ b/extensions/elasticsearch-rest-client/runtime/pom.xml @@ -0,0 +1,71 @@ + + + + + + quarkus-elasticsearch-rest-client-parent + io.quarkus + 999-SNAPSHOT + ../ + + 4.0.0 + + quarkus-elasticsearch-rest-client + Quarkus - Elasticsearch REST client - Runtime + + + + io.quarkus + quarkus-core + + + org.elasticsearch.client + elasticsearch-rest-client + + + org.elasticsearch.client + elasticsearch-rest-client-sniffer + + + com.oracle.substratevm + svm + + + + + + + io.quarkus + quarkus-bootstrap-maven-plugin + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + diff --git a/extensions/elasticsearch-rest-client/runtime/src/main/java/io/quarkus/elasticsearch/restclient/runtime/graal/Substitute_RestClient.java b/extensions/elasticsearch-rest-client/runtime/src/main/java/io/quarkus/elasticsearch/restclient/runtime/graal/Substitute_RestClient.java new file mode 100644 index 0000000000000..8873741efb56d --- /dev/null +++ b/extensions/elasticsearch-rest-client/runtime/src/main/java/io/quarkus/elasticsearch/restclient/runtime/graal/Substitute_RestClient.java @@ -0,0 +1,154 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Licensed 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.elasticsearch.restclient.runtime.graal; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import org.apache.http.HttpHost; +import org.apache.http.annotation.Contract; +import org.apache.http.annotation.ThreadingBehavior; +import org.apache.http.auth.AuthScheme; +import org.apache.http.client.AuthCache; +import org.apache.http.conn.SchemePortResolver; +import org.apache.http.conn.UnsupportedSchemeException; +import org.apache.http.impl.auth.BasicScheme; +import org.apache.http.impl.client.BasicAuthCache; +import org.apache.http.impl.conn.DefaultSchemePortResolver; +import org.apache.http.util.Args; +import org.elasticsearch.client.Node; +import org.elasticsearch.client.RestClient; + +import com.oracle.svm.core.annotate.Alias; +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; + +/** + * {@link BasicAuthCache} used in the {@link RestClient} is using + * serialization which is not supported by GraalVM. + *

+ * We substitute it with an implementation which does not use serialization. + */ +@TargetClass(className = "org.elasticsearch.client.RestClient") +final class Substitute_RestClient { + + @Alias + private ConcurrentMap blacklist; + + @Alias + private volatile NodeTuple> nodeTuple; + + @Substitute + public synchronized void setNodes(Collection nodes) { + if (nodes == null || nodes.isEmpty()) { + throw new IllegalArgumentException("nodes must not be null or empty"); + } + AuthCache authCache = new NoSerializationBasicAuthCache(); + + Map nodesByHost = new LinkedHashMap<>(); + for (Node node : nodes) { + Objects.requireNonNull(node, "node cannot be null"); + // TODO should we throw an IAE if we have two nodes with the same host? + nodesByHost.put(node.getHost(), node); + authCache.put(node.getHost(), new BasicScheme()); + } + this.nodeTuple = new NodeTuple<>(Collections.unmodifiableList(new ArrayList<>(nodesByHost.values())), + authCache); + this.blacklist.clear(); + } + + @TargetClass(className = "org.elasticsearch.client.DeadHostState") + final static class DeadHostState { + } + + @TargetClass(className = "org.elasticsearch.client.RestClient", innerClass = "NodeTuple") + final static class NodeTuple { + + @Alias + NodeTuple(final T nodes, final AuthCache authCache) { + } + } + + @Contract(threading = ThreadingBehavior.SAFE) + private static final class NoSerializationBasicAuthCache implements AuthCache { + + private final Map map; + private final SchemePortResolver schemePortResolver; + + public NoSerializationBasicAuthCache(final SchemePortResolver schemePortResolver) { + this.map = new ConcurrentHashMap<>(); + this.schemePortResolver = schemePortResolver != null ? schemePortResolver + : DefaultSchemePortResolver.INSTANCE; + } + + public NoSerializationBasicAuthCache() { + this(null); + } + + protected HttpHost getKey(final HttpHost host) { + if (host.getPort() <= 0) { + final int port; + try { + port = schemePortResolver.resolve(host); + } catch (final UnsupportedSchemeException ignore) { + return host; + } + return new HttpHost(host.getHostName(), port, host.getSchemeName()); + } else { + return host; + } + } + + @Override + public void put(final HttpHost host, final AuthScheme authScheme) { + Args.notNull(host, "HTTP host"); + if (authScheme == null) { + return; + } + this.map.put(getKey(host), authScheme); + } + + @Override + public AuthScheme get(final HttpHost host) { + Args.notNull(host, "HTTP host"); + return this.map.get(getKey(host)); + } + + @Override + public void remove(final HttpHost host) { + Args.notNull(host, "HTTP host"); + this.map.remove(getKey(host)); + } + + @Override + public void clear() { + this.map.clear(); + } + + @Override + public String toString() { + return this.map.toString(); + } + } +} diff --git a/extensions/elytron-security/deployment/pom.xml b/extensions/elytron-security/deployment/pom.xml index 718f2c7968415..118b7143fc7f2 100644 --- a/extensions/elytron-security/deployment/pom.xml +++ b/extensions/elytron-security/deployment/pom.xml @@ -26,26 +26,26 @@ 4.0.0 - quarkus-elytron-security + quarkus-elytron-security-deployment Quarkus - Security - Deployment io.quarkus - quarkus-core + quarkus-core-deployment io.quarkus - quarkus-resteasy + quarkus-resteasy-deployment io.quarkus - quarkus-undertow + quarkus-undertow-deployment io.quarkus - quarkus-elytron-security-runtime + quarkus-elytron-security diff --git a/extensions/elytron-security/deployment/src/main/java/io/quarkus/elytron/security/deployment/AuthConfigBuildItem.java b/extensions/elytron-security/deployment/src/main/java/io/quarkus/elytron/security/deployment/AuthConfigBuildItem.java index e452fad4db141..a7d1b567d99fa 100644 --- a/extensions/elytron-security/deployment/src/main/java/io/quarkus/elytron/security/deployment/AuthConfigBuildItem.java +++ b/extensions/elytron-security/deployment/src/main/java/io/quarkus/elytron/security/deployment/AuthConfigBuildItem.java @@ -2,8 +2,7 @@ import java.util.List; -import org.jboss.builder.item.MultiBuildItem; - +import io.quarkus.builder.item.MultiBuildItem; import io.quarkus.elytron.security.runtime.AuthConfig; import io.quarkus.runtime.RuntimeValue; import io.undertow.security.idm.IdentityManager; diff --git a/extensions/elytron-security/deployment/src/main/java/io/quarkus/elytron/security/deployment/IdentityManagerBuildItem.java b/extensions/elytron-security/deployment/src/main/java/io/quarkus/elytron/security/deployment/IdentityManagerBuildItem.java index 6bd64f1ea8e41..6f70ffb28a846 100644 --- a/extensions/elytron-security/deployment/src/main/java/io/quarkus/elytron/security/deployment/IdentityManagerBuildItem.java +++ b/extensions/elytron-security/deployment/src/main/java/io/quarkus/elytron/security/deployment/IdentityManagerBuildItem.java @@ -2,8 +2,7 @@ import java.util.List; -import org.jboss.builder.item.MultiBuildItem; - +import io.quarkus.builder.item.MultiBuildItem; import io.quarkus.deployment.annotations.BuildProducer; import io.undertow.security.idm.IdentityManager; diff --git a/extensions/elytron-security/deployment/src/main/java/io/quarkus/elytron/security/deployment/JCAProviderBuildItem.java b/extensions/elytron-security/deployment/src/main/java/io/quarkus/elytron/security/deployment/JCAProviderBuildItem.java new file mode 100644 index 0000000000000..2243d24673422 --- /dev/null +++ b/extensions/elytron-security/deployment/src/main/java/io/quarkus/elytron/security/deployment/JCAProviderBuildItem.java @@ -0,0 +1,18 @@ +package io.quarkus.elytron.security.deployment; + +import io.quarkus.builder.item.MultiBuildItem; + +/** + * Metadata for the names of JCA {@linkplain java.security.Provider} to register for reflection + */ +public final class JCAProviderBuildItem extends MultiBuildItem { + private String providerName; + + public JCAProviderBuildItem(String providerName) { + this.providerName = providerName; + } + + public String getProviderName() { + return providerName; + } +} diff --git a/extensions/elytron-security/deployment/src/main/java/io/quarkus/elytron/security/deployment/PasswordRealmBuildItem.java b/extensions/elytron-security/deployment/src/main/java/io/quarkus/elytron/security/deployment/PasswordRealmBuildItem.java index 500d078dbf9d6..69b0f88fda6a8 100644 --- a/extensions/elytron-security/deployment/src/main/java/io/quarkus/elytron/security/deployment/PasswordRealmBuildItem.java +++ b/extensions/elytron-security/deployment/src/main/java/io/quarkus/elytron/security/deployment/PasswordRealmBuildItem.java @@ -2,8 +2,7 @@ import java.util.List; -import org.jboss.builder.item.MultiBuildItem; - +import io.quarkus.builder.item.MultiBuildItem; import io.quarkus.deployment.annotations.BuildProducer; /** diff --git a/extensions/elytron-security/deployment/src/main/java/io/quarkus/elytron/security/deployment/SecurityDeploymentProcessor.java b/extensions/elytron-security/deployment/src/main/java/io/quarkus/elytron/security/deployment/SecurityDeploymentProcessor.java index c2cbea03dc5b4..ae2076b00d36a 100644 --- a/extensions/elytron-security/deployment/src/main/java/io/quarkus/elytron/security/deployment/SecurityDeploymentProcessor.java +++ b/extensions/elytron-security/deployment/src/main/java/io/quarkus/elytron/security/deployment/SecurityDeploymentProcessor.java @@ -16,7 +16,10 @@ package io.quarkus.elytron.security.deployment; +import java.security.Provider; +import java.security.Security; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Set; @@ -56,7 +59,6 @@ * TODO: The handling of the configuration to SecurityRealm instance creation/loading is clumsy to not being able to * have a config object annotated with @ConfigGroup inherit from another object with MP config annotated properties. * - * TODO: What additional features would be needed for Keycloak adaptor integration */ class SecurityDeploymentProcessor { private static final Logger log = Logger.getLogger(SecurityDeploymentProcessor.class.getName()); @@ -83,9 +85,19 @@ FeatureBuildItem feature() { * @param classes producer factory for ReflectiveClassBuildItems */ @BuildStep - void services(BuildProducer classes) { - classes.produce( - new ReflectiveClassBuildItem(false, false, "org.wildfly.security.password.impl.PasswordFactorySpiImpl")); + void services(BuildProducer classes, BuildProducer jcaProviders) { + String[] allClasses = { + "org.wildfly.security.password.impl.PasswordFactorySpiImpl", + }; + classes.produce(new ReflectiveClassBuildItem(true, false, allClasses)); + + // 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); + } + } } /** @@ -251,14 +263,72 @@ void addIdentityManager(SecurityTemplate template, BuildProducer allAuthConfigs = new ArrayList<>(); + ServletExtension idmExt = template.configureUndertowIdentityManager(securityDomain.getSecurityDomain(), + identityManager.getIdentityManager()); + extension.produce(new ServletExtensionBuildItem(idmExt)); + } + + /** + * Produces a {@code ServletExtension} to configure Undertow {@code AuthConfigBuildItem} produced during the build + * + * @param template - the runtime template class used to access runtime behaviors + * @param extension - the ServletExtensionBuildItem producer used to add the Undertow auth config + * @param authConfigs - the authentication method information that has been registered + */ + @BuildStep + @Record(ExecutionTime.STATIC_INIT) + void addLoginConfig(SecurityTemplate template, List authConfigs, + BuildProducer extension) { + List allAuthConfigs = new ArrayList<>(); + for (AuthConfigBuildItem authConfigExt : authConfigs) { AuthConfig ac = authConfigExt.getAuthConfig(); allAuthConfigs.add(ac); } - ServletExtension idmExt = template.configureUndertowIdentityManager(securityDomain.getSecurityDomain(), - identityManager.getIdentityManager(), allAuthConfigs); - extension.produce(new ServletExtensionBuildItem(idmExt)); + + extension.produce(new ServletExtensionBuildItem(template.configureLoginConfig(allAuthConfigs))); + } + + /** + * Register the classes for reflection in the requested named providers + * + * @param classes - ReflectiveClassBuildItem producer + * @param jcaProviders - JCAProviderBuildItem for requested providers + */ + @BuildStep + @Record(ExecutionTime.STATIC_INIT) + void registerJCAProviders(BuildProducer classes, List jcaProviders) { + for (JCAProviderBuildItem provider : jcaProviders) { + List providerClasses = registerProvider(provider.getProviderName()); + for (String className : providerClasses) { + classes.produce(new ReflectiveClassBuildItem(true, true, className)); + log.debugf("Register JCA class: %s", className); + } + } + } + + /** + * Determine the classes that make up the provider and its services + * + * @param providerName - JCA provider name + * @return class names that make up the provider and its services + */ + private List registerProvider(String providerName) { + ArrayList providerClasses = new ArrayList<>(); + Provider provider = Security.getProvider(providerName); + providerClasses.add(provider.getClass().getName()); + Set services = provider.getServices(); + for (Provider.Service service : services) { + String serviceClass = service.getClassName(); + providerClasses.add(serviceClass); + // Need to pull in the key classes + String supportedKeyClasses = service.getAttribute("SupportedKeyClasses"); + if (supportedKeyClasses != null) { + String[] keyClasses = supportedKeyClasses.split("\\|"); + providerClasses.addAll(Arrays.asList(keyClasses)); + } + } + return providerClasses; } /** diff --git a/extensions/elytron-security/deployment/src/main/java/io/quarkus/elytron/security/deployment/SecurityDomainBuildItem.java b/extensions/elytron-security/deployment/src/main/java/io/quarkus/elytron/security/deployment/SecurityDomainBuildItem.java index 07817d8963fc4..7ce753d0fa3c0 100644 --- a/extensions/elytron-security/deployment/src/main/java/io/quarkus/elytron/security/deployment/SecurityDomainBuildItem.java +++ b/extensions/elytron-security/deployment/src/main/java/io/quarkus/elytron/security/deployment/SecurityDomainBuildItem.java @@ -16,9 +16,9 @@ package io.quarkus.elytron.security.deployment; -import org.jboss.builder.item.SimpleBuildItem; import org.wildfly.security.auth.server.SecurityDomain; +import io.quarkus.builder.item.SimpleBuildItem; import io.quarkus.runtime.RuntimeValue; /** diff --git a/extensions/elytron-security/deployment/src/main/java/io/quarkus/elytron/security/deployment/SecurityRealmBuildItem.java b/extensions/elytron-security/deployment/src/main/java/io/quarkus/elytron/security/deployment/SecurityRealmBuildItem.java index 0739eccb0faae..d7c14596b7efa 100644 --- a/extensions/elytron-security/deployment/src/main/java/io/quarkus/elytron/security/deployment/SecurityRealmBuildItem.java +++ b/extensions/elytron-security/deployment/src/main/java/io/quarkus/elytron/security/deployment/SecurityRealmBuildItem.java @@ -1,8 +1,8 @@ package io.quarkus.elytron.security.deployment; -import org.jboss.builder.item.MultiBuildItem; import org.wildfly.security.auth.server.SecurityRealm; +import io.quarkus.builder.item.MultiBuildItem; import io.quarkus.elytron.security.runtime.AuthConfig; import io.quarkus.runtime.RuntimeValue; diff --git a/extensions/elytron-security/deployment/src/test/java/io/quarkus/security/test/TestCharClone.java b/extensions/elytron-security/deployment/src/test/java/io/quarkus/security/test/TestCharClone.java index 09f69447d8c54..7ce92129397d2 100644 --- a/extensions/elytron-security/deployment/src/test/java/io/quarkus/security/test/TestCharClone.java +++ b/extensions/elytron-security/deployment/src/test/java/io/quarkus/security/test/TestCharClone.java @@ -11,13 +11,13 @@ public void testClone() { char[] password = "jb0ss".toCharArray(); char[] clone = password.clone(); if (clone == password) { - System.out.printf("Failure, clone == password\n"); + System.out.printf("Failure, clone == password%n"); } if (!Arrays.equals(password, clone)) { - System.out.printf("Failure, clone neq password\n"); + System.out.printf("Failure, clone neq password%n"); } Class charArrayClass = password.getClass(); - System.out.printf("char[](%s) methods:\n", charArrayClass.getName()); + System.out.printf("char[](%s) methods:%n", charArrayClass.getName()); for (Method m : charArrayClass.getMethods()) { System.out.println(m); } diff --git a/extensions/elytron-security/runtime/pom.xml b/extensions/elytron-security/runtime/pom.xml index 02a270da725fd..9fb0365b7c99a 100644 --- a/extensions/elytron-security/runtime/pom.xml +++ b/extensions/elytron-security/runtime/pom.xml @@ -25,13 +25,13 @@ 4.0.0 - quarkus-elytron-security-runtime + quarkus-elytron-security Quarkus - Security - Runtime io.quarkus - quarkus-core-runtime + quarkus-core com.oracle.substratevm @@ -41,6 +41,10 @@ org.wildfly.security wildfly-elytron-auth-server + + org.wildfly.security + wildfly-elytron-password-impl + org.wildfly.security wildfly-elytron-realm @@ -63,7 +67,8 @@ - maven-dependency-plugin + io.quarkus + quarkus-bootstrap-maven-plugin maven-compiler-plugin diff --git a/extensions/elytron-security/runtime/src/main/java/io/quarkus/elytron/security/runtime/SecurityConfig.java b/extensions/elytron-security/runtime/src/main/java/io/quarkus/elytron/security/runtime/SecurityConfig.java index 380008f41153c..8eb024ee0db65 100644 --- a/extensions/elytron-security/runtime/src/main/java/io/quarkus/elytron/security/runtime/SecurityConfig.java +++ b/extensions/elytron-security/runtime/src/main/java/io/quarkus/elytron/security/runtime/SecurityConfig.java @@ -1,5 +1,7 @@ package io.quarkus.elytron.security.runtime; +import java.util.List; + import io.quarkus.runtime.annotations.ConfigItem; import io.quarkus.runtime.annotations.ConfigPhase; import io.quarkus.runtime.annotations.ConfigRoot; @@ -7,7 +9,7 @@ /** * */ -@ConfigRoot(phase = ConfigPhase.RUN_TIME) +@ConfigRoot(phase = ConfigPhase.BUILD_AND_RUN_TIME_FIXED) public final class SecurityConfig { /** * The configuration for the {@linkplain org.wildfly.security.auth.realm.LegacyPropertiesSecurityRealm} @@ -19,4 +21,10 @@ public final class SecurityConfig { */ @ConfigItem public MPRealmConfig embedded; + + /** + * List of security providers to enable for reflection + */ + @ConfigItem + public List securityProviders; } diff --git a/extensions/elytron-security/runtime/src/main/java/io/quarkus/elytron/security/runtime/SecurityTemplate.java b/extensions/elytron-security/runtime/src/main/java/io/quarkus/elytron/security/runtime/SecurityTemplate.java index 3354b9e1a0eaa..1a860403516c3 100644 --- a/extensions/elytron-security/runtime/src/main/java/io/quarkus/elytron/security/runtime/SecurityTemplate.java +++ b/extensions/elytron-security/runtime/src/main/java/io/quarkus/elytron/security/runtime/SecurityTemplate.java @@ -109,12 +109,13 @@ public void loadRealm(RuntimeValue realm, MPRealmConfig config) t SimpleMapBackedSecurityRealm memRealm = (SimpleMapBackedSecurityRealm) secRealm; HashMap identityMap = new HashMap<>(); Map userInfo = config.getUsers(); - log.debugf("UserInfoMap: %s\n", userInfo); + log.debugf("UserInfoMap: %s%n", userInfo); Map roleInfo = config.getRoles(); - log.debugf("RoleInfoMap: %s\n", roleInfo); - for (String user : userInfo.keySet()) { - String password = userInfo.get(user); - ClearPassword clear = ClearPassword.createRaw(ClearPassword.ALGORITHM_CLEAR, password.toCharArray()); + log.debugf("RoleInfoMap: %s%n", roleInfo); + for (Map.Entry userPasswordEntry : userInfo.entrySet()) { + String user = userPasswordEntry.getKey(); + ClearPassword clear = ClearPassword.createRaw(ClearPassword.ALGORITHM_CLEAR, + userPasswordEntry.getValue().toCharArray()); PasswordCredential passwordCred = new PasswordCredential(clear); List credentials = new ArrayList<>(); credentials.add(passwordCred); @@ -126,7 +127,7 @@ public void loadRealm(RuntimeValue realm, MPRealmConfig config) t } SimpleRealmEntry entry = new SimpleRealmEntry(credentials, attributes); identityMap.put(user, entry); - log.debugf("Added user(%s), roles=%s\n", user, attributes.get("groups")); + log.debugf("Added user(%s), roles=%s%n", user, attributes.get("groups")); } memRealm.setIdentityMap(identityMap); } @@ -262,18 +263,32 @@ public IdentityManager createIdentityManager(RuntimeValue domain * * @param domain - the SecurityDomain to use for auth decisions * @param identityManager - the IdentityManager for auth decisions - * @param authConfigs - the authentication methods to register with the deployment {@linkplain LoginConfig} * @return - the ServletExtension instance to register */ public ServletExtension configureUndertowIdentityManager(RuntimeValue domain, - IdentityManager identityManager, - List authConfigs) { + IdentityManager identityManager) { + return new ServletExtension() { + @Override + public void handleDeployment(DeploymentInfo deploymentInfo, ServletContext servletContext) { + deploymentInfo.setIdentityManager(identityManager); + } + }; + } + + /** + * Called to create a {@linkplain ServletExtension} to associate the {@linkplain LoginConfig} with the + * deployment. + * + * @param authConfigs - the authenticaiton methods to register with the deployment {@linkplain LoginConfig} + * @return - the ServletExtension instance to register + */ + public ServletExtension configureLoginConfig(List authConfigs) { return new ServletExtension() { @Override public void handleDeployment(DeploymentInfo deploymentInfo, ServletContext servletContext) { if (authConfigs.size() > 0) { AuthConfig first = authConfigs.get(0); - log.debugf("configureUndertowIdentityManager, %s", authConfigs); + log.debugf("configureLoginConfig, %s", authConfigs); LoginConfig loginConfig = new LoginConfig(first.authMechanism, first.realmName); for (int n = 1; n < authConfigs.size(); n++) { AuthConfig ac = authConfigs.get(n); @@ -281,9 +296,7 @@ public void handleDeployment(DeploymentInfo deploymentInfo, ServletContext servl } deploymentInfo.setLoginConfig(loginConfig); } - deploymentInfo.setIdentityManager(identityManager); } }; } - } diff --git a/extensions/flyway/deployment/pom.xml b/extensions/flyway/deployment/pom.xml new file mode 100644 index 0000000000000..4d6958de46111 --- /dev/null +++ b/extensions/flyway/deployment/pom.xml @@ -0,0 +1,76 @@ + + + + + + quarkus-flyway-parent + io.quarkus + 999-SNAPSHOT + + 4.0.0 + + quarkus-flyway-deployment + Quarkus - Flyway - Deployment + + + + io.quarkus + quarkus-core-deployment + + + io.quarkus + quarkus-arc-deployment + + + io.quarkus + quarkus-agroal-deployment + + + io.quarkus + quarkus-flyway + + + io.quarkus + quarkus-junit5-internal + test + + + io.quarkus + quarkus-test-h2 + test + + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + 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 new file mode 100644 index 0000000000000..1e508087bb000 --- /dev/null +++ b/extensions/flyway/deployment/src/main/java/io/quarkus/flyway/FlywayProcessor.java @@ -0,0 +1,172 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Licensed 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.flyway; + +import static io.quarkus.deployment.annotations.ExecutionTime.STATIC_INIT; +import static java.nio.file.Files.walk; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import org.jboss.logging.Logger; + +import io.quarkus.agroal.deployment.DataSourceInitializedBuildItem; +import io.quarkus.arc.deployment.AdditionalBeanBuildItem; +import io.quarkus.arc.deployment.BeanContainerBuildItem; +import io.quarkus.arc.deployment.BeanContainerListenerBuildItem; +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.GeneratedResourceBuildItem; +import io.quarkus.deployment.builditem.substrate.SubstrateResourceBuildItem; +import io.quarkus.flyway.runtime.FlywayBuildConfig; +import io.quarkus.flyway.runtime.FlywayProducer; +import io.quarkus.flyway.runtime.FlywayRuntimeConfig; +import io.quarkus.flyway.runtime.FlywayTemplate; +import io.quarkus.flyway.runtime.graal.QuarkusPathLocationScanner; + +class FlywayProcessor { + /** + * Flyway internal resources that must be added to the native image + */ + private static final String FLYWAY_DATABASES_PATH_ROOT = "org/flywaydb/core/internal/database"; + private static final String FLYWAY_METADATA_TABLE_FILENAME = "createMetaDataTable.sql"; + private static final String[] FLYWAY_DATABASES_WITH_SQL_FILE = { + "cockroachdb", + "derby", + "h2", + "hsqldb", + "mysql", + "oracle", + "postgresql", + "redshift", + "saphana", + "sqlite", + "sybasease" + }; + private static final Logger LOGGER = Logger.getLogger(FlywayProcessor.class); + /** + * Flyway build config + */ + FlywayBuildConfig flywayBuildConfig; + + @Record(STATIC_INIT) + @BuildStep(providesCapabilities = "io.quarkus.flyway") + void build(BuildProducer additionalBeanProducer, + BuildProducer featureProducer, + BuildProducer resourceProducer, + BuildProducer containerListenerProducer, + BuildProducer generatedResourceProducer, + FlywayTemplate template, + DataSourceInitializedBuildItem dataSourceInitializedBuildItem) throws IOException, URISyntaxException { + + featureProducer.produce(new FeatureBuildItem(FeatureBuildItem.FLYWAY)); + + AdditionalBeanBuildItem unremovableProducer = AdditionalBeanBuildItem.unremovableOf(FlywayProducer.class); + additionalBeanProducer.produce(unremovableProducer); + + registerSubstrateResources(resourceProducer, generatedResourceProducer, flywayBuildConfig); + + containerListenerProducer.produce( + new BeanContainerListenerBuildItem(template.setFlywayBuildConfig(flywayBuildConfig))); + } + + /** + * Handles all the operations that can be recorded in the RUNTIME_INIT execution time phase + * + * @param template Used to set the runtime config + * @param flywayRuntimeConfig The Flyway configuration + * @param dataSourceInitializedBuildItem Added this dependency to be sure that Agroal is initialized first + */ + @Record(ExecutionTime.RUNTIME_INIT) + @BuildStep + void configureRuntimeProperties(FlywayTemplate template, + FlywayRuntimeConfig flywayRuntimeConfig, + BeanContainerBuildItem beanContainer, + DataSourceInitializedBuildItem dataSourceInitializedBuildItem) { + template.configureFlywayProperties(flywayRuntimeConfig, beanContainer.getValue()); + template.doStartActions(flywayRuntimeConfig, beanContainer.getValue()); + } + + private void registerSubstrateResources(BuildProducer resource, + BuildProducer generatedResourceProducer, + FlywayBuildConfig flywayBuildConfig) + throws IOException, URISyntaxException { + List nativeResources = generateDatabasesSQLFiles(); + 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 + .stream() + .collect(Collectors.joining("\n", "", "\n")); + generatedResourceProducer.produce( + new GeneratedResourceBuildItem( + QuarkusPathLocationScanner.MIGRATIONS_LIST_FILE, + resourcesList.getBytes(StandardCharsets.UTF_8))); + nativeResources.add(QuarkusPathLocationScanner.MIGRATIONS_LIST_FILE); + resource.produce(new SubstrateResourceBuildItem(nativeResources.toArray(new String[0]))); + } + + private List discoverApplicationMigrations(FlywayBuildConfig flywayBuildConfig) + throws IOException, URISyntaxException { + List resources = new ArrayList<>(); + try { + List locations = new ArrayList<>(flywayBuildConfig.locations); + if (locations.isEmpty()) { + locations.add("db/migration"); + } + // Locations can be a comma separated list + for (String location : locations) { + Enumeration migrations = Thread.currentThread().getContextClassLoader().getResources(location); + while (migrations.hasMoreElements()) { + URL path = migrations.nextElement(); + LOGGER.info("Adding application migrations in path: " + path); + Set applicationMigrations = walk(Paths.get(path.toURI())) + .filter(Files::isRegularFile) + .map(it -> Paths.get(location, it.getFileName().toString()).toString()) + .peek(it -> LOGGER.debug("Discovered: " + it)) + .collect(Collectors.toSet()); + resources.addAll(applicationMigrations); + } + } + return resources; + } catch (IOException | URISyntaxException e) { + throw e; + } + } + + private List generateDatabasesSQLFiles() { + List result = new ArrayList<>(FLYWAY_DATABASES_WITH_SQL_FILE.length); + for (String database : FLYWAY_DATABASES_WITH_SQL_FILE) { + String filePath = FLYWAY_DATABASES_PATH_ROOT + "/" + database + "/" + FLYWAY_METADATA_TABLE_FILENAME; + result.add(filePath); + LOGGER.debug("Adding flyway internal migration: " + filePath); + } + return result; + } +} 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 new file mode 100644 index 0000000000000..be69cbcc6a283 --- /dev/null +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionFullConfigTest.java @@ -0,0 +1,88 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Licensed 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.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; + + // 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()); + } +} diff --git a/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionMigrateAtStartTest.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionMigrateAtStartTest.java new file mode 100644 index 0000000000000..b5e4e4107e1f9 --- /dev/null +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayExtensionMigrateAtStartTest.java @@ -0,0 +1,50 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Licensed 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.flyway.test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +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 FlywayExtensionMigrateAtStartTest { + // Quarkus built object + @Inject + Flyway flyway; + + @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.properties", "application.properties")); + + @Test + @DisplayName("Migrates at start correctly") + public void testFlywayConfigInjection() { + String currentVersion = flyway.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/java/io/quarkus/flyway/test/FlywayTestResources.java b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayTestResources.java new file mode 100644 index 0000000000000..ea9d2f0dfa4f5 --- /dev/null +++ b/extensions/flyway/deployment/src/test/java/io/quarkus/flyway/test/FlywayTestResources.java @@ -0,0 +1,24 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Licensed 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.flyway.test; + +import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.h2.H2DatabaseTestResource; + +@QuarkusTestResource(H2DatabaseTestResource.class) +public class FlywayTestResources { +} diff --git a/extensions/flyway/deployment/src/test/resources/db/migration/V1.0.0__Quarkus.sql b/extensions/flyway/deployment/src/test/resources/db/migration/V1.0.0__Quarkus.sql new file mode 100644 index 0000000000000..7f555a1c34ca7 --- /dev/null +++ b/extensions/flyway/deployment/src/test/resources/db/migration/V1.0.0__Quarkus.sql @@ -0,0 +1,5 @@ +CREATE TABLE quarked_flyway +( + id INT, + some VARCHAR(20) +); \ No newline at end of file diff --git a/extensions/flyway/deployment/src/test/resources/full-config.properties b/extensions/flyway/deployment/src/test/resources/full-config.properties new file mode 100644 index 0000000000000..d9a1d4dfba3f9 --- /dev/null +++ b/extensions/flyway/deployment/src/test/resources/full-config.properties @@ -0,0 +1,28 @@ +# +# Copyright 2019 Red Hat, Inc. +# +# Licensed 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. +# +# Agroal config +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 +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 \ No newline at end of file diff --git a/extensions/flyway/deployment/src/test/resources/migrate-at-start-config.properties b/extensions/flyway/deployment/src/test/resources/migrate-at-start-config.properties new file mode 100644 index 0000000000000..aadbcebe2cee8 --- /dev/null +++ b/extensions/flyway/deployment/src/test/resources/migrate-at-start-config.properties @@ -0,0 +1,22 @@ +# +# Copyright 2019 Red Hat, Inc. +# +# Licensed 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. +# +# Agroal config +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.migrate-at-start=true \ No newline at end of file diff --git a/extensions/flyway/pom.xml b/extensions/flyway/pom.xml new file mode 100644 index 0000000000000..aa278b51322f2 --- /dev/null +++ b/extensions/flyway/pom.xml @@ -0,0 +1,37 @@ + + + + + + + quarkus-build-parent + io.quarkus + 999-SNAPSHOT + ../../build-parent/pom.xml + + 4.0.0 + + quarkus-flyway-parent + Quarkus - Flyway + pom + + runtime + deployment + + diff --git a/extensions/flyway/runtime/pom.xml b/extensions/flyway/runtime/pom.xml new file mode 100644 index 0000000000000..51ce34f15a383 --- /dev/null +++ b/extensions/flyway/runtime/pom.xml @@ -0,0 +1,74 @@ + + + + + + quarkus-flyway-parent + io.quarkus + 999-SNAPSHOT + + 4.0.0 + + quarkus-flyway + Quarkus - Flyway - Runtime + + + + io.quarkus + quarkus-core + + + org.flywaydb + flyway-core + + + io.quarkus + quarkus-agroal + + + io.quarkus + quarkus-narayana-jta + + + com.oracle.substratevm + svm + + + + + + + io.quarkus + quarkus-bootstrap-maven-plugin + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + 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 new file mode 100644 index 0000000000000..b12af3b185212 --- /dev/null +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayBuildConfig.java @@ -0,0 +1,36 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Licensed 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.flyway.runtime; + +import java.util.List; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; + +@ConfigRoot(name = "flyway", phase = ConfigPhase.BUILD_AND_RUN_TIME_FIXED) +public final class FlywayBuildConfig { + /** + * 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 List 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 new file mode 100644 index 0000000000000..5a1dc5a97be17 --- /dev/null +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayProducer.java @@ -0,0 +1,73 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Licensed 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.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; +import javax.inject.Inject; + +import org.flywaydb.core.Flyway; +import org.flywaydb.core.api.configuration.FluentConfiguration; + +import io.agroal.api.AgroalDataSource; + +@ApplicationScoped +public class FlywayProducer { + @Inject + AgroalDataSource dataSource; + private FlywayRuntimeConfig flywayRuntimeConfig; + private FlywayBuildConfig flywayBuildConfig; + + @Produces + @Dependent + 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.table.ifPresent(configure::table); + List notEmptyLocations = filterBlanks(flywayBuildConfig.locations); + if (!notEmptyLocations.isEmpty()) { + configure.locations(notEmptyLocations.toArray(new String[0])); + } + flywayRuntimeConfig.sqlMigrationPrefix.ifPresent(configure::sqlMigrationPrefix); + flywayRuntimeConfig.repeatableSqlMigrationPrefix.ifPresent(configure::repeatableSqlMigrationPrefix); + 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; + } + + public void setFlywayBuildConfig(FlywayBuildConfig flywayBuildConfig) { + this.flywayBuildConfig = flywayBuildConfig; + } +} 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 new file mode 100644 index 0000000000000..bbced29e8d1d6 --- /dev/null +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayRuntimeConfig.java @@ -0,0 +1,72 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Licensed 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.flyway.runtime; + +import java.util.List; +import java.util.Optional; +import java.util.OptionalInt; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; + +@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 List 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 automatically when the application starts, false otherwise. + * + */ + public boolean migrateAtStart = false; +} diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayTemplate.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayTemplate.java new file mode 100644 index 0000000000000..ee030c14a64a0 --- /dev/null +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/FlywayTemplate.java @@ -0,0 +1,45 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Licensed 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.flyway.runtime; + +import org.flywaydb.core.Flyway; + +import io.quarkus.arc.runtime.BeanContainer; +import io.quarkus.arc.runtime.BeanContainerListener; +import io.quarkus.runtime.annotations.Template; + +@Template +public class FlywayTemplate { + + public BeanContainerListener setFlywayBuildConfig(FlywayBuildConfig flywayBuildConfig) { + return beanContainer -> { + FlywayProducer producer = beanContainer.instance(FlywayProducer.class); + producer.setFlywayBuildConfig(flywayBuildConfig); + }; + } + + public void configureFlywayProperties(FlywayRuntimeConfig flywayRuntimeConfig, BeanContainer container) { + container.instance(FlywayProducer.class).setFlywayRuntimeConfig(flywayRuntimeConfig); + } + + public void doStartActions(FlywayRuntimeConfig config, BeanContainer container) { + if (config.migrateAtStart) { + Flyway flyway = container.instance(Flyway.class); + flyway.migrate(); + } + } +} diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/graal/CompositeMigrationResolverSubstitutions.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/graal/CompositeMigrationResolverSubstitutions.java new file mode 100644 index 0000000000000..e0f9f20c3ca04 --- /dev/null +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/graal/CompositeMigrationResolverSubstitutions.java @@ -0,0 +1,70 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Licensed 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.flyway.runtime.graal; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; + +import org.flywaydb.core.api.configuration.Configuration; +import org.flywaydb.core.api.resolver.MigrationResolver; +import org.flywaydb.core.internal.clazz.ClassProvider; +import org.flywaydb.core.internal.database.base.Database; +import org.flywaydb.core.internal.resolver.java.JavaMigrationResolver; +import org.flywaydb.core.internal.resolver.jdbc.JdbcMigrationResolver; +import org.flywaydb.core.internal.resolver.sql.SqlMigrationResolver; +import org.flywaydb.core.internal.resource.ResourceProvider; +import org.flywaydb.core.internal.sqlscript.SqlStatementBuilderFactory; + +import com.oracle.svm.core.annotate.Alias; +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; + +@TargetClass(className = "org.flywaydb.core.internal.resolver.CompositeMigrationResolver") +public final class CompositeMigrationResolverSubstitutions { + @Alias + private Collection migrationResolvers = new ArrayList<>(); + + /** + * Substitution to remove Spring-data migration resolver as the spring-data dependency is optional. + * This method removes the inclusion of {@link org.flywaydb.core.internal.resolver.spring.SpringJdbcMigrationResolver} + * in the resolvers list to avoid native image errors because of the incomplete classpath + * + * @see org.flywaydb.core.internal.resolver.spring.SpringJdbcMigrationResolver + * @see org.flywaydb.core.internal.resolver.CompositeMigrationResolver#CompositeMigrationResolver(Database, + * ResourceProvider, ClassProvider, Configuration, SqlStatementBuilderFactory, MigrationResolver...) + */ + @Substitute + public CompositeMigrationResolverSubstitutions( + Database database, + ResourceProvider resourceProvider, + ClassProvider classProvider, + Configuration configuration, + SqlStatementBuilderFactory sqlStatementBuilderFactory, + MigrationResolver... customMigrationResolvers) { + if (!configuration.isSkipDefaultResolvers()) { + migrationResolvers.add(new SqlMigrationResolver( + database, + resourceProvider, + sqlStatementBuilderFactory, + configuration)); + migrationResolvers.add(new JavaMigrationResolver(classProvider, configuration)); + migrationResolvers.add(new JdbcMigrationResolver(classProvider, configuration)); + } + migrationResolvers.addAll(Arrays.asList(customMigrationResolvers)); + } +} diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/graal/FeatureDetectorSubstitutions.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/graal/FeatureDetectorSubstitutions.java new file mode 100644 index 0000000000000..b41f1efaeedb9 --- /dev/null +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/graal/FeatureDetectorSubstitutions.java @@ -0,0 +1,66 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Licensed 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.flyway.runtime.graal; + +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; + +@Substitute +@TargetClass(className = "org.flywaydb.core.internal.util.FeatureDetector") +public final class FeatureDetectorSubstitutions { + + @Substitute + public FeatureDetectorSubstitutions(ClassLoader classLoader) { + + } + + @Substitute + public boolean isApacheCommonsLoggingAvailable() { + return false; + } + + @Substitute + public boolean isSlf4jAvailable() { + return false; + } + + @Substitute + public boolean isSpringJdbcAvailable() { + return false; + } + + @Substitute + public boolean isJBossVFSv2Available() { + return false; + } + + @Substitute + public boolean isJBossVFSv3Available() { + return false; + } + + @Substitute + public boolean isOsgiFrameworkAvailable() { + return false; + } + + @Substitute + public boolean isAndroidAvailable() { + return false; + } + +} diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/graal/PostgreSQLSqlStatementBuilderSubstitutions.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/graal/PostgreSQLSqlStatementBuilderSubstitutions.java new file mode 100644 index 0000000000000..f5a9434a3d193 --- /dev/null +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/graal/PostgreSQLSqlStatementBuilderSubstitutions.java @@ -0,0 +1,69 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Licensed 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.flyway.runtime.graal; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.BooleanSupplier; + +import org.flywaydb.core.internal.line.Line; +import org.flywaydb.core.internal.sqlscript.Delimiter; +import org.flywaydb.core.internal.sqlscript.SqlStatement; +import org.flywaydb.core.internal.sqlscript.StandardSqlStatement; + +import com.oracle.svm.core.annotate.Alias; +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; + +/** + * This substitution removes de PosgreSQL COPY statement support in Flyway to allow native image compilation + * when the PostgreSQL Driver is not in the classpath. + */ +@TargetClass(className = "org.flywaydb.core.internal.database.postgresql.PostgreSQLSqlStatementBuilder", onlyWith = PostgreSQLSqlStatementBuilderSubstitutions.Selector.class) +public final class PostgreSQLSqlStatementBuilderSubstitutions { + @Alias + protected List lines = new ArrayList<>(); + @Alias + protected Delimiter delimiter; + @Alias + private boolean pgCopy; + + /** + * Returns only a {@link StandardSqlStatement} as the normal SQL migrations do or throw an exception + * if pgCopy flag is true + */ + @Substitute + public SqlStatement getSqlStatement() { + if (pgCopy) { + throw new IllegalStateException("pgCopy is not supported yet!"); + } + return new StandardSqlStatement(lines, delimiter); + } + + static final class Selector implements BooleanSupplier { + + @Override + public boolean getAsBoolean() { + try { + Class.forName("org.postgresql.Driver"); + return false; + } catch (ClassNotFoundException e) { + return true; + } + } + } +} diff --git a/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/graal/QuarkusPathLocationScanner.java b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/graal/QuarkusPathLocationScanner.java new file mode 100644 index 0000000000000..02284afed61eb --- /dev/null +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/graal/QuarkusPathLocationScanner.java @@ -0,0 +1,78 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Licensed 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.flyway.runtime.graal; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import org.flywaydb.core.api.logging.Log; +import org.flywaydb.core.api.logging.LogFactory; +import org.flywaydb.core.internal.resource.LoadableResource; +import org.flywaydb.core.internal.resource.classpath.ClassPathResource; +import org.flywaydb.core.internal.scanner.classpath.ResourceAndClassScanner; + +public final class QuarkusPathLocationScanner implements ResourceAndClassScanner { + private static final Log LOG = LogFactory.getLog(QuarkusPathLocationScanner.class); + /** + * File with the migrations list. It is generated dynamically in the Flyway Quarkus Processor + */ + public final static String MIGRATIONS_LIST_FILE = "META-INF/flyway-migrations.txt"; + + /** + * Returns the migrations loaded into the {@see MIGRATIONS_LIST_FILE} + * + * @return The resources that were found. + */ + @Override + public Collection scanForResources() { + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + try (InputStream resource = classLoader.getResourceAsStream(MIGRATIONS_LIST_FILE); + BufferedReader reader = new BufferedReader(new InputStreamReader(Objects.requireNonNull(resource)))) { + List migrations = reader.lines().collect(Collectors.toList()); + Set resources = new HashSet<>(); + for (String file : migrations) { + LOG.debug("Loading " + file); + resources.add(new ClassPathResource(null, file, classLoader, StandardCharsets.UTF_8)); + } + return resources; + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + /** + * Scans the classpath for concrete classes under the specified package implementing this interface. + * Non-instantiable abstract classes are filtered out. + * + * @return The non-abstract classes that were found. + */ + @Override + public Collection> scanForClasses() { + // Classes are not supported in native mode + return Collections.emptyList(); + } +} 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 new file mode 100644 index 0000000000000..af34432a2597f --- /dev/null +++ b/extensions/flyway/runtime/src/main/java/io/quarkus/flyway/runtime/graal/ScannerSubstitutions.java @@ -0,0 +1,56 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Licensed 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.flyway.runtime.graal; + +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.flywaydb.core.api.Location; +import org.flywaydb.core.internal.resource.LoadableResource; +import org.flywaydb.core.internal.scanner.classpath.ResourceAndClassScanner; + +import com.oracle.svm.core.annotate.Alias; +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; + +/** + * This substitution replaces the Flyway dynamic scanners with a fixed path scanner in native mode + */ +@TargetClass(className = "org.flywaydb.core.internal.scanner.Scanner") +public final class ScannerSubstitutions { + + @Alias + private List resources = new ArrayList<>(); + @Alias + private List> classes = new ArrayList<>(); + + /** + * Creates only {@link QuarkusPathLocationScanner} instances. + * Replaces the original method that tries to detect migrations using reflection techniques that are not allowed + * in native mode + * + * @see org.flywaydb.core.internal.scanner.Scanner#Scanner(Collection, ClassLoader, Charset) + */ + @Substitute + public ScannerSubstitutions(Collection locations, ClassLoader classLoader, Charset encoding) { + ResourceAndClassScanner quarkusScanner = new QuarkusPathLocationScanner(); + resources.addAll(quarkusScanner.scanForResources()); + classes.addAll(quarkusScanner.scanForClasses()); + } +} diff --git a/extensions/hibernate-orm/deployment/pom.xml b/extensions/hibernate-orm/deployment/pom.xml index 51a098e1193a2..6c795ddbe7fad 100644 --- a/extensions/hibernate-orm/deployment/pom.xml +++ b/extensions/hibernate-orm/deployment/pom.xml @@ -25,47 +25,29 @@ 4.0.0 - quarkus-hibernate-orm + quarkus-hibernate-orm-deployment Quarkus - Hibernate ORM - Deployment io.quarkus - quarkus-core + quarkus-core-deployment io.quarkus - quarkus-hibernate-orm-runtime + quarkus-hibernate-orm io.quarkus - quarkus-narayana-jta + quarkus-narayana-jta-deployment io.quarkus - quarkus-agroal + quarkus-agroal-deployment io.quarkus - quarkus-arc - - - - - org.hibernate - hibernate-core - - - org.javassist - javassist - - + quarkus-arc-deployment @@ -83,6 +65,21 @@ test-jar test + + io.quarkus + quarkus-resteasy-deployment + test + + + io.quarkus + quarkus-jdbc-h2 + test + + + io.rest-assured + rest-assured + test + diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/AdditionalJpaModelBuildItem.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/AdditionalJpaModelBuildItem.java index 81cd095439587..f666ab79cc009 100644 --- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/AdditionalJpaModelBuildItem.java +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/AdditionalJpaModelBuildItem.java @@ -16,7 +16,7 @@ package io.quarkus.hibernate.orm.deployment; -import org.jboss.builder.item.MultiBuildItem; +import io.quarkus.builder.item.MultiBuildItem; /** * Additional Jpa model class that we need to index diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateEnhancersRegisteredBuildItem.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateEnhancersRegisteredBuildItem.java index c729dd3296c69..a64920812165b 100644 --- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateEnhancersRegisteredBuildItem.java +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/HibernateEnhancersRegisteredBuildItem.java @@ -1,6 +1,6 @@ package io.quarkus.hibernate.orm.deployment; -import org.jboss.builder.item.SimpleBuildItem; +import io.quarkus.builder.item.SimpleBuildItem; /** * Purely marker build item so that you can register enhancers after Hibernate 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 298ea81d1d38b..182f0186ee8d7 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 @@ -20,11 +20,15 @@ import static io.quarkus.deployment.annotations.ExecutionTime.STATIC_INIT; import java.io.IOException; +import java.io.UnsupportedEncodingException; import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Optional; @@ -42,9 +46,11 @@ import org.hibernate.dialect.H2Dialect; import org.hibernate.dialect.MariaDB103Dialect; import org.hibernate.dialect.PostgreSQL95Dialect; +import org.hibernate.integrator.spi.Integrator; import org.hibernate.jpa.boot.internal.ParsedPersistenceXmlDescriptor; import org.hibernate.jpa.boot.internal.PersistenceXmlParser; import org.hibernate.loader.BatchFetchStyle; +import org.hibernate.service.spi.ServiceContributor; import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationTarget; import org.jboss.jandex.CompositeIndex; @@ -79,6 +85,9 @@ import io.quarkus.deployment.index.IndexingUtil; import io.quarkus.deployment.recording.RecorderContext; import io.quarkus.deployment.util.IoUtil; +import io.quarkus.deployment.util.ServiceUtil; +import io.quarkus.hibernate.orm.deployment.integration.HibernateOrmIntegrationBuildItem; +import io.quarkus.hibernate.orm.deployment.integration.HibernateOrmIntegrationRuntimeConfiguredBuildItem; import io.quarkus.hibernate.orm.runtime.DefaultEntityManagerFactoryProducer; import io.quarkus.hibernate.orm.runtime.DefaultEntityManagerProducer; import io.quarkus.hibernate.orm.runtime.HibernateOrmTemplate; @@ -115,39 +124,30 @@ HotDeploymentConfigFileBuildItem configFile() { return new HotDeploymentConfigFileBuildItem("META-INF/persistence.xml"); } - @BuildStep - void doParseAndRegisterSubstrateResources(BuildProducer persistenceProducer, - BuildProducer resourceProducer, - BuildProducer hotDeploymentProducer, - BuildProducer systemPropertyProducer, - ArchiveRootBuildItem root, - ApplicationArchivesBuildItem applicationArchivesBuildItem, - Optional driverBuildItem) throws IOException { - List descriptors = loadOriginalXMLParsedDescriptors(); - handleHibernateORMWithNoPersistenceXml(descriptors, resourceProducer, hotDeploymentProducer, systemPropertyProducer, - root, driverBuildItem, applicationArchivesBuildItem); - for (ParsedPersistenceXmlDescriptor i : descriptors) { - persistenceProducer.produce(new PersistenceUnitDescriptorBuildItem(i)); - } - } - + @SuppressWarnings("unchecked") @BuildStep @Record(STATIC_INIT) public void build(RecorderContext recorder, HibernateOrmTemplate template, - List descItems, List additionalJpaModelBuildItems, List nonJpaModelBuildItems, CombinedIndexBuildItem index, ApplicationIndexBuildItem applicationIndex, - BuildProducer reflectiveClass, + ArchiveRootBuildItem archiveRoot, + ApplicationArchivesBuildItem applicationArchivesBuildItem, + Optional driverBuildItem, BuildProducer feature, + BuildProducer persistenceUnitDescriptorProducer, + BuildProducer resourceProducer, + BuildProducer hotDeploymentProducer, + BuildProducer systemPropertyProducer, + BuildProducer reflectiveClass, BuildProducer domainObjectsProducer, - BuildProducer beanContainerListener) throws Exception { + BuildProducer beanContainerListener, + List integrations) throws Exception { feature.produce(new FeatureBuildItem(FeatureBuildItem.HIBERNATE_ORM)); - List descriptors = descItems.stream() - .map(PersistenceUnitDescriptorBuildItem::getDescriptor).collect(Collectors.toList()); + List explicitDescriptors = loadOriginalXMLParsedDescriptors(); // build a composite index with additional jpa model classes Indexer indexer = new Indexer(); @@ -161,7 +161,8 @@ public void build(RecorderContext recorder, HibernateOrmTemplate template, Set nonJpaModelClasses = nonJpaModelBuildItems.stream() .map(NonJpaModelBuildItem::getClassName) .collect(Collectors.toSet()); - JpaJandexScavenger scavenger = new JpaJandexScavenger(reflectiveClass, descriptors, compositeIndex, nonJpaModelClasses); + JpaJandexScavenger scavenger = new JpaJandexScavenger(reflectiveClass, explicitDescriptors, compositeIndex, + nonJpaModelClasses); final JpaEntitiesBuildItem domainObjects = scavenger.discoverModelAndRegisterForReflection(); // remember how to run the enhancers later @@ -172,17 +173,28 @@ public void build(RecorderContext recorder, HibernateOrmTemplate template, return; } - for (String className : domainObjects.getClassNames()) { + template.callHibernateFeatureInit(); + + // handle the implicit persistence unit + List allDescriptors = new ArrayList<>(explicitDescriptors.size() + 1); + allDescriptors.addAll(explicitDescriptors); + handleHibernateORMWithNoPersistenceXml(allDescriptors, resourceProducer, hotDeploymentProducer, systemPropertyProducer, + archiveRoot, driverBuildItem, applicationArchivesBuildItem); + + for (ParsedPersistenceXmlDescriptor descriptor : allDescriptors) { + persistenceUnitDescriptorProducer.produce(new PersistenceUnitDescriptorBuildItem(descriptor)); + } + + for (String className : domainObjects.getEntityClassNames()) { template.addEntity(className); } template.enlistPersistenceUnit(); - template.callHibernateFeatureInit(); //set up the scanner, as this scanning has already been done we need to just tell it about the classes we //have discovered. This scanner is bytecode serializable and is passed directly into the template QuarkusScanner scanner = new QuarkusScanner(); Set classDescriptors = new HashSet<>(); - for (String i : domainObjects.getClassNames()) { + for (String i : domainObjects.getAllModelClassNames()) { QuarkusScanner.ClassDescriptorImpl desc = new QuarkusScanner.ClassDescriptorImpl(i, ClassDescriptor.Categorization.MODEL); classDescriptors.add(desc); @@ -192,7 +204,25 @@ public void build(RecorderContext recorder, HibernateOrmTemplate template, //now we serialize the XML and class list to bytecode, to remove the need to re-parse the XML on JVM startup recorder.registerNonDefaultConstructor(ParsedPersistenceXmlDescriptor.class.getDeclaredConstructor(URL.class), (i) -> Collections.singletonList(i.getPersistenceUnitRootUrl())); - beanContainerListener.produce(new BeanContainerListenerBuildItem(template.initMetadata(descriptors, scanner))); + + // inspect service files for additional integrators + Collection> integratorClasses = new LinkedHashSet<>(); + for (String integratorClassName : ServiceUtil.classNamesNamedIn(getClass().getClassLoader(), + "META-INF/services/org.hibernate.integrator.spi.Integrator")) { + integratorClasses.add((Class) recorder.classProxy(integratorClassName)); + } + + // inspect service files for service contributors + Collection> serviceContributorClasses = new LinkedHashSet<>(); + for (String serviceContributorClassName : ServiceUtil.classNamesNamedIn(getClass().getClassLoader(), + "META-INF/services/org.hibernate.service.spi.ServiceContributor")) { + serviceContributorClasses + .add((Class) recorder.classProxy(serviceContributorClassName)); + } + + beanContainerListener + .produce(new BeanContainerListenerBuildItem( + template.initMetadata(allDescriptors, scanner, integratorClasses, serviceContributorClasses))); } @BuildStep @@ -217,13 +247,13 @@ void handleNativeImageImportSql(BuildProducer resour @BuildStep void setupResourceInjection(BuildProducer resourceAnnotations, BuildProducer resources, - JpaEntitiesBuildItem jpaEntities, List nonJpaModels) { + JpaEntitiesBuildItem jpaEntities, List nonJpaModels) throws UnsupportedEncodingException { if (!hasEntities(jpaEntities, nonJpaModels)) { return; } resources.produce(new GeneratedResourceBuildItem("META-INF/services/io.quarkus.arc.ResourceReferenceProvider", - JPAResourceReferenceProvider.class.getName().getBytes())); + JPAResourceReferenceProvider.class.getName().getBytes("UTF-8"))); resourceAnnotations.produce(new ResourceAnnotationBuildItem(PERSISTENCE_CONTEXT)); resourceAnnotations.produce(new ResourceAnnotationBuildItem(PERSISTENCE_UNIT)); } @@ -236,8 +266,10 @@ void registerBeans(BuildProducer additionalBeans, Combi return; } - additionalBeans.produce(new AdditionalBeanBuildItem(false, JPAConfig.class, TransactionEntityManagers.class, - RequestScopedEntityManagerHolder.class)); + additionalBeans.produce(AdditionalBeanBuildItem.builder().setUnremovable() + .addBeanClasses(JPAConfig.class, TransactionEntityManagers.class, + RequestScopedEntityManagerHolder.class) + .build()); if (descriptors.size() == 1) { // There is only one persistence unit - register CDI beans for EM and EMF if no @@ -286,7 +318,8 @@ public void build(HibernateOrmTemplate template, @Record(RUNTIME_INIT) public void startPersistenceUnits(HibernateOrmTemplate template, BeanContainerBuildItem beanContainer, Optional dataSourceInitialized, - JpaEntitiesBuildItem jpaEntities, List nonJpaModels) throws Exception { + JpaEntitiesBuildItem jpaEntities, List nonJpaModels, + List integrationsRuntimeConfigured) throws Exception { if (!hasEntities(jpaEntities, nonJpaModels)) { return; } @@ -295,7 +328,7 @@ public void startPersistenceUnits(HibernateOrmTemplate template, BeanContainerBu } private boolean hasEntities(JpaEntitiesBuildItem jpaEntities, List nonJpaModels) { - return !jpaEntities.getClassNames().isEmpty() || !nonJpaModels.isEmpty(); + return !jpaEntities.getEntityClassNames().isEmpty() || !nonJpaModels.isEmpty(); } private boolean isUserDefinedProducerMissing(IndexView index, DotName annotationName) { @@ -403,18 +436,21 @@ private void handleHibernateORMWithNoPersistenceXml( // sql-load-script // explicit file or default one - String file = hibernateConfig.sqlLoadScript.orElse("import.sql"); //default Hibernate ORM file imported + String importFile = hibernateConfig.sqlLoadScript.orElse("import.sql"); //default Hibernate ORM file imported Optional loadScriptPath = Optional - .ofNullable(applicationArchivesBuildItem.getRootArchive().getChildPath(file)); + .ofNullable(applicationArchivesBuildItem.getRootArchive().getChildPath(importFile)); + + // we enroll for hot deployment even if the file does not exist + hotDeploymentProducer.produce(new HotDeploymentConfigFileBuildItem(importFile)); + // enlist resource if present loadScriptPath .filter(path -> !Files.isDirectory(path)) .ifPresent(path -> { String resourceAsString = root.getPath().relativize(loadScriptPath.get()).toString(); resourceProducer.produce(new SubstrateResourceBuildItem(resourceAsString)); - hotDeploymentProducer.produce(new HotDeploymentConfigFileBuildItem(resourceAsString)); - desc.getProperties().setProperty(AvailableSettings.HBM2DDL_IMPORT_FILES, file); + desc.getProperties().setProperty(AvailableSettings.HBM2DDL_IMPORT_FILES, importFile); }); //raise exception if explicit file is not present (i.e. not the default) @@ -424,7 +460,7 @@ private void handleHibernateORMWithNoPersistenceXml( c -> { throw new ConfigurationError( "Unable to find file referenced in '" + HIBERNATE_ORM_CONFIG_PREFIX - + "sql-load-script-source=" + + "sql-load-script=" + c + "'. Remove property or add file to your path."); }); @@ -478,7 +514,7 @@ private void enhanceEntities(final JpaEntitiesBuildItem domainObjects, List additionalJpaModelBuildItems, BuildProducer additionalClasses) { HibernateEntityEnhancer hibernateEntityEnhancer = new HibernateEntityEnhancer(); - for (String i : domainObjects.getClassNames()) { + for (String i : domainObjects.getAllModelClassNames()) { transformers.produce(new BytecodeTransformerBuildItem(i, hibernateEntityEnhancer)); } for (AdditionalJpaModelBuildItem additionalJpaModel : additionalJpaModelBuildItems) { diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/JpaEntitiesBuildItem.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/JpaEntitiesBuildItem.java index 70402ed71ce1f..b97b995a41a72 100644 --- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/JpaEntitiesBuildItem.java +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/JpaEntitiesBuildItem.java @@ -19,8 +19,9 @@ import java.util.HashSet; import java.util.Set; -import org.jboss.builder.item.SimpleBuildItem; +import javax.persistence.Entity; +import io.quarkus.builder.item.SimpleBuildItem; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.builditem.substrate.ReflectiveClassBuildItem; @@ -30,19 +31,35 @@ */ public final class JpaEntitiesBuildItem extends SimpleBuildItem { - private final Set classNames = new HashSet(); + private final Set entityClassNames = new HashSet(); + private final Set allModelClassNames = new HashSet(); - void addEntity(final String className) { - classNames.add(className); + void addEntityClass(final String className) { + entityClassNames.add(className); + allModelClassNames.add(className); + } + + void addModelClass(final String className) { + allModelClassNames.add(className); } void registerAllForReflection(final BuildProducer reflectiveClass) { - for (String className : classNames) { + for (String className : allModelClassNames) { reflectiveClass.produce(new ReflectiveClassBuildItem(true, true, className)); } } - public Set getClassNames() { - return classNames; + /** + * @return the list of entities (i.e. classes marked with {@link Entity}) + */ + public Set getEntityClassNames() { + return entityClassNames; + } + + /** + * @return the list of all model class names: entities, mapped super classes... + */ + public Set getAllModelClassNames() { + return allModelClassNames; } } diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/JpaJandexScavenger.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/JpaJandexScavenger.java index 1268586351531..7b60c068f36b9 100644 --- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/JpaJandexScavenger.java +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/JpaJandexScavenger.java @@ -17,11 +17,12 @@ package io.quarkus.hibernate.orm.deployment; import java.io.IOException; -import java.io.Serializable; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.TreeSet; +import java.util.stream.Collectors; import javax.persistence.Embeddable; import javax.persistence.Embedded; @@ -33,14 +34,13 @@ import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationTarget; import org.jboss.jandex.ClassInfo; -import org.jboss.jandex.ClassType; import org.jboss.jandex.DotName; import org.jboss.jandex.FieldInfo; import org.jboss.jandex.IndexView; -import org.jboss.logging.Logger; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.builditem.substrate.ReflectiveClassBuildItem; +import io.quarkus.deployment.configuration.ConfigurationError; /** * Scan the Jandex index to find JPA entities (and embeddables supporting entity models). @@ -61,19 +61,18 @@ final class JpaJandexScavenger { private static final DotName MAPPED_SUPERCLASS = DotName.createSimple(MappedSuperclass.class.getName()); private static final DotName ENUM = DotName.createSimple(Enum.class.getName()); - private static final Logger log = Logger.getLogger("io.quarkus.hibernate.orm"); - private final List descriptors; + private final List explicitDescriptors; private final BuildProducer reflectiveClass; private final IndexView indexView; private final Set nonJpaModelClasses; JpaJandexScavenger(BuildProducer reflectiveClass, - List descriptors, + List explicitDescriptors, IndexView indexView, Set nonJpaModelClasses) { this.reflectiveClass = reflectiveClass; - this.descriptors = descriptors; + this.explicitDescriptors = explicitDescriptors; this.indexView = indexView; this.nonJpaModelClasses = nonJpaModelClasses; } @@ -83,14 +82,16 @@ public JpaEntitiesBuildItem discoverModelAndRegisterForReflection() throws IOExc // Not functional as we will need one deployment template per persistence unit final JpaEntitiesBuildItem domainObjectCollector = new JpaEntitiesBuildItem(); final Set enumTypeCollector = new HashSet<>(); + final Set unindexedClasses = new TreeSet<>(); - enlistJPAModelClasses(indexView, domainObjectCollector, enumTypeCollector, JPA_ENTITY); - enlistJPAModelClasses(indexView, domainObjectCollector, enumTypeCollector, EMBEDDABLE); - enlistJPAModelClasses(indexView, domainObjectCollector, enumTypeCollector, MAPPED_SUPERCLASS); - enlistReturnType(indexView, domainObjectCollector, enumTypeCollector); + enlistJPAModelClasses(indexView, domainObjectCollector, enumTypeCollector, JPA_ENTITY, unindexedClasses); + enlistJPAModelClasses(indexView, domainObjectCollector, enumTypeCollector, EMBEDDABLE, unindexedClasses); + enlistJPAModelClasses(indexView, domainObjectCollector, enumTypeCollector, MAPPED_SUPERCLASS, unindexedClasses); + enlistReturnType(indexView, domainObjectCollector, enumTypeCollector, unindexedClasses); - for (PersistenceUnitDescriptor pud : descriptors) { - enlistExplicitClasses(indexView, domainObjectCollector, enumTypeCollector, pud.getManagedClassNames()); + for (PersistenceUnitDescriptor pud : explicitDescriptors) { + enlistExplicitClasses(indexView, domainObjectCollector, enumTypeCollector, pud.getManagedClassNames(), + unindexedClasses); } domainObjectCollector.registerAllForReflection(reflectiveClass); @@ -102,90 +103,98 @@ public JpaEntitiesBuildItem discoverModelAndRegisterForReflection() throws IOExc } } + if (!unindexedClasses.isEmpty()) { + final String unindexedClassesErrorMessage = unindexedClasses.stream().map(d -> "\t- " + d + "\n") + .collect(Collectors.joining()); + throw new ConfigurationError( + "Unable to properly register the hierarchy of the following JPA classes as they are not in the Jandex index:\n" + + unindexedClassesErrorMessage + + "Consider adding them to the index either by creating a Jandex index " + + "for your dependency via the Maven plugin, an empty META-INF/beans.xml or quarkus.index-dependency properties."); + } + return domainObjectCollector; } private static void enlistExplicitClasses(IndexView index, JpaEntitiesBuildItem domainObjectCollector, - Set enumTypeCollector, List managedClassNames) { + Set enumTypeCollector, List managedClassNames, Set unindexedClasses) { for (String className : managedClassNames) { DotName dotName = DotName.createSimple(className); boolean isInIndex = index.getClassByName(dotName) != null; - if (isInIndex) { - addClassHierarchyToReflectiveList(index, domainObjectCollector, enumTypeCollector, dotName); - } else { - // We do lipstick service by manually adding explicitly the reference but not navigating the hierarchy - // so a class with a complex hierarchy will fail. - log.warnf( - "Did not find `%s` in the indexed jars. You likely forgot to tell Quarkus to index your dependency jar. See https://github.com/quarkus-project/quarkus/#indexing-and-application-classes for more info.", - className); - domainObjectCollector.addEntity(className); + if (!isInIndex) { + unindexedClasses.add(dotName); } + + addClassHierarchyToReflectiveList(index, domainObjectCollector, enumTypeCollector, dotName, unindexedClasses); } } private static void enlistReturnType(IndexView index, JpaEntitiesBuildItem domainObjectCollector, - Set enumTypeCollector) { + Set enumTypeCollector, Set unindexedClasses) { Collection annotations = index.getAnnotations(EMBEDDED); - if (annotations != null && annotations.size() > 0) { - for (AnnotationInstance annotation : annotations) { - AnnotationTarget target = annotation.target(); - DotName jpaClassName = null; - switch (target.kind()) { - case FIELD: - // TODO could fail if that's an array or a generic type - jpaClassName = target.asField().type().name(); - break; - case METHOD: - // TODO could fail if that's an array or a generic type - jpaClassName = target.asMethod().returnType().name(); - break; - default: - throw new IllegalStateException("[internal error] @Embedded placed on a unknown element: " + target); - } - addClassHierarchyToReflectiveList(index, domainObjectCollector, enumTypeCollector, jpaClassName); + if (annotations == null) { + return; + } + + for (AnnotationInstance annotation : annotations) { + AnnotationTarget target = annotation.target(); + DotName jpaClassName; + switch (target.kind()) { + case FIELD: + // TODO could fail if that's an array or a generic type + jpaClassName = target.asField().type().name(); + break; + case METHOD: + // TODO could fail if that's an array or a generic type + jpaClassName = target.asMethod().returnType().name(); + break; + default: + throw new IllegalStateException("[internal error] @Embedded placed on a unknown element: " + target); } + addClassHierarchyToReflectiveList(index, domainObjectCollector, enumTypeCollector, jpaClassName, + unindexedClasses); } } private void enlistJPAModelClasses(IndexView index, JpaEntitiesBuildItem domainObjectCollector, - Set enumTypeCollector, DotName dotName) { + Set enumTypeCollector, DotName dotName, Set unindexedClasses) { Collection jpaAnnotations = index.getAnnotations(dotName); - if (jpaAnnotations != null && jpaAnnotations.size() > 0) { - for (AnnotationInstance annotation : jpaAnnotations) { - ClassInfo klass = annotation.target().asClass(); - DotName targetDotName = klass.name(); - // ignore non-jpa model classes that we think belong to JPA - if (nonJpaModelClasses.contains(targetDotName.toString())) { - continue; - } - addClassHierarchyToReflectiveList(index, domainObjectCollector, enumTypeCollector, targetDotName); - domainObjectCollector.addEntity(targetDotName.toString()); + + if (jpaAnnotations == null) { + return; + } + + for (AnnotationInstance annotation : jpaAnnotations) { + ClassInfo klass = annotation.target().asClass(); + DotName targetDotName = klass.name(); + // ignore non-jpa model classes that we think belong to JPA + if (nonJpaModelClasses.contains(targetDotName.toString())) { + continue; } + addClassHierarchyToReflectiveList(index, domainObjectCollector, enumTypeCollector, targetDotName, + unindexedClasses); + collectDomainObject(domainObjectCollector, klass); } } /** * Add the class to the reflective list with full method and field access. * Add the superclasses recursively as well as the interfaces. + * Un-indexed classes/interfaces are accumulated to be thrown as a configuration error in the top level caller method *

- * TODO this approach fails if the Jandex index is not complete (e.g. misses somes interface or super types) - * TODO should we also return the return types of all methods and fields? It could container Enums for example. + * TODO should we also return the return types of all methods and fields? It could contain Enums for example. */ private static void addClassHierarchyToReflectiveList(IndexView index, JpaEntitiesBuildItem domainObjectCollector, - Set enumTypeCollector, DotName className) { - // If type is not Object - // recursively add superclass and interfaces - if (className == null) { - // java.lang.Object + Set enumTypeCollector, DotName className, Set unindexedClasses) { + if (className == null || className.toString().startsWith("java.")) { + // bail out if java.lang.Object or any java. class return; } + ClassInfo classInfo = index.getClassByName(className); if (classInfo == null) { - if (className.equals(ClassType.OBJECT_TYPE.name()) || className.toString().equals(Serializable.class.getName())) { - return; - } else { - throw new IllegalStateException("The Jandex index is not complete, missing: " + className.toString()); - } + unindexedClasses.add(className); + return; } //we need to check for enums for (FieldInfo fieldInfo : classInfo.fields()) { @@ -197,12 +206,23 @@ private static void addClassHierarchyToReflectiveList(IndexView index, JpaEntiti } //Capture this one (for various needs: Reflective access enablement, Hibernate enhancement, JPA Template) - domainObjectCollector.addEntity(className.toString()); + collectDomainObject(domainObjectCollector, classInfo); + // add superclass recursively - addClassHierarchyToReflectiveList(index, domainObjectCollector, enumTypeCollector, classInfo.superName()); + addClassHierarchyToReflectiveList(index, domainObjectCollector, enumTypeCollector, classInfo.superName(), + unindexedClasses); // add interfaces recursively for (DotName interfaceDotName : classInfo.interfaceNames()) { - addClassHierarchyToReflectiveList(index, domainObjectCollector, enumTypeCollector, interfaceDotName); + addClassHierarchyToReflectiveList(index, domainObjectCollector, enumTypeCollector, interfaceDotName, + unindexedClasses); + } + } + + private static void collectDomainObject(JpaEntitiesBuildItem domainObjectCollector, ClassInfo modelClass) { + if (modelClass.classAnnotation(JPA_ENTITY) != null) { + domainObjectCollector.addEntityClass(modelClass.name().toString()); + } else { + domainObjectCollector.addModelClass(modelClass.name().toString()); } } } diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/NonJpaModelBuildItem.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/NonJpaModelBuildItem.java index 8961b0ee39c32..d072a6366488b 100644 --- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/NonJpaModelBuildItem.java +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/NonJpaModelBuildItem.java @@ -16,7 +16,7 @@ package io.quarkus.hibernate.orm.deployment; -import org.jboss.builder.item.MultiBuildItem; +import io.quarkus.builder.item.MultiBuildItem; /** * Model class that JPA should ignore diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/PersistenceUnitDescriptorBuildItem.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/PersistenceUnitDescriptorBuildItem.java index 140b3c09c221c..39e73ec42acca 100644 --- a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/PersistenceUnitDescriptorBuildItem.java +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/PersistenceUnitDescriptorBuildItem.java @@ -17,7 +17,8 @@ package io.quarkus.hibernate.orm.deployment; import org.hibernate.jpa.boot.internal.ParsedPersistenceXmlDescriptor; -import org.jboss.builder.item.MultiBuildItem; + +import io.quarkus.builder.item.MultiBuildItem; public final class PersistenceUnitDescriptorBuildItem extends MultiBuildItem { diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/integration/HibernateOrmIntegrationBuildItem.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/integration/HibernateOrmIntegrationBuildItem.java new file mode 100644 index 0000000000000..1345a4454c760 --- /dev/null +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/integration/HibernateOrmIntegrationBuildItem.java @@ -0,0 +1,26 @@ +package io.quarkus.hibernate.orm.deployment.integration; + +import io.quarkus.builder.item.MultiBuildItem; + +public final class HibernateOrmIntegrationBuildItem extends MultiBuildItem { + + private final String name; + + public HibernateOrmIntegrationBuildItem(String name) { + if (name == null) { + throw new IllegalArgumentException("name cannot be null"); + } + this.name = name; + } + + public String getName() { + return name; + } + + @Override + public String toString() { + return new StringBuilder().append(HibernateOrmIntegrationBuildItem.class.getSimpleName()) + .append(" [").append(name).append("]") + .toString(); + } +} diff --git a/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/integration/HibernateOrmIntegrationRuntimeConfiguredBuildItem.java b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/integration/HibernateOrmIntegrationRuntimeConfiguredBuildItem.java new file mode 100644 index 0000000000000..508cb8e04ac51 --- /dev/null +++ b/extensions/hibernate-orm/deployment/src/main/java/io/quarkus/hibernate/orm/deployment/integration/HibernateOrmIntegrationRuntimeConfiguredBuildItem.java @@ -0,0 +1,26 @@ +package io.quarkus.hibernate.orm.deployment.integration; + +import io.quarkus.builder.item.MultiBuildItem; + +public final class HibernateOrmIntegrationRuntimeConfiguredBuildItem extends MultiBuildItem { + + private final String name; + + public HibernateOrmIntegrationRuntimeConfiguredBuildItem(String name) { + if (name == null) { + throw new IllegalArgumentException("name cannot be null"); + } + this.name = name; + } + + public String getName() { + return name; + } + + @Override + public String toString() { + return new StringBuilder().append(HibernateOrmIntegrationRuntimeConfiguredBuildItem.class.getSimpleName()) + .append(" [").append(name).append("]") + .toString(); + } +} diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/sql_load_script/DefaultSqlLoadScriptTestCase.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/sql_load_script/DefaultSqlLoadScriptTestCase.java new file mode 100644 index 0000000000000..1136eaf229144 --- /dev/null +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/sql_load_script/DefaultSqlLoadScriptTestCase.java @@ -0,0 +1,40 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Licensed 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.hibernate.orm.sql_load_script; + +import org.hamcrest.Matchers; +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 DefaultSqlLoadScriptTestCase { + @RegisterExtension + static QuarkusUnitTest runner = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addAsResource("application.properties") + .addClasses(SqlLoadScriptTestResource.class, MyEntity.class)); + + @Test + public void testDefaultSqlLoadScriptTest() { + String name = "default sql load script entity"; + RestAssured.when().get("/orm-sql-load-script/1").then().body(Matchers.is(name)); + } +} diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/sql_load_script/ImportSqlLoadScriptTestCase.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/sql_load_script/ImportSqlLoadScriptTestCase.java new file mode 100644 index 0000000000000..440ad1d774f7d --- /dev/null +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/sql_load_script/ImportSqlLoadScriptTestCase.java @@ -0,0 +1,41 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Licensed 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.hibernate.orm.sql_load_script; + +import org.hamcrest.Matchers; +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 ImportSqlLoadScriptTestCase { + @RegisterExtension + static QuarkusUnitTest runner = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(MyEntity.class, SqlLoadScriptTestResource.class) + .addAsResource("application-import-load-script-test.properties", "application.properties") + .addAsResource("import.sql")); + + @Test + public void testImportSqlLoadScriptTest() { + String name = "import.sql load script entity"; + RestAssured.when().get("/orm-sql-load-script/2").then().body(Matchers.is(name)); + } +} diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/sql_load_script/MyEntity.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/sql_load_script/MyEntity.java new file mode 100644 index 0000000000000..d7cd25a559dae --- /dev/null +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/sql_load_script/MyEntity.java @@ -0,0 +1,54 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Licensed 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.hibernate.orm.sql_load_script; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +@Entity +public class MyEntity { + private long id; + private String name; + + public MyEntity() { + } + + public MyEntity(String name) { + this.name = name; + } + + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "myEntitySeq") + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + +} diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/sql_load_script/SqlLoadScriptFileAbsentTestCase.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/sql_load_script/SqlLoadScriptFileAbsentTestCase.java new file mode 100644 index 0000000000000..a739e0fd3b623 --- /dev/null +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/sql_load_script/SqlLoadScriptFileAbsentTestCase.java @@ -0,0 +1,43 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Licensed 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.hibernate.orm.sql_load_script; + +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.deployment.configuration.ConfigurationError; +import io.quarkus.test.QuarkusUnitTest; + +public class SqlLoadScriptFileAbsentTestCase { + @RegisterExtension + static QuarkusUnitTest runner = new QuarkusUnitTest() + .setExpectedException(ConfigurationError.class) + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(MyEntity.class) + .addAsResource("application-other-load-script-test.properties", "application.properties")); + + @Test + public void testSqlLoadScriptFileAbsentTest() { + // should not be called, deployment exception should happen first: + // it's illegal to have Hibernate sql-load-script configuration property set + // to an absent file + Assertions.fail(); + } +} diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/sql_load_script/SqlLoadScriptPresentTestCase.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/sql_load_script/SqlLoadScriptPresentTestCase.java new file mode 100644 index 0000000000000..662ce87ce0ea1 --- /dev/null +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/sql_load_script/SqlLoadScriptPresentTestCase.java @@ -0,0 +1,41 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Licensed 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.hibernate.orm.sql_load_script; + +import org.hamcrest.Matchers; +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 SqlLoadScriptPresentTestCase { + @RegisterExtension + static QuarkusUnitTest runner = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(MyEntity.class, SqlLoadScriptTestResource.class) + .addAsResource("application-other-load-script-test.properties", "application.properties") + .addAsResource("load-script-test.sql")); + + @Test + public void testSqlLoadScriptPresentTest() { + String name = "other-load-script sql load script entity"; + RestAssured.when().get("/orm-sql-load-script/3").then().body(Matchers.is(name)); + } +} diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/sql_load_script/SqlLoadScriptTestResource.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/sql_load_script/SqlLoadScriptTestResource.java new file mode 100644 index 0000000000000..8a2b284dffc4a --- /dev/null +++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/sql_load_script/SqlLoadScriptTestResource.java @@ -0,0 +1,41 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Licensed 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.hibernate.orm.sql_load_script; + +import javax.inject.Inject; +import javax.persistence.EntityManager; +import javax.ws.rs.*; +import javax.ws.rs.core.MediaType; + +@Path("/orm-sql-load-script") +public class SqlLoadScriptTestResource { + + @Inject + EntityManager em; + + @GET + @Path("/{id}") + @Produces(MediaType.TEXT_PLAIN) + public String getName(@PathParam("id") long id) { + MyEntity entity = em.find(MyEntity.class, id); + if (entity != null) { + return entity.getName(); + } + + return null; + } +} diff --git a/extensions/hibernate-orm/deployment/src/test/resources/application-import-load-script-test.properties b/extensions/hibernate-orm/deployment/src/test/resources/application-import-load-script-test.properties new file mode 100644 index 0000000000000..a1f7b6a62f9c3 --- /dev/null +++ b/extensions/hibernate-orm/deployment/src/test/resources/application-import-load-script-test.properties @@ -0,0 +1,7 @@ +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 +quarkus.hibernate-orm.sql-load-script=import.sql diff --git a/extensions/hibernate-orm/deployment/src/test/resources/application-other-load-script-test.properties b/extensions/hibernate-orm/deployment/src/test/resources/application-other-load-script-test.properties new file mode 100644 index 0000000000000..fa93ba61d0f24 --- /dev/null +++ b/extensions/hibernate-orm/deployment/src/test/resources/application-other-load-script-test.properties @@ -0,0 +1,7 @@ +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 +quarkus.hibernate-orm.sql-load-script=load-script-test.sql diff --git a/extensions/hibernate-orm/deployment/src/test/resources/application.properties b/extensions/hibernate-orm/deployment/src/test/resources/application.properties index 6933efa0cc93c..1699c91584e55 100644 --- a/extensions/hibernate-orm/deployment/src/test/resources/application.properties +++ b/extensions/hibernate-orm/deployment/src/test/resources/application.properties @@ -1,4 +1,4 @@ -quarkus.datasource.url=jdbc:h2:tcp://localhost/mem:test +quarkus.datasource.url=jdbc:h2:mem:test quarkus.datasource.driver=org.h2.Driver quarkus.hibernate-orm.dialect=org.hibernate.dialect.H2Dialect diff --git a/extensions/hibernate-orm/deployment/src/test/resources/import.sql b/extensions/hibernate-orm/deployment/src/test/resources/import.sql new file mode 100644 index 0000000000000..861dca425e30c --- /dev/null +++ b/extensions/hibernate-orm/deployment/src/test/resources/import.sql @@ -0,0 +1,2 @@ +INSERT INTO MyEntity(id, name) VALUES(1, 'default sql load script entity'); +INSERT INTO MyEntity(id, name) VALUES(2, 'import.sql load script entity'); diff --git a/extensions/hibernate-orm/deployment/src/test/resources/load-script-test.sql b/extensions/hibernate-orm/deployment/src/test/resources/load-script-test.sql new file mode 100644 index 0000000000000..534bfa16f0254 --- /dev/null +++ b/extensions/hibernate-orm/deployment/src/test/resources/load-script-test.sql @@ -0,0 +1 @@ +INSERT INTO MyEntity(id, name) VALUES(3, 'other-load-script sql load script entity'); diff --git a/extensions/hibernate-orm/runtime/pom.xml b/extensions/hibernate-orm/runtime/pom.xml index eba68d14fdb04..22c39b787ff3d 100644 --- a/extensions/hibernate-orm/runtime/pom.xml +++ b/extensions/hibernate-orm/runtime/pom.xml @@ -25,31 +25,33 @@ 4.0.0 - quarkus-hibernate-orm-runtime + quarkus-hibernate-orm Quarkus - Hibernate ORM - Runtime io.quarkus - quarkus-core-runtime + quarkus-core io.quarkus - quarkus-arc-runtime + quarkus-agroal + + + io.quarkus + quarkus-arc compile true + + io.quarkus + quarkus-narayana-jta + org.hibernate hibernate-core - - - net.bytebuddy - byte-buddy - - org.javassist @@ -88,14 +90,15 @@ other Quarkus components (yet) --> io.quarkus - quarkus-caffeine-runtime + quarkus-caffeine - maven-dependency-plugin + io.quarkus + quarkus-bootstrap-maven-plugin maven-compiler-plugin diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/FastBootHibernatePersistenceProvider.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/FastBootHibernatePersistenceProvider.java index af211c19195ec..5055e2ffa1e8d 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/FastBootHibernatePersistenceProvider.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/FastBootHibernatePersistenceProvider.java @@ -35,12 +35,14 @@ import org.hibernate.jpa.boot.spi.EntityManagerFactoryBuilder; import org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor; import org.hibernate.jpa.internal.util.PersistenceUtilHelper; +import org.hibernate.service.internal.ProvidedService; import org.jboss.logging.Logger; import io.quarkus.arc.Arc; import io.quarkus.arc.InstanceHandle; import io.quarkus.hibernate.orm.runtime.boot.FastBootEntityManagerFactoryBuilder; import io.quarkus.hibernate.orm.runtime.boot.registry.PreconfiguredServiceRegistryBuilder; +import io.quarkus.hibernate.orm.runtime.integration.HibernateOrmIntegrations; import io.quarkus.hibernate.orm.runtime.recording.RecordedState; /** @@ -162,16 +164,20 @@ private EntityManagerFactoryBuilder getEntityManagerFactoryBuilderOrNull(String final MetadataImplementor metadata = recordedState.getMetadata(); final BuildTimeSettings buildTimeSettings = recordedState.getBuildTimeSettings(); + final IntegrationSettings integrationSettings = recordedState.getIntegrationSettings(); // TODO: final Object validatorFactory = null; // TODO: final Object cdiBeanManager = null; - RuntimeSettings.Builder runtimeSettingsBuilder = new RuntimeSettings.Builder(buildTimeSettings); + RuntimeSettings.Builder runtimeSettingsBuilder = new RuntimeSettings.Builder(buildTimeSettings, + integrationSettings); // Inject the datasource injectDataSource(persistenceUnitName, runtimeSettingsBuilder); + HibernateOrmIntegrations.contributeRuntimeProperties((k, v) -> runtimeSettingsBuilder.put(k, v)); + RuntimeSettings runtimeSettings = runtimeSettingsBuilder.build(); StandardServiceRegistry standardServiceRegistry = rewireMetadataAndExtractServiceRegistry( @@ -196,9 +202,12 @@ private StandardServiceRegistry rewireMetadataAndExtractServiceRegistry(RuntimeS runtimeSettings.getSettings().forEach((key, value) -> { serviceRegistryBuilder.applySetting(key, value); }); + + for (ProvidedService providedService : rs.getProvidedServices()) { + serviceRegistryBuilder.addService(providedService); + } + // TODO serviceRegistryBuilder.addInitiator( ) - // TODO serviceRegistryBuilder.applyIntegrator( ) - // TODO serviceregistryBuilder.addService( ) StandardServiceRegistryImpl standardServiceRegistry = serviceRegistryBuilder.buildNewServiceRegistry(); return standardServiceRegistry; diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/HibernateOrmTemplate.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/HibernateOrmTemplate.java index 2cdadef120d23..01f27672e738d 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/HibernateOrmTemplate.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/HibernateOrmTemplate.java @@ -17,10 +17,13 @@ package io.quarkus.hibernate.orm.runtime; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import org.hibernate.boot.archive.scan.spi.Scanner; +import org.hibernate.integrator.spi.Integrator; import org.hibernate.jpa.boot.internal.ParsedPersistenceXmlDescriptor; +import org.hibernate.service.spi.ServiceContributor; import org.jboss.logging.Logger; import io.quarkus.arc.runtime.BeanContainer; @@ -75,11 +78,13 @@ public void created(BeanContainer beanContainer) { } public BeanContainerListener initMetadata(List parsedPersistenceXmlDescriptors, - Scanner scanner) { + Scanner scanner, Collection> additionalIntegrators, + Collection> additionalServiceContributors) { return new BeanContainerListener() { @Override public void created(BeanContainer beanContainer) { - PersistenceUnitsHolder.initializeJpa(parsedPersistenceXmlDescriptors, scanner); + PersistenceUnitsHolder.initializeJpa(parsedPersistenceXmlDescriptors, scanner, additionalIntegrators, + additionalServiceContributors); } }; } diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/IntegrationSettings.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/IntegrationSettings.java new file mode 100644 index 0000000000000..c676c25808cbd --- /dev/null +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/IntegrationSettings.java @@ -0,0 +1,34 @@ +package io.quarkus.hibernate.orm.runtime; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public class IntegrationSettings { + + private final Map settings; + + private IntegrationSettings(Map settings) { + this.settings = Collections.unmodifiableMap(new HashMap<>(settings)); + } + + public Map getSettings() { + return settings; + } + + public static class Builder { + + private final Map settings = new HashMap<>(); + + public Builder() { + } + + public void put(String key, Object value) { + settings.put(key, value); + } + + public IntegrationSettings build() { + return new IntegrationSettings(settings); + } + } +} diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/JPAConfig.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/JPAConfig.java index eee9d5f107248..5df1f2c75e74c 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/JPAConfig.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/JPAConfig.java @@ -18,10 +18,13 @@ import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import javax.annotation.PreDestroy; +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.context.BeforeDestroyed; import javax.inject.Singleton; import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence; @@ -41,7 +44,7 @@ public class JPAConfig { public JPAConfig() { this.jtaEnabled = new AtomicBoolean(); - this.persistenceUnits = new HashMap<>(); + this.persistenceUnits = new ConcurrentHashMap<>(); this.defaultPersistenceUnitName = new AtomicReference(); } @@ -82,8 +85,13 @@ boolean isJtaEnabled() { return jtaEnabled.get(); } - @PreDestroy - void destroy() { + /** + * Need to shutdown all instances of Hibernate ORM before the actual destroy event, + * as it might need to use the datasources during shutdown. + * + * @param event ignored + */ + void destroy(@BeforeDestroyed(ApplicationScoped.class) Object event) { for (LazyPersistenceUnit factory : persistenceUnits.values()) { try { factory.close(); @@ -91,6 +99,10 @@ void destroy() { LOGGER.warn("Unable to close the EntityManagerFactory: " + factory, e); } } + } + + @PreDestroy + void destroy() { persistenceUnits.clear(); } diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/PersistenceUnitsHolder.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/PersistenceUnitsHolder.java index 371c26df55d37..fb12980bfab55 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/PersistenceUnitsHolder.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/PersistenceUnitsHolder.java @@ -16,6 +16,7 @@ package io.quarkus.hibernate.orm.runtime; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -24,8 +25,10 @@ import javax.persistence.PersistenceException; import org.hibernate.boot.archive.scan.spi.Scanner; +import org.hibernate.integrator.spi.Integrator; import org.hibernate.jpa.boot.internal.ParsedPersistenceXmlDescriptor; import org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor; +import org.hibernate.service.spi.ServiceContributor; import io.quarkus.hibernate.orm.runtime.boot.FastBootMetadataBuilder; import io.quarkus.hibernate.orm.runtime.boot.LightPersistenceXmlDescriptor; @@ -54,9 +57,11 @@ public final class PersistenceUnitsHolder { * @param scanner */ static void initializeJpa(List parsedPersistenceXmlDescriptors, - Scanner scanner) { + Scanner scanner, Collection> additionalIntegrators, + Collection> additionalServiceContributors) { final List units = convertPersistenceUnits(parsedPersistenceXmlDescriptors); - final Map metadata = constructMetadataAdvance(units, scanner); + final Map metadata = constructMetadataAdvance(units, scanner, additionalIntegrators, + additionalServiceContributors); persistenceUnits = new PersistenceUnits(units, metadata); } @@ -86,11 +91,13 @@ private static List convertPersistenceUnits( } private static Map constructMetadataAdvance( - final List parsedPersistenceXmlDescriptors, Scanner scanner) { + final List parsedPersistenceXmlDescriptors, Scanner scanner, + Collection> additionalIntegrators, + Collection> additionalServiceContributors) { Map recordedStates = new HashMap<>(); for (PersistenceUnitDescriptor unit : parsedPersistenceXmlDescriptors) { - RecordedState m = createMetadata(unit, scanner); + RecordedState m = createMetadata(unit, scanner, additionalIntegrators); Object previous = recordedStates.put(unitName(unit), m); if (previous != null) { throw new IllegalStateException("Duplicate persistence unit name: " + unit.getName()); @@ -114,8 +121,9 @@ private static String unitName(PersistenceUnitDescriptor unit) { return name; } - private static RecordedState createMetadata(PersistenceUnitDescriptor unit, Scanner scanner) { - FastBootMetadataBuilder fastBootMetadataBuilder = new FastBootMetadataBuilder(unit, scanner); + private static RecordedState createMetadata(PersistenceUnitDescriptor unit, Scanner scanner, + Collection> additionalIntegrators) { + FastBootMetadataBuilder fastBootMetadataBuilder = new FastBootMetadataBuilder(unit, scanner, additionalIntegrators); return fastBootMetadataBuilder.build(); } diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/RuntimeSettings.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/RuntimeSettings.java index 8b32525ed1070..4e7778fbfd291 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/RuntimeSettings.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/RuntimeSettings.java @@ -33,8 +33,9 @@ public static class Builder { private final Map settings; - public Builder(BuildTimeSettings buildTimeSettings) { + public Builder(BuildTimeSettings buildTimeSettings, IntegrationSettings integrationSettings) { this.settings = new HashMap<>(buildTimeSettings.getSettings()); + this.settings.putAll(integrationSettings.getSettings()); } public void put(String key, Object value) { diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/FastBootMetadataBuilder.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/FastBootMetadataBuilder.java index 9e21afb6ef4c7..5ee3d92d89395 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/FastBootMetadataBuilder.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/FastBootMetadataBuilder.java @@ -40,7 +40,9 @@ import static org.hibernate.jpa.AvailableSettings.PERSISTENCE_UNIT_NAME; import java.util.ArrayList; +import java.util.Collection; import java.util.Iterator; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.StringTokenizer; @@ -67,6 +69,7 @@ import org.hibernate.boot.spi.MetadataBuilderContributor; import org.hibernate.boot.spi.MetadataBuilderImplementor; import org.hibernate.boot.spi.MetadataImplementor; +import org.hibernate.cache.internal.CollectionCacheInvalidator; import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.Environment; import org.hibernate.cfg.beanvalidation.BeanValidationIntegrator; @@ -75,6 +78,7 @@ import org.hibernate.engine.transaction.jta.platform.internal.JBossStandAloneJtaPlatform; import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform; import org.hibernate.id.factory.spi.MutableIdentifierGeneratorFactory; +import org.hibernate.integrator.spi.Integrator; import org.hibernate.internal.EntityManagerMessageLogger; import org.hibernate.internal.util.StringHelper; import org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl; @@ -87,9 +91,13 @@ import org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorBuilderImpl; import org.hibernate.resource.transaction.backend.jta.internal.JtaTransactionCoordinatorBuilderImpl; import org.hibernate.service.internal.AbstractServiceRegistryImpl; +import org.hibernate.service.internal.ProvidedService; +import org.hibernate.service.spi.ServiceContributor; import org.infinispan.quarkus.hibernate.cache.QuarkusInfinispanRegionFactory; import io.quarkus.hibernate.orm.runtime.BuildTimeSettings; +import io.quarkus.hibernate.orm.runtime.IntegrationSettings; +import io.quarkus.hibernate.orm.runtime.integration.HibernateOrmIntegrations; import io.quarkus.hibernate.orm.runtime.recording.RecordableBootstrap; import io.quarkus.hibernate.orm.runtime.recording.RecordedState; import io.quarkus.hibernate.orm.runtime.recording.RecordingDialectFactory; @@ -109,10 +117,14 @@ public class FastBootMetadataBuilder { private final ManagedResources managedResources; private final MetadataBuilderImplementor metamodelBuilder; private final Object validatorFactory; + private final Collection> additionalIntegrators; + private final Collection providedServices; @SuppressWarnings("unchecked") - public FastBootMetadataBuilder(final PersistenceUnitDescriptor persistenceUnit, Scanner scanner) { + public FastBootMetadataBuilder(final PersistenceUnitDescriptor persistenceUnit, Scanner scanner, + Collection> additionalIntegrators) { this.persistenceUnit = persistenceUnit; + this.additionalIntegrators = additionalIntegrators; final ClassLoaderService providedClassLoaderService = FlatClassLoaderService.INSTANCE; // Copying semantics from: new EntityManagerFactoryBuilderImpl( unit, @@ -139,6 +151,8 @@ public FastBootMetadataBuilder(final PersistenceUnitDescriptor persistenceUnit, this.standardServiceRegistry = ssrBuilder.build(); registerIdentifierGenerators(standardServiceRegistry); + this.providedServices = ssrBuilder.getProvidedServices(); + final MetadataSources metadataSources = new MetadataSources(bsr); addPUManagedClassNamesToMetadataSources(persistenceUnit, metadataSources); @@ -280,6 +294,8 @@ private MergedSettings mergeSettings(PersistenceUnitDescriptor persistenceUnit) mergedSettings.configurationValues.put(org.hibernate.cfg.AvailableSettings.CACHE_REGION_FACTORY, QuarkusInfinispanRegionFactory.class.getName()); + HibernateOrmIntegrations.contributeBootProperties((k, v) -> mergedSettings.configurationValues.put(k, v)); + return mergedSettings; } @@ -300,11 +316,17 @@ public RecordedState build() { metamodelBuilder.getBootstrapContext(), metamodelBuilder.getMetadataBuildingOptions() //INTERCEPT & DESTROY :) ); + + IntegrationSettings.Builder integrationSettingsBuilder = new IntegrationSettings.Builder(); + HibernateOrmIntegrations.onMetadataInitialized(fullMeta, metamodelBuilder.getBootstrapContext(), + (k, v) -> integrationSettingsBuilder.put(k, v)); + Dialect dialect = extractDialect(); JtaPlatform jtaPlatform = extractJtaPlatform(); destroyServiceRegistry(fullMeta); MetadataImplementor storeableMetadata = trimBootstrapMetadata(fullMeta); - return new RecordedState(dialect, jtaPlatform, storeableMetadata, buildTimeSettings); + return new RecordedState(dialect, jtaPlatform, storeableMetadata, buildTimeSettings, getIntegrators(), + providedServices, integrationSettingsBuilder.build()); } private void destroyServiceRegistry(MetadataImplementor fullMeta) { @@ -352,6 +374,22 @@ private Dialect extractDialect() { return casted.getDialect(); } + private Collection getIntegrators() { + LinkedHashSet integrators = new LinkedHashSet<>(); + integrators.add(new BeanValidationIntegrator()); + integrators.add(new CollectionCacheInvalidator()); + + for (Class integratorClass : additionalIntegrators) { + try { + integrators.add(integratorClass.getConstructor().newInstance()); + } catch (Exception e) { + throw new IllegalArgumentException("Unable to instantiate integrator " + integratorClass, e); + } + } + + return integrators; + } + @SuppressWarnings("rawtypes") private static class MergedSettings { private final Map configurationValues = new ConcurrentHashMap(16, 0.75f, 1); diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/registry/MirroringIntegratorService.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/registry/MirroringIntegratorService.java index 16c790208f122..c51c54ee67054 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/registry/MirroringIntegratorService.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/registry/MirroringIntegratorService.java @@ -16,7 +16,7 @@ package io.quarkus.hibernate.orm.runtime.boot.registry; -import java.util.LinkedHashSet; +import java.util.Collection; import org.hibernate.integrator.spi.Integrator; import org.hibernate.integrator.spi.IntegratorService; @@ -35,10 +35,10 @@ */ final class MirroringIntegratorService implements IntegratorService { - private final LinkedHashSet integrators = new LinkedHashSet(); + private final Collection integrators; - void addIntegrator(Integrator integrator) { - integrators.add(integrator); + MirroringIntegratorService(Collection integrators) { + this.integrators = integrators; } @Override diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/registry/PreconfiguredServiceRegistryBuilder.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/registry/PreconfiguredServiceRegistryBuilder.java index 10268029ae205..c114f6b58f0ff 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/registry/PreconfiguredServiceRegistryBuilder.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/boot/registry/PreconfiguredServiceRegistryBuilder.java @@ -19,6 +19,7 @@ import static org.hibernate.internal.HEMLogging.messageLogger; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -28,7 +29,6 @@ import org.hibernate.boot.registry.internal.BootstrapServiceRegistryImpl; import org.hibernate.boot.registry.internal.StandardServiceRegistryImpl; import org.hibernate.boot.registry.selector.internal.StrategySelectorImpl; -import org.hibernate.cache.internal.RegionFactoryInitiator; import org.hibernate.engine.config.internal.ConfigurationServiceInitiator; import org.hibernate.engine.jdbc.batch.internal.BatchBuilderInitiator; import org.hibernate.engine.jdbc.connections.internal.ConnectionProviderInitiator; @@ -41,7 +41,6 @@ import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform; import org.hibernate.event.internal.EntityCopyObserverFactoryInitiator; import org.hibernate.hql.internal.QueryTranslatorFactoryInitiator; -import org.hibernate.id.factory.internal.MutableIdentifierGeneratorFactoryInitiator; import org.hibernate.integrator.spi.Integrator; import org.hibernate.internal.EntityManagerMessageLogger; import org.hibernate.internal.util.config.ConfigurationHelper; @@ -53,12 +52,18 @@ import org.hibernate.service.Service; import org.hibernate.service.internal.ProvidedService; import org.hibernate.service.internal.SessionFactoryServiceRegistryFactoryInitiator; +import org.hibernate.service.spi.ServiceContributor; import org.hibernate.service.spi.ServiceRegistryImplementor; import org.hibernate.tool.hbm2ddl.ImportSqlCommandExtractorInitiator; import org.hibernate.tool.schema.internal.SchemaManagementToolInitiator; import io.quarkus.hibernate.orm.runtime.recording.RecordedState; -import io.quarkus.hibernate.orm.runtime.service.*; +import io.quarkus.hibernate.orm.runtime.service.CfgXmlAccessServiceInitiatorQuarkus; +import io.quarkus.hibernate.orm.runtime.service.DisabledJMXInitiator; +import io.quarkus.hibernate.orm.runtime.service.FlatClassLoaderService; +import io.quarkus.hibernate.orm.runtime.service.QuarkusJdbcEnvironmentInitiator; +import io.quarkus.hibernate.orm.runtime.service.QuarkusJtaPlatformResolver; +import io.quarkus.hibernate.orm.runtime.service.QuarkusRegionFactoryInitiator; /** * Helps to instantiate a ServiceRegistryBuilder from a previous state. This @@ -76,11 +81,12 @@ public class PreconfiguredServiceRegistryBuilder { private final Map configurationValues = new HashMap(); private final List initiators; private final List providedServices = new ArrayList(); - private final MirroringIntegratorService integrators = new MirroringIntegratorService(); + private final Collection integrators; private final StandardServiceRegistryImpl destroyedRegistry; public PreconfiguredServiceRegistryBuilder(RecordedState rs) { this.initiators = buildQuarkusServiceInitiatorList(rs); + this.integrators = rs.getIntegrators(); this.destroyedRegistry = (StandardServiceRegistryImpl) rs.getMetadata().getMetadataBuildingOptions() .getServiceRegistry(); } @@ -90,18 +96,13 @@ public PreconfiguredServiceRegistryBuilder applySetting(String settingName, Obje return this; } - public PreconfiguredServiceRegistryBuilder applyIntegrator(Integrator integrator) { - integrators.addIntegrator(integrator); - return this; - } - public PreconfiguredServiceRegistryBuilder addInitiator(StandardServiceInitiator initiator) { initiators.add(initiator); return this; } - public PreconfiguredServiceRegistryBuilder addService(final Class serviceRole, final Service service) { - providedServices.add(new ProvidedService(serviceRole, service)); + public PreconfiguredServiceRegistryBuilder addService(ProvidedService providedService) { + providedServices.add(providedService); return this; } @@ -143,7 +144,7 @@ private BootstrapServiceRegistry buildEmptyBootstrapServiceRegistry() { return new BootstrapServiceRegistryImpl(true, FlatClassLoaderService.INSTANCE, strategySelector, // new MirroringStrategySelector(), - integrators); + new MirroringIntegratorService(integrators)); } /** @@ -200,7 +201,9 @@ private static List buildQuarkusServiceInitiatorList(R serviceInitiators.add(RefCursorSupportInitiator.INSTANCE); serviceInitiators.add(QueryTranslatorFactoryInitiator.INSTANCE); - serviceInitiators.add(MutableIdentifierGeneratorFactoryInitiator.INSTANCE); + + // Disabled: IdentifierGenerators are no longer initiated after Metadata was generated. + // serviceInitiators.add(MutableIdentifierGeneratorFactoryInitiator.INSTANCE); // Replaces JtaPlatformResolverInitiator.INSTANCE ); serviceInitiators.add(new QuarkusJtaPlatformResolver(rs.getJtaPlatform())); diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/integration/HibernateOrmIntegrationListener.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/integration/HibernateOrmIntegrationListener.java new file mode 100644 index 0000000000000..94ede2a244536 --- /dev/null +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/integration/HibernateOrmIntegrationListener.java @@ -0,0 +1,16 @@ +package io.quarkus.hibernate.orm.runtime.integration; + +import java.util.function.BiConsumer; + +import org.hibernate.boot.Metadata; +import org.hibernate.boot.spi.BootstrapContext; + +public interface HibernateOrmIntegrationListener { + + void contributeBootProperties(BiConsumer propertyCollector); + + void onMetadataInitialized(Metadata metadata, BootstrapContext bootstrapContext, + BiConsumer propertyCollector); + + void contributeRuntimeProperties(BiConsumer propertyCollector); +} diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/integration/HibernateOrmIntegrations.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/integration/HibernateOrmIntegrations.java new file mode 100644 index 0000000000000..ab17ce3801c5c --- /dev/null +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/integration/HibernateOrmIntegrations.java @@ -0,0 +1,38 @@ +package io.quarkus.hibernate.orm.runtime.integration; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiConsumer; + +import org.hibernate.boot.Metadata; +import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; +import org.hibernate.boot.spi.BootstrapContext; + +public class HibernateOrmIntegrations { + + private static final List LISTENERS = new ArrayList<>(); + + public static void registerListener(HibernateOrmIntegrationListener listener) { + LISTENERS.add(listener); + } + + public static void contributeBootProperties(BiConsumer propertyCollector) { + for (HibernateOrmIntegrationListener listener : LISTENERS) { + listener.contributeBootProperties(propertyCollector); + } + } + + public static void onMetadataInitialized(Metadata metadata, BootstrapContext bootstrapContext, + BiConsumer propertyCollector) { + for (HibernateOrmIntegrationListener listener : LISTENERS) { + ClassLoaderService cls = bootstrapContext.getServiceRegistry().getService(ClassLoaderService.class); + listener.onMetadataInitialized(metadata, bootstrapContext, propertyCollector); + } + } + + public static void contributeRuntimeProperties(BiConsumer propertyCollector) { + for (HibernateOrmIntegrationListener listener : LISTENERS) { + listener.contributeRuntimeProperties(propertyCollector); + } + } +} diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/recording/RecordableBootstrap.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/recording/RecordableBootstrap.java index ca80076832afa..8926100b79170 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/recording/RecordableBootstrap.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/recording/RecordableBootstrap.java @@ -67,6 +67,7 @@ import io.quarkus.hibernate.orm.runtime.boot.QuarkusEnvironment; import io.quarkus.hibernate.orm.runtime.service.DialectFactoryInitiator; import io.quarkus.hibernate.orm.runtime.service.DisabledJMXInitiator; +import io.quarkus.hibernate.orm.runtime.service.QuarkusMutableIdentifierGeneratorFactoryInitiator; import io.quarkus.hibernate.orm.runtime.service.QuarkusRegionFactoryInitiator; /** @@ -136,7 +137,9 @@ private static List standardInitiatorList() { serviceInitiators.add(RefCursorSupportInitiator.INSTANCE); serviceInitiators.add(QueryTranslatorFactoryInitiator.INSTANCE); - serviceInitiators.add(MutableIdentifierGeneratorFactoryInitiator.INSTANCE); + + // Custom one! Also, this one has state so can't use the singleton. + serviceInitiators.add(new QuarkusMutableIdentifierGeneratorFactoryInitiator());// MutableIdentifierGeneratorFactoryInitiator.INSTANCE); serviceInitiators.add(JtaPlatformResolverInitiator.INSTANCE); serviceInitiators.add(JtaPlatformInitiator.INSTANCE); @@ -382,6 +385,10 @@ private void applyServiceContributors() { } } + public List getProvidedServices() { + return providedServices; + } + /** * Temporarily exposed since Configuration is still around and much code still * uses Configuration. This allows code to configure the builder and access that diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/recording/RecordedState.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/recording/RecordedState.java index cd3ccd9c017dd..4e614f4963c13 100644 --- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/recording/RecordedState.java +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/recording/RecordedState.java @@ -16,11 +16,16 @@ package io.quarkus.hibernate.orm.runtime.recording; +import java.util.Collection; + import org.hibernate.boot.spi.MetadataImplementor; import org.hibernate.dialect.Dialect; import org.hibernate.engine.transaction.jta.platform.spi.JtaPlatform; +import org.hibernate.integrator.spi.Integrator; +import org.hibernate.service.internal.ProvidedService; import io.quarkus.hibernate.orm.runtime.BuildTimeSettings; +import io.quarkus.hibernate.orm.runtime.IntegrationSettings; public final class RecordedState { @@ -28,13 +33,20 @@ public final class RecordedState { private final MetadataImplementor metadata; private final JtaPlatform jtaPlatform; private final BuildTimeSettings settings; + private final Collection integrators; + private final Collection providedServices; + private final IntegrationSettings integrationSettings; public RecordedState(Dialect dialect, JtaPlatform jtaPlatform, MetadataImplementor metadata, - BuildTimeSettings settings) { + BuildTimeSettings settings, Collection integrators, + Collection providedServices, IntegrationSettings integrationSettings) { this.dialect = dialect; this.jtaPlatform = jtaPlatform; this.metadata = metadata; this.settings = settings; + this.integrators = integrators; + this.providedServices = providedServices; + this.integrationSettings = integrationSettings; } public Dialect getDialect() { @@ -49,6 +61,18 @@ public BuildTimeSettings getBuildTimeSettings() { return settings; } + public Collection getIntegrators() { + return integrators; + } + + public Collection getProvidedServices() { + return providedServices; + } + + public IntegrationSettings getIntegrationSettings() { + return integrationSettings; + } + public JtaPlatform getJtaPlatform() { return jtaPlatform; } diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/service/QuarkusMutableIdentifierGeneratorFactory.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/service/QuarkusMutableIdentifierGeneratorFactory.java new file mode 100644 index 0000000000000..e7b98e75d54d8 --- /dev/null +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/service/QuarkusMutableIdentifierGeneratorFactory.java @@ -0,0 +1,82 @@ +package io.quarkus.hibernate.orm.runtime.service; + +import java.io.Serializable; +import java.util.Properties; +import java.util.concurrent.ConcurrentHashMap; + +import org.hibernate.dialect.Dialect; +import org.hibernate.id.IdentifierGenerator; +import org.hibernate.id.factory.internal.DefaultIdentifierGeneratorFactory; +import org.hibernate.id.factory.spi.MutableIdentifierGeneratorFactory; +import org.hibernate.service.spi.ServiceRegistryAwareService; +import org.hibernate.service.spi.ServiceRegistryImplementor; +import org.hibernate.type.Type; + +/** + * Wraps the default DefaultIdentifierGeneratorFactory so to make sure we store the Class references + * of any IdentifierGenerator which is accessed during the build of the Metadata. + * + * This is not to register them for reflection access: all reflective instantiation is performed + * during the build of the Metadata and is therefore safe even in native mode; + * however we still need the Class instances as some runtime operations will need these, and + * will look them up by either fully qualified name (and then reflection) or strategy name. + * + * Since all IdentifierGenerator types used by a model are accessed during the Metadata creation, + * just watching for these will provide the full list of Class instances we need to keep. + */ +public final class QuarkusMutableIdentifierGeneratorFactory + implements MutableIdentifierGeneratorFactory, Serializable, ServiceRegistryAwareService { + + private final DefaultIdentifierGeneratorFactory original = new DefaultIdentifierGeneratorFactory(); + private final ConcurrentHashMap> typeCache = new ConcurrentHashMap<>(); + + @Override + public void register(final String strategy, final Class generatorClass) { + original.register(strategy, generatorClass); + storeCache(strategy, generatorClass); + } + + @Override + public Dialect getDialect() { + return original.getDialect(); + } + + @Override + public void setDialect(final Dialect dialect) { + //currently a no-op anyway..? + original.setDialect(dialect); + } + + @Override + public IdentifierGenerator createIdentifierGenerator(final String strategy, final Type type, final Properties config) { + final IdentifierGenerator identifierGenerator = original.createIdentifierGenerator(strategy, type, config); + storeCache(strategy, identifierGenerator.getClass()); + return identifierGenerator; + } + + private void storeCache(final String strategy, final Class generatorClass) { + if (strategy == null || generatorClass == null) + return; + final String className = generatorClass.getName(); + //Store for access both via short and long names: + typeCache.put(strategy, generatorClass); + if (!className.equals(strategy)) { + typeCache.put(className, generatorClass); + } + } + + @Override + public Class getIdentifierGeneratorClass(final String strategy) { + Class aClass = typeCache.get(strategy); + if (aClass != null) + return aClass; + aClass = original.getIdentifierGeneratorClass(strategy); + storeCache(strategy, aClass); + return aClass; + } + + @Override + public void injectServices(final ServiceRegistryImplementor serviceRegistry) { + original.injectServices(serviceRegistry); + } +} diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/service/QuarkusMutableIdentifierGeneratorFactoryInitiator.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/service/QuarkusMutableIdentifierGeneratorFactoryInitiator.java new file mode 100644 index 0000000000000..dee8a10dcd10b --- /dev/null +++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/service/QuarkusMutableIdentifierGeneratorFactoryInitiator.java @@ -0,0 +1,33 @@ +package io.quarkus.hibernate.orm.runtime.service; + +import java.util.Map; + +import org.hibernate.boot.registry.StandardServiceInitiator; +import org.hibernate.id.factory.internal.MutableIdentifierGeneratorFactoryInitiator; +import org.hibernate.id.factory.spi.MutableIdentifierGeneratorFactory; +import org.hibernate.service.spi.ServiceRegistryImplementor; + +/** + * Needs to mimick MutableIdentifierGeneratorFactoryInitiator, but allows us to capture + * which Identifier strategies are being used, so that we can keep a reference to the classed + * needed at runtime. + * + * @see MutableIdentifierGeneratorFactoryInitiator + */ +public final class QuarkusMutableIdentifierGeneratorFactoryInitiator + implements StandardServiceInitiator { + + private final MutableIdentifierGeneratorFactory sfScopedSingleton = new QuarkusMutableIdentifierGeneratorFactory(); + + @Override + public MutableIdentifierGeneratorFactory initiateService(final Map configurationValues, + final ServiceRegistryImplementor registry) { + return sfScopedSingleton; + } + + @Override + public Class getServiceInitiated() { + return MutableIdentifierGeneratorFactory.class; + } + +} diff --git a/extensions/hibernate-search-elasticsearch/deployment/pom.xml b/extensions/hibernate-search-elasticsearch/deployment/pom.xml new file mode 100644 index 0000000000000..55242bc5a5ef5 --- /dev/null +++ b/extensions/hibernate-search-elasticsearch/deployment/pom.xml @@ -0,0 +1,77 @@ + + + + + + quarkus-hibernate-search-elasticsearch-parent + io.quarkus + 999-SNAPSHOT + ../ + + 4.0.0 + + quarkus-hibernate-search-elasticsearch-deployment + Quarkus - Hibernate Search - Elasticsearch - Deployment + + + + io.quarkus + quarkus-core-deployment + + + io.quarkus + quarkus-hibernate-orm-deployment + + + io.quarkus + quarkus-elasticsearch-rest-client-deployment + + + io.quarkus + quarkus-hibernate-search-elasticsearch + + + + + io.quarkus + quarkus-junit5-internal + test + + + + + + + maven-surefire-plugin + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + 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 new file mode 100644 index 0000000000000..9cc067027c2a9 --- /dev/null +++ b/extensions/hibernate-search-elasticsearch/deployment/src/main/java/io/quarkus/hibernate/search/elasticsearch/HibernateSearchClasses.java @@ -0,0 +1,96 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Licensed 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.hibernate.search.elasticsearch; + +import java.util.Arrays; +import java.util.List; + +import org.hibernate.search.backend.elasticsearch.analysis.model.impl.esnative.AbstractCompositeAnalysisDefinition; +import org.hibernate.search.backend.elasticsearch.analysis.model.impl.esnative.AnalysisDefinition; +import org.hibernate.search.backend.elasticsearch.analysis.model.impl.esnative.AnalysisDefinitionJsonAdapterFactory; +import org.hibernate.search.backend.elasticsearch.analysis.model.impl.esnative.AnalyzerDefinition; +import org.hibernate.search.backend.elasticsearch.analysis.model.impl.esnative.AnalyzerDefinitionJsonAdapterFactory; +import org.hibernate.search.backend.elasticsearch.analysis.model.impl.esnative.CharFilterDefinition; +import org.hibernate.search.backend.elasticsearch.analysis.model.impl.esnative.NormalizerDefinition; +import org.hibernate.search.backend.elasticsearch.analysis.model.impl.esnative.NormalizerDefinitionJsonAdapterFactory; +import org.hibernate.search.backend.elasticsearch.analysis.model.impl.esnative.TokenFilterDefinition; +import org.hibernate.search.backend.elasticsearch.analysis.model.impl.esnative.TokenizerDefinition; +import org.hibernate.search.backend.elasticsearch.document.model.impl.esnative.AbstractTypeMapping; +import org.hibernate.search.backend.elasticsearch.document.model.impl.esnative.AbstractTypeMappingJsonAdapterFactory; +import org.hibernate.search.backend.elasticsearch.document.model.impl.esnative.DataType; +import org.hibernate.search.backend.elasticsearch.document.model.impl.esnative.DynamicType; +import org.hibernate.search.backend.elasticsearch.document.model.impl.esnative.ElasticsearchFormatJsonAdapter; +import org.hibernate.search.backend.elasticsearch.document.model.impl.esnative.ElasticsearchRoutingTypeJsonAdapter; +import org.hibernate.search.backend.elasticsearch.document.model.impl.esnative.PropertyMapping; +import org.hibernate.search.backend.elasticsearch.document.model.impl.esnative.PropertyMappingJsonAdapterFactory; +import org.hibernate.search.backend.elasticsearch.document.model.impl.esnative.RootTypeMapping; +import org.hibernate.search.backend.elasticsearch.document.model.impl.esnative.RootTypeMappingJsonAdapterFactory; +import org.hibernate.search.backend.elasticsearch.document.model.impl.esnative.RoutingType; +import org.hibernate.search.backend.elasticsearch.index.settings.impl.esnative.Analysis; +import org.hibernate.search.backend.elasticsearch.index.settings.impl.esnative.IndexSettings; +import org.hibernate.search.mapper.pojo.bridge.declaration.PropertyBridgeMapping; +import org.hibernate.search.mapper.pojo.bridge.declaration.TypeBridgeMapping; +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.KeywordField; +import org.jboss.jandex.DotName; + +class HibernateSearchClasses { + + 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(IndexedEmbedded.class.getName())); + + static final DotName PROPERTY_BRIDGE_DECLARATION_ANNOTATION = DotName + .createSimple(PropertyBridgeMapping.class.getName()); + + static final DotName TYPE_BRIDGE_DECLARATION_ANNOTATION = DotName + .createSimple(TypeBridgeMapping.class.getName()); + + static final List SCHEMA_MAPPING_CLASSES = Arrays.asList( + DotName.createSimple(AbstractTypeMapping.class.getName()), + DotName.createSimple(AbstractTypeMappingJsonAdapterFactory.class.getName()), + DotName.createSimple(DataType.class.getName()), + DotName.createSimple(DynamicType.class.getName()), + DotName.createSimple(ElasticsearchFormatJsonAdapter.class.getName()), + DotName.createSimple(ElasticsearchRoutingTypeJsonAdapter.class.getName()), + DotName.createSimple(PropertyMapping.class.getName()), + DotName.createSimple(PropertyMappingJsonAdapterFactory.class.getName()), + DotName.createSimple(RootTypeMapping.class.getName()), + DotName.createSimple(RootTypeMappingJsonAdapterFactory.class.getName()), + DotName.createSimple(RoutingType.class.getName()), + DotName.createSimple(IndexSettings.class.getName()), + DotName.createSimple(Analysis.class.getName()), + DotName.createSimple(AnalysisDefinition.class.getName()), + DotName.createSimple(AbstractCompositeAnalysisDefinition.class.getName()), + DotName.createSimple(AnalyzerDefinition.class.getName()), + DotName.createSimple(AnalyzerDefinitionJsonAdapterFactory.class.getName()), + DotName.createSimple(NormalizerDefinition.class.getName()), + DotName.createSimple(NormalizerDefinitionJsonAdapterFactory.class.getName()), + DotName.createSimple(TokenizerDefinition.class.getName()), + DotName.createSimple(TokenFilterDefinition.class.getName()), + DotName.createSimple(CharFilterDefinition.class.getName()), + DotName.createSimple(AnalysisDefinitionJsonAdapterFactory.class.getName())); + + static final DotName INDEXED = DotName.createSimple(Indexed.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 new file mode 100644 index 0000000000000..e3d4c386ba1eb --- /dev/null +++ b/extensions/hibernate-search-elasticsearch/deployment/src/main/java/io/quarkus/hibernate/search/elasticsearch/HibernateSearchElasticsearchProcessor.java @@ -0,0 +1,256 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Licensed 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.hibernate.search.elasticsearch; + +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_BRIDGE_DECLARATION_ANNOTATION; +import static io.quarkus.hibernate.search.elasticsearch.HibernateSearchClasses.SCHEMA_MAPPING_CLASSES; +import static io.quarkus.hibernate.search.elasticsearch.HibernateSearchClasses.TYPE_BRIDGE_DECLARATION_ANNOTATION; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map.Entry; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Stream; + +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationTarget; +import org.jboss.jandex.AnnotationTarget.Kind; +import org.jboss.jandex.ArrayType; +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.ClassType; +import org.jboss.jandex.DotName; +import org.jboss.jandex.FieldInfo; +import org.jboss.jandex.IndexView; +import org.jboss.jandex.MethodInfo; +import org.jboss.jandex.ParameterizedType; +import org.jboss.jandex.PrimitiveType; +import org.jboss.jandex.Type; +import org.jboss.jandex.UnresolvedTypeVariable; +import org.jboss.jandex.VoidType; + +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.CombinedIndexBuildItem; +import io.quarkus.deployment.builditem.FeatureBuildItem; +import io.quarkus.deployment.builditem.substrate.ReflectiveClassBuildItem; +import io.quarkus.deployment.builditem.substrate.ReflectiveHierarchyBuildItem; +import io.quarkus.deployment.configuration.ConfigurationError; +import io.quarkus.hibernate.orm.deployment.integration.HibernateOrmIntegrationBuildItem; +import io.quarkus.hibernate.orm.deployment.integration.HibernateOrmIntegrationRuntimeConfiguredBuildItem; +import io.quarkus.hibernate.search.elasticsearch.runtime.HibernateSearchElasticsearchBuildTimeConfig; +import io.quarkus.hibernate.search.elasticsearch.runtime.HibernateSearchElasticsearchBuildTimeConfig.ElasticsearchBackendBuildTimeConfig; +import io.quarkus.hibernate.search.elasticsearch.runtime.HibernateSearchElasticsearchRuntimeConfig; +import io.quarkus.hibernate.search.elasticsearch.runtime.HibernateSearchElasticsearchTemplate; + +class HibernateSearchElasticsearchProcessor { + + private static final String HIBERNATE_SEARCH_ELASTICSEARCH = "Hibernate Search Elasticsearch"; + + HibernateSearchElasticsearchBuildTimeConfig buildTimeConfig; + + @BuildStep + @Record(ExecutionTime.STATIC_INIT) + public void build(HibernateSearchElasticsearchTemplate template, + CombinedIndexBuildItem combinedIndexBuildItem, + BuildProducer reflectiveClass, + BuildProducer reflectiveHierarchy, + BuildProducer integrations, + BuildProducer feature) throws Exception { + feature.produce(new FeatureBuildItem(FeatureBuildItem.HIBERNATE_SEARCH_ELASTICSEARCH)); + + IndexView index = combinedIndexBuildItem.getIndex(); + + if (index.getAnnotations(INDEXED).isEmpty()) { + // we don't have any indexed entity, we can bail out + return; + } + + checkConfig(buildTimeConfig); + + // Register the Hibernate Search integration + integrations.produce(new HibernateOrmIntegrationBuildItem(HIBERNATE_SEARCH_ELASTICSEARCH)); + + // Register the required reflection declarations + registerReflection(index, reflectiveClass, reflectiveHierarchy); + + // Register the Hibernate Search integration listener + template.registerHibernateSearchIntegration(buildTimeConfig); + } + + @BuildStep + @Record(ExecutionTime.RUNTIME_INIT) + void setRuntimeConfig(HibernateSearchElasticsearchTemplate template, + HibernateSearchElasticsearchRuntimeConfig runtimeConfig, + BuildProducer runtimeConfigured) { + template.setRuntimeConfig(runtimeConfig); + + runtimeConfigured.produce(new HibernateOrmIntegrationRuntimeConfiguredBuildItem(HIBERNATE_SEARCH_ELASTICSEARCH)); + } + + private static void checkConfig(HibernateSearchElasticsearchBuildTimeConfig buildTimeConfig) { + if (buildTimeConfig.defaultBackend.isPresent()) { + // we have a default named backend + if (buildTimeConfig.elasticsearch.version.isPresent()) { + throw new ConfigurationError( + "quarkus.hibernate-search.elasticsearch.default-backend cannot be used in conjunction with a default backend configuration."); + } + if (!buildTimeConfig.additionalBackends.containsKey(buildTimeConfig.defaultBackend.get())) { + throw new ConfigurationError( + "The default backend defined does not exist: " + buildTimeConfig.defaultBackend.get()); + } + } else { + // we are in the default backend case + if (!buildTimeConfig.elasticsearch.version.isPresent()) { + throw new ConfigurationError( + "The Elasticsearch version needs to be defined via the quarkus.hibernate-search.elasticsearch.version property."); + } + } + + // we validate that the version is present for all the additional backends + if (!buildTimeConfig.additionalBackends.isEmpty()) { + List additionalBackendsWithNoVersion = new ArrayList<>(); + for (Entry additionalBackendEntry : buildTimeConfig.additionalBackends + .entrySet()) { + if (!additionalBackendEntry.getValue().version.isPresent()) { + additionalBackendsWithNoVersion.add(additionalBackendEntry.getKey()); + } + } + if (!additionalBackendsWithNoVersion.isEmpty()) { + throw new ConfigurationError("The Elasticsearch version property needs to be defined for backends " + + String.join(", ", additionalBackendsWithNoVersion)); + } + } + } + + private void registerReflection(IndexView index, BuildProducer reflectiveClass, + BuildProducer reflectiveHierarchy) { + Set reflectiveClassCollector = new HashSet<>(); + Set reflectiveTypeCollector = new HashSet<>(); + + if (buildTimeConfig.elasticsearch.analysisConfigurer.isPresent()) { + reflectiveClass.produce( + new ReflectiveClassBuildItem(true, false, buildTimeConfig.elasticsearch.analysisConfigurer.get())); + } + + for (DotName fieldAnnotation : FIELD_ANNOTATIONS) { + for (AnnotationInstance fieldAnnotationInstance : index.getAnnotations(fieldAnnotation)) { + AnnotationTarget annotationTarget = fieldAnnotationInstance.target(); + if (annotationTarget.kind() == Kind.FIELD) { + FieldInfo fieldInfo = annotationTarget.asField(); + addReflectiveClass(index, reflectiveClassCollector, reflectiveTypeCollector, fieldInfo.declaringClass()); + addReflectiveType(index, reflectiveTypeCollector, fieldInfo.type()); + } else if (annotationTarget.kind() == Kind.METHOD) { + MethodInfo methodInfo = annotationTarget.asMethod(); + addReflectiveClass(index, reflectiveClassCollector, reflectiveTypeCollector, methodInfo.declaringClass()); + addReflectiveType(index, reflectiveTypeCollector, methodInfo.returnType()); + } + } + } + + Set reflectiveHierarchyCollector = new HashSet<>(); + + for (AnnotationInstance propertyBridgeMappingInstance : index.getAnnotations(PROPERTY_BRIDGE_DECLARATION_ANNOTATION)) { + 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 (AnnotationInstance typeBridgeMappingInstance : index.getAnnotations(TYPE_BRIDGE_DECLARATION_ANNOTATION)) { + for (AnnotationInstance typeBridgeInstance : index.getAnnotations(typeBridgeMappingInstance.name())) { + addReflectiveClass(index, reflectiveClassCollector, reflectiveTypeCollector, + typeBridgeInstance.target().asClass()); + } + } + + String[] reflectiveClasses = Stream + .of(reflectiveClassCollector.stream(), reflectiveTypeCollector.stream(), SCHEMA_MAPPING_CLASSES.stream()) + .flatMap(Function.identity()).map(c -> c.toString()).toArray(String[]::new); + reflectiveClass.produce(new ReflectiveClassBuildItem(true, true, reflectiveClasses)); + + for (Type reflectiveHierarchyType : reflectiveHierarchyCollector) { + reflectiveHierarchy.produce(new ReflectiveHierarchyBuildItem(reflectiveHierarchyType)); + } + } + + private static void addReflectiveClass(IndexView index, Set reflectiveClassCollector, + Set reflectiveTypeCollector, ClassInfo classInfo) { + if (skipClass(classInfo.name(), reflectiveClassCollector)) { + return; + } + + reflectiveClassCollector.add(classInfo.name()); + + for (ClassInfo subclass : index.getAllKnownSubclasses(classInfo.name())) { + reflectiveClassCollector.add(subclass.name()); + } + for (ClassInfo implementor : index.getAllKnownImplementors(classInfo.name())) { + reflectiveClassCollector.add(implementor.name()); + } + + Type superClassType = classInfo.superClassType(); + while (superClassType != null && !superClassType.name().toString().equals("java.lang.Object")) { + if (superClassType instanceof ClassType) { + superClassType = index.getClassByName(superClassType.name()).superClassType(); + } else if (superClassType instanceof ParameterizedType) { + ParameterizedType parameterizedType = superClassType.asParameterizedType(); + for (Type typeArgument : parameterizedType.arguments()) { + addReflectiveType(index, reflectiveTypeCollector, typeArgument); + } + superClassType = parameterizedType.owner(); + } + } + } + + private static void addReflectiveType(IndexView index, 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()); + } else if (type instanceof ArrayType) { + addReflectiveType(index, reflectiveTypeCollector, type.asArrayType().component()); + } else if (type instanceof ParameterizedType) { + ParameterizedType parameterizedType = type.asParameterizedType(); + addReflectiveType(index, reflectiveTypeCollector, parameterizedType.owner()); + for (Type typeArgument : parameterizedType.arguments()) { + addReflectiveType(index, reflectiveTypeCollector, typeArgument); + } + } + } + + private static boolean skipClass(DotName name, Set processedClasses) { + return name.toString().startsWith("java.") || processedClasses.contains(name); + } +} diff --git a/extensions/hibernate-search-elasticsearch/deployment/src/test/java/io/quarkus/hibernate/search/elasticsearch/test/IndexedEntity.java b/extensions/hibernate-search-elasticsearch/deployment/src/test/java/io/quarkus/hibernate/search/elasticsearch/test/IndexedEntity.java new file mode 100644 index 0000000000000..e118ff6d4aaa9 --- /dev/null +++ b/extensions/hibernate-search-elasticsearch/deployment/src/test/java/io/quarkus/hibernate/search/elasticsearch/test/IndexedEntity.java @@ -0,0 +1,13 @@ +package io.quarkus.hibernate.search.elasticsearch.test; + +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.Indexed; + +/** + * This particular entity is not a valid entity as it is not marked with @Entity. + *

+ * It is done this way just for the sake of testing the extension bootstrap. + */ +@Indexed +public class IndexedEntity { + +} diff --git a/extensions/hibernate-search-elasticsearch/deployment/src/test/java/io/quarkus/hibernate/search/elasticsearch/test/NoConfigIndexedEntityTest.java b/extensions/hibernate-search-elasticsearch/deployment/src/test/java/io/quarkus/hibernate/search/elasticsearch/test/NoConfigIndexedEntityTest.java new file mode 100644 index 0000000000000..11c9565f17a1d --- /dev/null +++ b/extensions/hibernate-search-elasticsearch/deployment/src/test/java/io/quarkus/hibernate/search/elasticsearch/test/NoConfigIndexedEntityTest.java @@ -0,0 +1,24 @@ +package io.quarkus.hibernate.search.elasticsearch.test; + +import java.sql.SQLException; + +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.deployment.configuration.ConfigurationError; +import io.quarkus.test.QuarkusUnitTest; + +public class NoConfigIndexedEntityTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class).addClass(IndexedEntity.class)) + .setExpectedException(ConfigurationError.class); + + @Test + public void testNoConfig() throws SQLException { + // an exception should be thrown + } +} diff --git a/extensions/hibernate-search-elasticsearch/deployment/src/test/java/io/quarkus/hibernate/search/elasticsearch/test/NoConfigNoIndexedEntityTest.java b/extensions/hibernate-search-elasticsearch/deployment/src/test/java/io/quarkus/hibernate/search/elasticsearch/test/NoConfigNoIndexedEntityTest.java new file mode 100644 index 0000000000000..9b2b7c0e60267 --- /dev/null +++ b/extensions/hibernate-search-elasticsearch/deployment/src/test/java/io/quarkus/hibernate/search/elasticsearch/test/NoConfigNoIndexedEntityTest.java @@ -0,0 +1,22 @@ +package io.quarkus.hibernate.search.elasticsearch.test; + +import java.sql.SQLException; + +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 NoConfigNoIndexedEntityTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class)); + + @Test + public void testNoConfig() throws SQLException { + // we should be able to start the application, even with no configuration at all nor indexed entities + } +} diff --git a/extensions/hibernate-search-elasticsearch/pom.xml b/extensions/hibernate-search-elasticsearch/pom.xml new file mode 100644 index 0000000000000..bf96b70cb6ba6 --- /dev/null +++ b/extensions/hibernate-search-elasticsearch/pom.xml @@ -0,0 +1,36 @@ + + + + + + quarkus-build-parent + io.quarkus + 999-SNAPSHOT + ../../build-parent/pom.xml + + 4.0.0 + + quarkus-hibernate-search-elasticsearch-parent + Quarkus - Hibernate Search - Elasticsearch + pom + + deployment + runtime + + diff --git a/extensions/hibernate-search-elasticsearch/runtime/pom.xml b/extensions/hibernate-search-elasticsearch/runtime/pom.xml new file mode 100644 index 0000000000000..775cf0a6bfa71 --- /dev/null +++ b/extensions/hibernate-search-elasticsearch/runtime/pom.xml @@ -0,0 +1,79 @@ + + + + + + quarkus-hibernate-search-elasticsearch-parent + io.quarkus + 999-SNAPSHOT + ../ + + 4.0.0 + + quarkus-hibernate-search-elasticsearch + Quarkus - Hibernate Search - Elasticsearch - Runtime + + + + io.quarkus + quarkus-core + + + io.quarkus + quarkus-hibernate-orm + + + io.quarkus + quarkus-elasticsearch-rest-client + + + org.hibernate.search + hibernate-search-backend-elasticsearch + + + org.hibernate.search + hibernate-search-mapper-orm + + + com.oracle.substratevm + svm + + + + + + + io.quarkus + quarkus-bootstrap-maven-plugin + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + diff --git a/extensions/hibernate-search-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/elasticsearch/runtime/HibernateSearchConfigUtil.java b/extensions/hibernate-search-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/elasticsearch/runtime/HibernateSearchConfigUtil.java new file mode 100644 index 0000000000000..6c66cd69841b0 --- /dev/null +++ b/extensions/hibernate-search-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/elasticsearch/runtime/HibernateSearchConfigUtil.java @@ -0,0 +1,84 @@ +package io.quarkus.hibernate.search.elasticsearch.runtime; + +import java.util.Optional; +import java.util.OptionalInt; +import java.util.function.BiConsumer; +import java.util.function.Function; + +import org.hibernate.search.engine.cfg.BackendSettings; +import org.hibernate.search.engine.cfg.EngineSettings; +import org.hibernate.search.mapper.orm.cfg.HibernateOrmMapperSettings; + +public class HibernateSearchConfigUtil { + + public static void addConfig(BiConsumer propertyCollector, String configPath, T value) { + propertyCollector.accept(configKey(configPath), value); + } + + public static void addBackendConfig(BiConsumer propertyCollector, String backendName, String configPath, + T value) { + propertyCollector.accept(backendConfigKey(backendName, configPath), value); + } + + public static void addBackendConfig(BiConsumer propertyCollector, String backendName, String configPath, + Optional value) { + addBackendConfig(propertyCollector, backendName, configPath, value, Optional::isPresent, Optional::get); + } + + public static void addBackendConfig(BiConsumer propertyCollector, String backendName, String configPath, + OptionalInt value) { + addBackendConfig(propertyCollector, backendName, configPath, value, OptionalInt::isPresent, OptionalInt::getAsInt); + } + + public static void addBackendConfig(BiConsumer propertyCollector, String backendName, String configPath, + T value, + Function shouldBeAdded, Function getValue) { + if (shouldBeAdded.apply(value)) { + propertyCollector.accept(backendConfigKey(backendName, configPath), getValue.apply(value)); + } + } + + public static void addBackendDefaultIndexConfig(BiConsumer propertyCollector, String backendName, + String configPath, Optional value) { + addBackendDefaultIndexConfig(propertyCollector, backendName, configPath, value, Optional::isPresent, Optional::get); + } + + public static void addBackendDefaultIndexConfig(BiConsumer propertyCollector, String backendName, + String configPath, OptionalInt value) { + addBackendDefaultIndexConfig(propertyCollector, backendName, configPath, value, OptionalInt::isPresent, + OptionalInt::getAsInt); + } + + public static void addBackendDefaultIndexConfig(BiConsumer propertyCollector, String backendName, + String configPath, T value, + Function shouldBeAdded, Function getValue) { + addBackendConfig(propertyCollector, backendName, BackendSettings.INDEX_DEFAULTS + "." + configPath, value, + shouldBeAdded, getValue); + } + + public static void addBackendIndexConfig(BiConsumer propertyCollector, String backendName, + String indexName, String configPath, Optional value) { + addBackendIndexConfig(propertyCollector, backendName, indexName, configPath, value, Optional::isPresent, Optional::get); + } + + public static void addBackendIndexConfig(BiConsumer propertyCollector, String backendName, + String indexName, String configPath, OptionalInt value) { + addBackendIndexConfig(propertyCollector, backendName, indexName, configPath, value, OptionalInt::isPresent, + OptionalInt::getAsInt); + } + + public static void addBackendIndexConfig(BiConsumer propertyCollector, String backendName, + String indexName, String configPath, T value, + Function shouldBeAdded, Function getValue) { + addBackendConfig(propertyCollector, backendName, BackendSettings.INDEXES + "." + indexName + "." + configPath, value, + shouldBeAdded, getValue); + } + + private static String configKey(String configPath) { + return HibernateOrmMapperSettings.PREFIX + configPath; + } + + private static String backendConfigKey(String backendName, String configPath) { + return configKey(EngineSettings.BACKENDS + "." + backendName + "." + configPath); + } +} diff --git a/extensions/hibernate-search-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/elasticsearch/runtime/HibernateSearchElasticsearchBuildTimeConfig.java b/extensions/hibernate-search-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/elasticsearch/runtime/HibernateSearchElasticsearchBuildTimeConfig.java new file mode 100644 index 0000000000000..1738e4534f74e --- /dev/null +++ b/extensions/hibernate-search-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/elasticsearch/runtime/HibernateSearchElasticsearchBuildTimeConfig.java @@ -0,0 +1,72 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Licensed 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.hibernate.search.elasticsearch.runtime; + +import java.util.Map; +import java.util.Optional; + +import org.hibernate.search.backend.elasticsearch.cfg.ElasticsearchVersion; + +import io.quarkus.runtime.annotations.ConfigGroup; +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; + +@ConfigRoot(name = "hibernate-search", phase = ConfigPhase.BUILD_AND_RUN_TIME_FIXED) +public class HibernateSearchElasticsearchBuildTimeConfig { + + /** + * Configuration of the default backend. + */ + public ElasticsearchBackendBuildTimeConfig elasticsearch; + + /** + * If not using the default backend configuration, the name of the default backend that is part of the + * {@link #additionalBackends}. + */ + public Optional defaultBackend; + + /** + * Configuration of optional additional backends. + */ + @ConfigItem(name = "elasticsearch.backends") + public Map additionalBackends; + + @ConfigGroup + public static class ElasticsearchBackendBuildTimeConfig { + /** + * The version of Elasticsearch used in the cluster. + *

+ * As the schema is generated without a connection to the server, this item is mandatory. + *

+ * It doesn't have to be the exact version (it can be 7 or 7.1 for instance) but it has to be sufficiently precise to + * choose a model dialect (the one used to generate the schema) compatible with the protocol dialect (the one used to + * communicate with Elasticsearch). + *

+ * There's no rule of thumb here as it depends on the schema incompatibilities introduced by Elasticsearch versions. In + * any case, if there is a problem, you will have an error when Hibernate Search tries to connect to the cluster. + */ + @ConfigItem + public Optional version; + + /** + * The class or the name of the bean used to configure full text analysis (e.g. analyzers, normalizers). + */ + @ConfigItem + public Optional> analysisConfigurer; + } +} 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 new file mode 100644 index 0000000000000..2aa3c9af78232 --- /dev/null +++ b/extensions/hibernate-search-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/elasticsearch/runtime/HibernateSearchElasticsearchRuntimeConfig.java @@ -0,0 +1,164 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Licensed 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.hibernate.search.elasticsearch.runtime; + +import java.time.Duration; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.OptionalInt; + +import org.hibernate.search.backend.elasticsearch.cfg.ElasticsearchIndexLifecycleStrategyName; +import org.hibernate.search.backend.elasticsearch.cfg.ElasticsearchIndexStatus; + +import io.quarkus.runtime.annotations.ConfigGroup; +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; + +@ConfigRoot(name = "hibernate-search", phase = ConfigPhase.RUN_TIME) +public class HibernateSearchElasticsearchRuntimeConfig { + + /** + * Configuration of the default backend. + */ + ElasticsearchBackendRuntimeConfig elasticsearch; + + /** + * Configuration of optional additional backends. + */ + @ConfigItem(name = "elasticsearch.backends") + Map additionalBackends; + + @ConfigGroup + public static class ElasticsearchBackendRuntimeConfig { + /** + * The list of hosts of the Elasticsearch servers. + */ + @ConfigItem + List hosts; + + /** + * The username used for authentication. + */ + @ConfigItem + Optional username; + + /** + * The password used for authentication. + */ + @ConfigItem + Optional password; + + /** + * The connection timeout. + */ + @ConfigItem + Optional connectionTimeout; + + /** + * The maximum number of connections to all the Elasticsearch servers. + */ + @ConfigItem + OptionalInt maxConnections; + + /** + * The maximum number of connections per Elasticsearch server. + */ + @ConfigItem + OptionalInt maxConnectionsPerRoute; + + /** + * Configuration for the automatic discovery of new Elasticsearch nodes. + */ + @ConfigItem + DiscoveryConfig discovery; + + /** + * The default configuration for the Elasticsearch indexes. + */ + @ConfigItem + ElasticsearchIndexConfig indexDefaults; + + /** + * Per-index specific configuration. + */ + @ConfigItem + Map indexes; + } + + @ConfigGroup + public static class ElasticsearchIndexConfig { + /** + * Configuration for the lifecyle of the indexes. + */ + @ConfigItem + LifecycleConfig lifecycle; + + /** + * Defines if the indexes should be refreshed after writes. + */ + @ConfigItem + Optional refreshAfterWrite; + } + + @ConfigGroup + public static class DiscoveryConfig { + + /** + * Defines if automatic discovery is enabled. + */ + @ConfigItem + Optional enabled; + + /** + * Refresh interval of the node list. + */ + Optional refreshInterval; + + /** + * The scheme that should be used for the new nodes discovered. + */ + Optional defaultScheme; + } + + @ConfigGroup + public static class LifecycleConfig { + + /** + * The strategy used for index lifecycle. + *

+ * Must be one of: none, validate, update, create, drop-and-create or drop-and-create-and-drop. + */ + @ConfigItem + Optional strategy; + + /** + * The minimal cluster status required. + *

+ * Must be one of: green, yellow, red. + */ + @ConfigItem + Optional requiredStatus; + + /** + * How long we should wait for the status before failing the bootstrap. + */ + @ConfigItem + Optional requiredStatusWaitTimeout; + } +} diff --git a/extensions/hibernate-search-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/elasticsearch/runtime/HibernateSearchElasticsearchTemplate.java b/extensions/hibernate-search-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/elasticsearch/runtime/HibernateSearchElasticsearchTemplate.java new file mode 100644 index 0000000000000..a68cdfc358da5 --- /dev/null +++ b/extensions/hibernate-search-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/elasticsearch/runtime/HibernateSearchElasticsearchTemplate.java @@ -0,0 +1,176 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Licensed 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.hibernate.search.elasticsearch.runtime; + +import static io.quarkus.hibernate.search.elasticsearch.runtime.HibernateSearchConfigUtil.addBackendConfig; +import static io.quarkus.hibernate.search.elasticsearch.runtime.HibernateSearchConfigUtil.addBackendDefaultIndexConfig; +import static io.quarkus.hibernate.search.elasticsearch.runtime.HibernateSearchConfigUtil.addBackendIndexConfig; +import static io.quarkus.hibernate.search.elasticsearch.runtime.HibernateSearchConfigUtil.addConfig; + +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; +import org.hibernate.search.backend.elasticsearch.cfg.ElasticsearchBackendSettings; +import org.hibernate.search.backend.elasticsearch.cfg.ElasticsearchIndexSettings; +import org.hibernate.search.engine.cfg.BackendSettings; +import org.hibernate.search.engine.cfg.EngineSettings; +import org.hibernate.search.mapper.orm.bootstrap.spi.HibernateOrmIntegrationBooter; +import org.hibernate.search.mapper.orm.cfg.spi.HibernateOrmMapperSpiSettings; +import org.hibernate.search.mapper.orm.cfg.spi.HibernateOrmPropertyHandleFactoryName; + +import io.quarkus.hibernate.orm.runtime.integration.HibernateOrmIntegrationListener; +import io.quarkus.hibernate.orm.runtime.integration.HibernateOrmIntegrations; +import io.quarkus.hibernate.search.elasticsearch.runtime.HibernateSearchElasticsearchBuildTimeConfig.ElasticsearchBackendBuildTimeConfig; +import io.quarkus.hibernate.search.elasticsearch.runtime.HibernateSearchElasticsearchRuntimeConfig.ElasticsearchBackendRuntimeConfig; +import io.quarkus.hibernate.search.elasticsearch.runtime.HibernateSearchElasticsearchRuntimeConfig.ElasticsearchIndexConfig; +import io.quarkus.runtime.annotations.Template; + +@Template +public class HibernateSearchElasticsearchTemplate { + + public static final String DEFAULT_BACKEND = "_quarkus_"; + + private static HibernateSearchElasticsearchRuntimeConfig runtimeConfig; + + public void registerHibernateSearchIntegration(HibernateSearchElasticsearchBuildTimeConfig buildTimeConfig) { + HibernateOrmIntegrations.registerListener(new HibernateSearchIntegrationListener(buildTimeConfig)); + } + + public void setRuntimeConfig(HibernateSearchElasticsearchRuntimeConfig runtimeConfig) { + HibernateSearchElasticsearchTemplate.runtimeConfig = runtimeConfig; + } + + private static final class HibernateSearchIntegrationListener implements HibernateOrmIntegrationListener { + + private HibernateSearchElasticsearchBuildTimeConfig buildTimeConfig; + + private HibernateSearchIntegrationListener(HibernateSearchElasticsearchBuildTimeConfig buildTimeConfig) { + this.buildTimeConfig = buildTimeConfig; + } + + @Override + public void contributeBootProperties(BiConsumer propertyCollector) { + addConfig(propertyCollector, HibernateOrmMapperSpiSettings.Radicals.PROPERTY_HANDLE_FACTORY, + HibernateOrmPropertyHandleFactoryName.JAVA_LANG_REFLECT); + + if (buildTimeConfig.defaultBackend.isPresent()) { + // we have a named default backend + addConfig(propertyCollector, EngineSettings.DEFAULT_BACKEND, + buildTimeConfig.defaultBackend.get()); + } else if (buildTimeConfig.elasticsearch.version.isPresent()) { + // we use the default backend configuration + addConfig(propertyCollector, EngineSettings.DEFAULT_BACKEND, + HibernateSearchElasticsearchTemplate.DEFAULT_BACKEND); + } + + contributeBackendBuildTimeProperties(propertyCollector, HibernateSearchElasticsearchTemplate.DEFAULT_BACKEND, + buildTimeConfig.elasticsearch); + + for (Entry backendEntry : buildTimeConfig.additionalBackends + .entrySet()) { + contributeBackendBuildTimeProperties(propertyCollector, backendEntry.getKey(), backendEntry.getValue()); + } + } + + @Override + public void onMetadataInitialized(Metadata metadata, BootstrapContext bootstrapContext, + BiConsumer propertyCollector) { + HibernateOrmIntegrationBooter booter = HibernateOrmIntegrationBooter.create(metadata, bootstrapContext); + booter.preBoot(propertyCollector); + } + + @Override + public void contributeRuntimeProperties(BiConsumer propertyCollector) { + contributeBackendRuntimeProperties(propertyCollector, DEFAULT_BACKEND, runtimeConfig.elasticsearch); + + for (Entry backendEntry : runtimeConfig.additionalBackends.entrySet()) { + contributeBackendRuntimeProperties(propertyCollector, backendEntry.getKey(), backendEntry.getValue()); + } + } + + private void contributeBackendBuildTimeProperties(BiConsumer propertyCollector, String backendName, + ElasticsearchBackendBuildTimeConfig elasticsearchBackendConfig) { + addBackendConfig(propertyCollector, backendName, BackendSettings.TYPE, + ElasticsearchBackendSettings.TYPE_NAME); + addBackendConfig(propertyCollector, backendName, ElasticsearchBackendSettings.VERSION, + elasticsearchBackendConfig.version); + addBackendConfig(propertyCollector, backendName, + ElasticsearchBackendSettings.ANALYSIS_CONFIGURER, + elasticsearchBackendConfig.analysisConfigurer, + Optional::isPresent, c -> c.get().getName()); + } + + 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()); + addBackendConfig(propertyCollector, backendName, ElasticsearchBackendSettings.USERNAME, + elasticsearchBackendConfig.username); + addBackendConfig(propertyCollector, backendName, ElasticsearchBackendSettings.PASSWORD, + elasticsearchBackendConfig.password); + addBackendConfig(propertyCollector, backendName, ElasticsearchBackendSettings.CONNECTION_TIMEOUT, + elasticsearchBackendConfig.connectionTimeout, + Optional::isPresent, d -> d.get().toMillis()); + addBackendConfig(propertyCollector, backendName, ElasticsearchBackendSettings.MAX_CONNECTIONS, + elasticsearchBackendConfig.maxConnections); + addBackendConfig(propertyCollector, backendName, ElasticsearchBackendSettings.MAX_CONNECTIONS_PER_ROUTE, + elasticsearchBackendConfig.maxConnectionsPerRoute); + + addBackendConfig(propertyCollector, backendName, ElasticsearchBackendSettings.DISCOVERY_ENABLED, + elasticsearchBackendConfig.discovery.enabled); + addBackendConfig(propertyCollector, backendName, ElasticsearchBackendSettings.DISCOVERY_REFRESH_INTERVAL, + elasticsearchBackendConfig.discovery.refreshInterval, + Optional::isPresent, d -> d.get().getSeconds()); + addBackendConfig(propertyCollector, backendName, ElasticsearchBackendSettings.DISCOVERY_SCHEME, + elasticsearchBackendConfig.discovery.defaultScheme); + + addBackendDefaultIndexConfig(propertyCollector, backendName, ElasticsearchIndexSettings.LIFECYCLE_STRATEGY, + elasticsearchBackendConfig.indexDefaults.lifecycle.strategy); + addBackendDefaultIndexConfig(propertyCollector, backendName, + ElasticsearchIndexSettings.LIFECYCLE_MINIMAL_REQUIRED_STATUS, + elasticsearchBackendConfig.indexDefaults.lifecycle.requiredStatus); + addBackendDefaultIndexConfig(propertyCollector, backendName, + ElasticsearchIndexSettings.LIFECYCLE_MINIMAL_REQUIRED_STATUS_WAIT_TIMEOUT, + elasticsearchBackendConfig.indexDefaults.lifecycle.requiredStatusWaitTimeout, Optional::isPresent, + d -> d.get().toMillis()); + addBackendDefaultIndexConfig(propertyCollector, backendName, ElasticsearchIndexSettings.REFRESH_AFTER_WRITE, + runtimeConfig.elasticsearch.indexDefaults.refreshAfterWrite); + + for (Entry indexConfigEntry : runtimeConfig.elasticsearch.indexes.entrySet()) { + String indexName = indexConfigEntry.getKey(); + ElasticsearchIndexConfig indexConfig = indexConfigEntry.getValue(); + + addBackendIndexConfig(propertyCollector, backendName, indexName, ElasticsearchIndexSettings.LIFECYCLE_STRATEGY, + indexConfig.lifecycle.strategy); + addBackendIndexConfig(propertyCollector, backendName, indexName, + ElasticsearchIndexSettings.LIFECYCLE_MINIMAL_REQUIRED_STATUS, + indexConfig.lifecycle.requiredStatus); + addBackendIndexConfig(propertyCollector, backendName, indexName, + ElasticsearchIndexSettings.LIFECYCLE_MINIMAL_REQUIRED_STATUS_WAIT_TIMEOUT, + indexConfig.lifecycle.requiredStatusWaitTimeout, Optional::isPresent, + d -> d.get().toMillis()); + addBackendIndexConfig(propertyCollector, backendName, indexName, ElasticsearchIndexSettings.REFRESH_AFTER_WRITE, + indexConfig.refreshAfterWrite); + } + } + } +} diff --git a/extensions/hibernate-search-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/elasticsearch/runtime/graal/Substitute_HibernateOrmIntegrationBooterImpl.java b/extensions/hibernate-search-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/elasticsearch/runtime/graal/Substitute_HibernateOrmIntegrationBooterImpl.java new file mode 100644 index 0000000000000..32334b271c9a7 --- /dev/null +++ b/extensions/hibernate-search-elasticsearch/runtime/src/main/java/io/quarkus/hibernate/search/elasticsearch/runtime/graal/Substitute_HibernateOrmIntegrationBooterImpl.java @@ -0,0 +1,21 @@ +package io.quarkus.hibernate.search.elasticsearch.runtime.graal; + +import com.oracle.svm.core.annotate.Substitute; +import com.oracle.svm.core.annotate.TargetClass; + +/** + * Force two phase-boot so that bootstrap code can be DCEd. + */ +@TargetClass(className = "org.hibernate.search.mapper.orm.bootstrap.impl.HibernateOrmIntegrationBooterImpl") +final class Substitute_HibernateOrmIntegrationBooterImpl { + + @Substitute + private HibernateOrmIntegrationPartialBuildState doBootFirstPhase() { + throw new IllegalStateException("Partial build state should have been generated during the static init phase."); + } + + @TargetClass(className = "org.hibernate.search.mapper.orm.bootstrap.impl.HibernateOrmIntegrationBooterImpl", innerClass = "HibernateOrmIntegrationPartialBuildState") + final static class HibernateOrmIntegrationPartialBuildState { + + } +} diff --git a/extensions/hibernate-validator/deployment/pom.xml b/extensions/hibernate-validator/deployment/pom.xml index 54a5249a0743f..6464f66c71644 100644 --- a/extensions/hibernate-validator/deployment/pom.xml +++ b/extensions/hibernate-validator/deployment/pom.xml @@ -26,21 +26,21 @@ 4.0.0 - quarkus-hibernate-validator + quarkus-hibernate-validator-deployment Quarkus - Hibernate Validator - Deployment io.quarkus - quarkus-core + quarkus-core-deployment io.quarkus - quarkus-arc + quarkus-arc-deployment io.quarkus - quarkus-hibernate-validator-runtime + quarkus-hibernate-validator diff --git a/extensions/hibernate-validator/runtime/pom.xml b/extensions/hibernate-validator/runtime/pom.xml index 714802f3b73f5..dab961461db23 100644 --- a/extensions/hibernate-validator/runtime/pom.xml +++ b/extensions/hibernate-validator/runtime/pom.xml @@ -26,17 +26,17 @@ 4.0.0 - quarkus-hibernate-validator-runtime + quarkus-hibernate-validator Quarkus - Hibernate Validator - Runtime io.quarkus - quarkus-core-runtime + quarkus-core io.quarkus - quarkus-arc-runtime + quarkus-arc org.hibernate.validator @@ -82,7 +82,8 @@ - maven-dependency-plugin + io.quarkus + quarkus-bootstrap-maven-plugin maven-compiler-plugin diff --git a/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/jaxrs/JaxrsEndPointValidationInterceptor.java b/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/jaxrs/JaxrsEndPointValidationInterceptor.java index 6172b1e67ba7a..ded4aafd5304f 100644 --- a/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/jaxrs/JaxrsEndPointValidationInterceptor.java +++ b/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/jaxrs/JaxrsEndPointValidationInterceptor.java @@ -2,6 +2,7 @@ import java.lang.reflect.Method; import java.util.Arrays; +import java.util.Collections; import java.util.List; import javax.annotation.Priority; @@ -38,6 +39,12 @@ public void validateConstructorInvocation(InvocationContext ctx) throws Exceptio } private List getAccept(Method method) { - return Arrays.asList(MediaTypeHelper.getProduces(method.getDeclaringClass(), method)); + MediaType[] producedMediaTypes = MediaTypeHelper.getProduces(method.getDeclaringClass(), method); + + if (producedMediaTypes == null) { + return Collections.emptyList(); + } + + return Arrays.asList(producedMediaTypes); } } diff --git a/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/jaxrs/ResteasyViolationExceptionMapper.java b/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/jaxrs/ResteasyViolationExceptionMapper.java new file mode 100644 index 0000000000000..c6e4383302cc5 --- /dev/null +++ b/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/jaxrs/ResteasyViolationExceptionMapper.java @@ -0,0 +1,105 @@ +package io.quarkus.hibernate.validator.runtime.jaxrs; + +import java.util.Iterator; +import java.util.List; + +import javax.validation.ConstraintDeclarationException; +import javax.validation.ConstraintDefinitionException; +import javax.validation.GroupDefinitionException; +import javax.validation.ValidationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.ResponseBuilder; +import javax.ws.rs.core.Response.Status; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; + +import org.jboss.resteasy.api.validation.ResteasyViolationException; +import org.jboss.resteasy.api.validation.Validation; +import org.jboss.resteasy.api.validation.ViolationReport; + +@Provider +public class ResteasyViolationExceptionMapper implements ExceptionMapper { + public Response toResponse(ValidationException exception) { + if (exception instanceof ConstraintDefinitionException) { + return buildResponse(unwrapException(exception), MediaType.TEXT_PLAIN, Status.INTERNAL_SERVER_ERROR); + } + if (exception instanceof ConstraintDeclarationException) { + return buildResponse(unwrapException(exception), MediaType.TEXT_PLAIN, Status.INTERNAL_SERVER_ERROR); + } + if (exception instanceof GroupDefinitionException) { + return buildResponse(unwrapException(exception), MediaType.TEXT_PLAIN, Status.INTERNAL_SERVER_ERROR); + } + if (exception instanceof ResteasyViolationException) { + ResteasyViolationException resteasyViolationException = ResteasyViolationException.class.cast(exception); + Exception e = resteasyViolationException.getException(); + if (e != null) { + return buildResponse(unwrapException(e), MediaType.TEXT_PLAIN, Status.INTERNAL_SERVER_ERROR); + } else if (resteasyViolationException.getReturnValueViolations().size() == 0) { + return buildViolationReportResponse(resteasyViolationException, Status.BAD_REQUEST); + } else { + return buildViolationReportResponse(resteasyViolationException, Status.INTERNAL_SERVER_ERROR); + } + } + return buildResponse(unwrapException(exception), MediaType.TEXT_PLAIN, Status.INTERNAL_SERVER_ERROR); + } + + protected Response buildResponse(Object entity, String mediaType, Status status) { + ResponseBuilder builder = Response.status(status).entity(entity); + builder.type(MediaType.TEXT_PLAIN); + builder.header(Validation.VALIDATION_HEADER, "true"); + return builder.build(); + } + + protected Response buildViolationReportResponse(ResteasyViolationException exception, Status status) { + ResponseBuilder builder = Response.status(status); + builder.header(Validation.VALIDATION_HEADER, "true"); + + // Check standard media types. + MediaType mediaType = getAcceptMediaType(exception.getAccept()); + if (mediaType != null) { + builder.type(mediaType); + builder.entity(new ViolationReport(exception)); + return builder.build(); + } + + // Default media type. + builder.type(MediaType.TEXT_PLAIN); + builder.entity(exception.toString()); + return builder.build(); + } + + protected String unwrapException(Throwable t) { + StringBuffer sb = new StringBuffer(); + doUnwrapException(sb, t); + return sb.toString(); + } + + private void doUnwrapException(StringBuffer sb, Throwable t) { + if (t == null) { + return; + } + sb.append(t.toString()); + if (t.getCause() != null && t != t.getCause()) { + sb.append('['); + doUnwrapException(sb, t.getCause()); + sb.append(']'); + } + } + + private MediaType getAcceptMediaType(List accept) { + Iterator it = accept.iterator(); + while (it.hasNext()) { + MediaType mt = it.next(); + if (MediaType.APPLICATION_XML_TYPE.getType().equals(mt.getType()) + && MediaType.APPLICATION_XML_TYPE.getSubtype().equals(mt.getSubtype())) { + return MediaType.APPLICATION_XML_TYPE; + } + if (MediaType.APPLICATION_JSON_TYPE.getType().equals(mt.getType()) + && MediaType.APPLICATION_JSON_TYPE.getSubtype().equals(mt.getSubtype())) { + return MediaType.APPLICATION_JSON_TYPE; + } + } + return null; + } +} diff --git a/extensions/hibernate-validator/runtime/src/main/resources/META-INF/services/javax.ws.rs.ext.Providers b/extensions/hibernate-validator/runtime/src/main/resources/META-INF/services/javax.ws.rs.ext.Providers index 2bebf3d074a34..c88efb99cf024 100644 --- a/extensions/hibernate-validator/runtime/src/main/resources/META-INF/services/javax.ws.rs.ext.Providers +++ b/extensions/hibernate-validator/runtime/src/main/resources/META-INF/services/javax.ws.rs.ext.Providers @@ -1 +1 @@ -org.jboss.resteasy.api.validation.ResteasyViolationExceptionMapper \ No newline at end of file +io.quarkus.hibernate.validator.runtime.jaxrs.ResteasyViolationExceptionMapper diff --git a/extensions/infinispan-client/deployment/pom.xml b/extensions/infinispan-client/deployment/pom.xml index 8d7dd30ab0c6a..5aa5dad05b820 100644 --- a/extensions/infinispan-client/deployment/pom.xml +++ b/extensions/infinispan-client/deployment/pom.xml @@ -25,29 +25,29 @@ 4.0.0 - quarkus-infinispan-client + quarkus-infinispan-client-deployment Quarkus - Infinispan - Client - Deployment io.quarkus - quarkus-core + quarkus-core-deployment io.quarkus - quarkus-arc + quarkus-arc-deployment io.quarkus - quarkus-infinispan-client-runtime + quarkus-infinispan-client io.quarkus - quarkus-caffeine + quarkus-caffeine-deployment io.quarkus - quarkus-netty + quarkus-netty-deployment diff --git a/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/InfinispanClientProcessor.java b/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/InfinispanClientProcessor.java index ccd913d119cd4..af02b4a5eeff2 100644 --- a/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/InfinispanClientProcessor.java +++ b/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/InfinispanClientProcessor.java @@ -27,10 +27,10 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; -import java.util.Optional; import java.util.Properties; import java.util.Set; +import org.infinispan.client.hotrod.annotation.ClientListener; import org.infinispan.client.hotrod.configuration.NearCacheMode; import org.infinispan.client.hotrod.exceptions.HotRodClientException; import org.infinispan.client.hotrod.impl.ConfigurationProperties; @@ -38,6 +38,11 @@ import org.infinispan.client.hotrod.logging.LogFactory; import org.infinispan.client.hotrod.marshall.ProtoStreamMarshaller; import org.infinispan.commons.util.Util; +import org.infinispan.protostream.BaseMarshaller; +import org.infinispan.protostream.EnumMarshaller; +import org.infinispan.protostream.FileDescriptorSource; +import org.infinispan.protostream.MessageMarshaller; +import org.infinispan.protostream.RawProtobufMarshaller; import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationTarget; import org.jboss.jandex.DotName; @@ -58,8 +63,9 @@ import io.quarkus.deployment.builditem.HotDeploymentConfigFileBuildItem; import io.quarkus.deployment.builditem.SystemPropertyBuildItem; import io.quarkus.deployment.builditem.substrate.ReflectiveClassBuildItem; -import io.quarkus.infinispan.client.runtime.InfinispanClientConfiguration; +import io.quarkus.infinispan.client.runtime.InfinispanClientBuildTimeConfig; import io.quarkus.infinispan.client.runtime.InfinispanClientProducer; +import io.quarkus.infinispan.client.runtime.InfinispanClientRuntimeConfig; import io.quarkus.infinispan.client.runtime.InfinispanTemplate; class InfinispanClientProcessor { @@ -69,8 +75,13 @@ class InfinispanClientProcessor { private static final String HOTROD_CLIENT_PROPERTIES = META_INF + File.separator + "/hotrod-client.properties"; private static final String PROTO_EXTENSION = ".proto"; + /** + * The Infinispan client build time configuration. + */ + InfinispanClientBuildTimeConfig infinispanClient; + @BuildStep - PropertiesBuildItem setup(ApplicationArchivesBuildItem applicationArchivesBuildItem, + InfinispanPropertiesBuildItem setup(ApplicationArchivesBuildItem applicationArchivesBuildItem, BuildProducer reflectiveClass, BuildProducer hotDeployment, BuildProducer systemProperties, @@ -79,7 +90,7 @@ PropertiesBuildItem setup(ApplicationArchivesBuildItem applicationArchivesBuildI ApplicationIndexBuildItem applicationIndexBuildItem) throws ClassNotFoundException, IOException { feature.produce(new FeatureBuildItem(FeatureBuildItem.INFINISPAN_CLIENT)); - additionalBeans.produce(new AdditionalBeanBuildItem(InfinispanClientProducer.class)); + additionalBeans.produce(AdditionalBeanBuildItem.unremovableOf(InfinispanClientProducer.class)); systemProperties.produce(new SystemPropertyBuildItem("io.netty.noUnsafe", "true")); hotDeployment.produce(new HotDeploymentConfigFileBuildItem(HOTROD_CLIENT_PROPERTIES)); @@ -144,7 +155,7 @@ PropertiesBuildItem setup(ApplicationArchivesBuildItem applicationArchivesBuildI // Add any user project listeners to allow reflection in native code Index index = applicationIndexBuildItem.getIndex(); List listenerInstances = index.getAnnotations( - DotName.createSimple("org.infinispan.client.hotrod.annotation.ClientListener")); + DotName.createSimple(ClientListener.class.getName())); for (AnnotationInstance instance : listenerInstances) { AnnotationTarget target = instance.target(); if (target.kind() == AnnotationTarget.Kind.CLASS) { @@ -164,7 +175,7 @@ PropertiesBuildItem setup(ApplicationArchivesBuildItem applicationArchivesBuildI reflectiveClass.produce(new ReflectiveClassBuildItem(false, false, "org.infinispan.client.hotrod.impl.consistenthash.SegmentConsistentHash")); - return new PropertiesBuildItem(properties); + return new InfinispanPropertiesBuildItem(properties); } private Properties loadFromStream(InputStream stream) { @@ -177,26 +188,16 @@ private Properties loadFromStream(InputStream stream) { return properties; } - /** - * The Infinispan client configuration, if set. - */ - InfinispanClientConfiguration infinispanClient; - @BuildStep @Record(ExecutionTime.STATIC_INIT) - BeanContainerListenerBuildItem build(InfinispanTemplate template, PropertiesBuildItem builderBuildItem) { + BeanContainerListenerBuildItem build(InfinispanTemplate template, InfinispanPropertiesBuildItem builderBuildItem) { Properties properties = builderBuildItem.getProperties(); - InfinispanClientConfiguration conf = infinispanClient; - final Optional serverList = conf.serverList; + InfinispanClientBuildTimeConfig conf = infinispanClient; if (log.isDebugEnabled()) { log.debugf("Applying micro profile configuration: %s", conf); } - if (serverList.isPresent()) { - // Retain the hotrod-client.properties definition if clashes - properties.putIfAbsent(ConfigurationProperties.SERVER_LIST, serverList.get()); - } int maxEntries = conf.nearCacheMaxEntries; - // Only write the entries if is a valid number and it isn't already configured + // Only write the entries if it is a valid number and it isn't already configured if (maxEntries > 0 && !properties.containsKey(ConfigurationProperties.NEAR_CACHE_MODE)) { // This is already empty so no need for putIfAbsent properties.put(ConfigurationProperties.NEAR_CACHE_MODE, NearCacheMode.INVALIDATED.toString()); @@ -206,10 +207,20 @@ BeanContainerListenerBuildItem build(InfinispanTemplate template, PropertiesBuil return new BeanContainerListenerBuildItem(template.configureInfinispan(properties)); } + @Record(ExecutionTime.RUNTIME_INIT) + @BuildStep + void configureRuntimeProperties(InfinispanTemplate template, + InfinispanClientRuntimeConfig infinispanClientRuntimeConfig) { + template.configureRuntimeProperties(infinispanClientRuntimeConfig); + } + private static final Set UNREMOVABLE_BEANS = Collections.unmodifiableSet( new HashSet<>(Arrays.asList( - DotName.createSimple("org.infinispan.protostream.MessageMarshaller"), - DotName.createSimple("org.infinispan.protostream.FileDescriptorSource")))); + DotName.createSimple(BaseMarshaller.class.getName()), + DotName.createSimple(EnumMarshaller.class.getName()), + DotName.createSimple(MessageMarshaller.class.getName()), + DotName.createSimple(RawProtobufMarshaller.class.getName()), + DotName.createSimple(FileDescriptorSource.class.getName())))); @BuildStep UnremovableBeanBuildItem ensureBeanLookupAvailable() { diff --git a/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/PropertiesBuildItem.java b/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/InfinispanPropertiesBuildItem.java similarity index 81% rename from extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/PropertiesBuildItem.java rename to extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/InfinispanPropertiesBuildItem.java index 615f96ab7acb6..9a224fbfd156d 100644 --- a/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/PropertiesBuildItem.java +++ b/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/InfinispanPropertiesBuildItem.java @@ -18,13 +18,13 @@ import java.util.Properties; -import org.jboss.builder.item.SimpleBuildItem; +import io.quarkus.builder.item.SimpleBuildItem; -public final class PropertiesBuildItem extends SimpleBuildItem { +public final class InfinispanPropertiesBuildItem extends SimpleBuildItem { private final Properties properties; - public PropertiesBuildItem(Properties properties) { + public InfinispanPropertiesBuildItem(Properties properties) { this.properties = properties; } diff --git a/extensions/infinispan-client/runtime/pom.xml b/extensions/infinispan-client/runtime/pom.xml index e67ef30babaf6..7143ef26fbbe7 100644 --- a/extensions/infinispan-client/runtime/pom.xml +++ b/extensions/infinispan-client/runtime/pom.xml @@ -25,25 +25,25 @@ 4.0.0 - quarkus-infinispan-client-runtime + quarkus-infinispan-client Quarkus - Infinispan - Client - Runtime io.quarkus - quarkus-core-runtime + quarkus-core io.quarkus - quarkus-arc-runtime + quarkus-arc io.quarkus - quarkus-caffeine-runtime + quarkus-caffeine io.quarkus - quarkus-netty-runtime + quarkus-netty org.infinispan @@ -72,10 +72,10 @@ - - maven-dependency-plugin + io.quarkus + quarkus-bootstrap-maven-plugin maven-compiler-plugin diff --git a/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanClientBuildTimeConfig.java b/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanClientBuildTimeConfig.java new file mode 100644 index 0000000000000..8143561325528 --- /dev/null +++ b/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanClientBuildTimeConfig.java @@ -0,0 +1,25 @@ +package io.quarkus.infinispan.client.runtime; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; + +/** + * @author William Burns + */ +@ConfigRoot(name = "infinispan-client", phase = ConfigPhase.BUILD_AND_RUN_TIME_FIXED) +public class InfinispanClientBuildTimeConfig { + + /** + * Sets the bounded entry count for near cache. If this value is 0 or less near cache is disabled. + */ + @ConfigItem(defaultValue = "0") + public int nearCacheMaxEntries; + + @Override + public String toString() { + return "InfinispanClientBuildTimeConfig{" + + "nearCacheMaxEntries=" + nearCacheMaxEntries + + '}'; + } +} diff --git a/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanClientProducer.java b/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanClientProducer.java index c88a20589aec9..ff14415948401 100644 --- a/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanClientProducer.java +++ b/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanClientProducer.java @@ -4,6 +4,7 @@ import java.io.InputStream; import java.lang.annotation.Annotation; import java.util.Map; +import java.util.Optional; import java.util.Properties; import java.util.Scanner; import java.util.Set; @@ -29,6 +30,7 @@ import org.infinispan.commons.marshall.Marshaller; import org.infinispan.commons.util.Util; import org.infinispan.counter.api.CounterManager; +import org.infinispan.protostream.BaseMarshaller; import org.infinispan.protostream.FileDescriptorSource; import org.infinispan.protostream.MessageMarshaller; import org.infinispan.protostream.SerializationContext; @@ -44,11 +46,17 @@ public class InfinispanClientProducer { public static final String PROTOBUF_FILE_PREFIX = "infinispan.client.hotrod.protofile."; - private Properties properties; - private RemoteCacheManager cacheManager; @Inject private BeanManager beanManager; + private Properties properties; + private RemoteCacheManager cacheManager; + private InfinispanClientRuntimeConfig infinispanClientRuntimeConfig; + + public void setRuntimeConfig(InfinispanClientRuntimeConfig infinispanClientConfigRuntime) { + this.infinispanClientRuntimeConfig = infinispanClientConfigRuntime; + } + private void initialize() { log.debug("Initializing CacheManager"); Configuration conf; @@ -82,7 +90,7 @@ private void initialize() { /** * This method is designed to be called during static initialization time. This is so we have access to the * classes, and thus we can use reflection to find and instantiate any instances we may need - * + * * @param properties properties file read from hot rod * @throws ClassNotFoundException if a class is not actually found that should be present */ @@ -100,7 +108,7 @@ public static void replaceProperties(Properties properties) throws ClassNotFound /** * Sets up additional properties for use when proto stream marshaller is in use - * + * * @param properties the properties to be updated for querying */ public static void handleProtoStreamRequirements(Properties properties) { @@ -116,19 +124,21 @@ public static void handleProtoStreamRequirements(Properties properties) { /** * Reads all the contents of the file as a single string using default charset - * + * * @param fileName file on class path to read contents of * @return string containing the contents of the file */ private static String getContents(String fileName) { InputStream stream = InfinispanClientProducer.class.getResourceAsStream(fileName); - return new Scanner(stream, "UTF-8").useDelimiter("\\A").next(); + try (Scanner scanner = new Scanner(stream, "UTF-8")) { + return scanner.useDelimiter("\\A").next(); + } } /** * The mirror side of {@link #replaceProperties(Properties)} so that we can take out any objects that were * instantiated during static init time and inject them properly - * + * * @param properties the properties that was static constructed * @return the configuration builder based on the provided properties */ @@ -142,7 +152,17 @@ private ConfigurationBuilder builderFromProperties(Properties properties) { } builder.marshaller((Marshaller) marshallerInstance); } + + // Override serverList property value at runtime if such configuration exists + if (infinispanClientRuntimeConfig != null) { + Optional runtimeServerList = infinispanClientRuntimeConfig.serverList; + if (runtimeServerList.isPresent()) { + properties.put(ConfigurationProperties.SERVER_LIST, runtimeServerList.get()); + } + } + builder.withProperties(properties); + return builder; } @@ -183,10 +203,10 @@ private static void handleProtoStreamMarshaller(Object marshallerInstance, Prope throw new RuntimeException(e); } - Set> beans = (Set) beanManager.getBeans(MessageMarshaller.class); - for (Bean bean : beans) { - CreationalContext ctx = beanManager.createCreationalContext(bean); - MessageMarshaller messageMarshaller = (MessageMarshaller) beanManager.getReference(bean, MessageMarshaller.class, + Set> beans = (Set) beanManager.getBeans(BaseMarshaller.class); + for (Bean bean : beans) { + CreationalContext ctx = beanManager.createCreationalContext(bean); + BaseMarshaller messageMarshaller = (BaseMarshaller) beanManager.getReference(bean, BaseMarshaller.class, ctx); serializationContext.registerMarshaller(messageMarshaller); } diff --git a/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanClientConfiguration.java b/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanClientRuntimeConfig.java similarity index 55% rename from extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanClientConfiguration.java rename to extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanClientRuntimeConfig.java index fd5c6ef927694..13343a856b1ce 100644 --- a/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanClientConfiguration.java +++ b/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanClientRuntimeConfig.java @@ -7,10 +7,10 @@ import io.quarkus.runtime.annotations.ConfigRoot; /** - * @author William Burns + * @author Katia Aresti */ -@ConfigRoot(phase = ConfigPhase.RUN_TIME_STATIC) -public class InfinispanClientConfiguration { +@ConfigRoot(name = "infinispan-client", phase = ConfigPhase.RUN_TIME) +public class InfinispanClientRuntimeConfig { /** * Sets the host name/port to connect to. Each one is separated by a semicolon (eg. host1:11222;host2:11222). @@ -18,17 +18,10 @@ public class InfinispanClientConfiguration { @ConfigItem public Optional serverList; - /** - * Sets the bounded entry count for near cache. If this value is 0 or less near cache is disabled. - */ - @ConfigItem(defaultValue = "0") - public int nearCacheMaxEntries; - @Override public String toString() { - return "InfinispanClientConfiguration{" + + return "InfinispanClientRuntimeConfig{" + "serverList=" + serverList + - ", nearCacheMaxEntries=" + nearCacheMaxEntries + '}'; } } diff --git a/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanTemplate.java b/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanTemplate.java index 2b483aaca39bc..cb32c2b12c5ac 100644 --- a/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanTemplate.java +++ b/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanTemplate.java @@ -2,6 +2,7 @@ import java.util.Properties; +import io.quarkus.arc.Arc; import io.quarkus.arc.runtime.BeanContainerListener; import io.quarkus.runtime.annotations.Template; @@ -14,4 +15,8 @@ public BeanContainerListener configureInfinispan(Properties properties) { instance.configure(properties); }; } + + public void configureRuntimeProperties(InfinispanClientRuntimeConfig infinispanClientRuntimeConfig) { + Arc.container().instance(InfinispanClientProducer.class).get().setRuntimeConfig(infinispanClientRuntimeConfig); + } } diff --git a/extensions/jaeger/deployment/pom.xml b/extensions/jaeger/deployment/pom.xml index 68066a2edbc37..93911fabeb4fa 100644 --- a/extensions/jaeger/deployment/pom.xml +++ b/extensions/jaeger/deployment/pom.xml @@ -26,17 +26,17 @@ 4.0.0 - quarkus-jaeger + quarkus-jaeger-deployment Quarkus - Jaeger - Deployment io.quarkus - quarkus-core + quarkus-core-deployment io.quarkus - quarkus-jaeger-runtime + quarkus-jaeger javax.enterprise diff --git a/extensions/jaeger/runtime/pom.xml b/extensions/jaeger/runtime/pom.xml index dfdd892fd8281..3d77f3d00c555 100644 --- a/extensions/jaeger/runtime/pom.xml +++ b/extensions/jaeger/runtime/pom.xml @@ -26,14 +26,14 @@ 4.0.0 - quarkus-jaeger-runtime + quarkus-jaeger Quarkus - Jaeger - Runtime io.quarkus - quarkus-core-runtime + quarkus-core io.jaegertracing @@ -56,7 +56,8 @@ - maven-dependency-plugin + io.quarkus + quarkus-bootstrap-maven-plugin maven-compiler-plugin diff --git a/extensions/jaeger/runtime/src/main/java/io/quarkus/jaeger/runtime/JaegerDeploymentTemplate.java b/extensions/jaeger/runtime/src/main/java/io/quarkus/jaeger/runtime/JaegerDeploymentTemplate.java index d5db79bb91b01..7e129c6f2951b 100644 --- a/extensions/jaeger/runtime/src/main/java/io/quarkus/jaeger/runtime/JaegerDeploymentTemplate.java +++ b/extensions/jaeger/runtime/src/main/java/io/quarkus/jaeger/runtime/JaegerDeploymentTemplate.java @@ -16,7 +16,6 @@ package io.quarkus.jaeger.runtime; -import static io.jaegertracing.Configuration.JAEGER_ENDPOINT; import static io.jaegertracing.Configuration.JAEGER_SERVICE_NAME; import java.util.Optional; @@ -39,7 +38,9 @@ public void registerTracer(JaegerConfig jaeger) { if (!registered) { if (isValidConfig(jaeger)) { initTracerConfig(jaeger); - GlobalTracer.register(new QuarkusJaegerTracer()); + QuarkusJaegerTracer quarkusJaegerTracer = new QuarkusJaegerTracer(); + log.debugf("Registering tracer to GlobalTracer %s", quarkusJaegerTracer); + GlobalTracer.register(quarkusJaegerTracer); } registered = true; } @@ -48,15 +49,9 @@ public void registerTracer(JaegerConfig jaeger) { private boolean isValidConfig(JaegerConfig jaeger) { Config mpconfig = ConfigProvider.getConfig(); Optional serviceName = mpconfig.getOptionalValue(JAEGER_SERVICE_NAME, String.class); - Optional endpoint = mpconfig.getOptionalValue(JAEGER_ENDPOINT, String.class); if (!jaeger.serviceName.isPresent() && !serviceName.isPresent()) { log.warn( - "Jaeger service name has not been defined (e.g. JAEGER_SERVICE_NAME environment variable or system properties)"); - } else if (!jaeger.endpoint.isPresent() && !endpoint.isPresent()) { - log.warn( - "Jaeger collector endpoint has not been defined (e.g. JAEGER_ENDPOINT environment variable or system properties)"); - // Return true for now, so we can reproduce issue with UdpSender - return true; + "Jaeger service name has not been defined, either as 'quarkus.jaeger.service-name' application property or JAEGER_SERVICE_NAME environment variable/system property"); } else { return true; } 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 7b0fcba4fc670..594270e8819c5 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 @@ -34,7 +34,7 @@ public QuarkusJaegerTracer() { @Override public String toString() { - return "Jaeger Tracer"; + return tracer().toString(); } Tracer tracer() { diff --git a/extensions/jaxb/deployment/pom.xml b/extensions/jaxb/deployment/pom.xml index f3a2940095049..0303c90b5f605 100644 --- a/extensions/jaxb/deployment/pom.xml +++ b/extensions/jaxb/deployment/pom.xml @@ -26,17 +26,17 @@ 4.0.0 - quarkus-jaxb + quarkus-jaxb-deployment Quarkus - JAXB - Deployment io.quarkus - quarkus-core + quarkus-core-deployment io.quarkus - quarkus-jaxb-runtime + quarkus-jaxb diff --git a/extensions/jaxb/deployment/src/main/java/io/quarkus/jaxb/deployment/JaxbEnabledBuildItem.java b/extensions/jaxb/deployment/src/main/java/io/quarkus/jaxb/deployment/JaxbEnabledBuildItem.java new file mode 100644 index 0000000000000..df673a21436bc --- /dev/null +++ b/extensions/jaxb/deployment/src/main/java/io/quarkus/jaxb/deployment/JaxbEnabledBuildItem.java @@ -0,0 +1,14 @@ +package io.quarkus.jaxb.deployment; + +import io.quarkus.builder.item.MultiBuildItem; + +/** + * JAXB is an opt-in extension and must be enabled by + * producing a JaxbEnabledBuildItem. + */ +public final class JaxbEnabledBuildItem extends MultiBuildItem { + + public JaxbEnabledBuildItem() { + } + +} diff --git a/extensions/jaxb/deployment/src/main/java/io/quarkus/jaxb/deployment/JaxbFileRootBuildItem.java b/extensions/jaxb/deployment/src/main/java/io/quarkus/jaxb/deployment/JaxbFileRootBuildItem.java index af2392d0051ca..e78ac872ec893 100644 --- a/extensions/jaxb/deployment/src/main/java/io/quarkus/jaxb/deployment/JaxbFileRootBuildItem.java +++ b/extensions/jaxb/deployment/src/main/java/io/quarkus/jaxb/deployment/JaxbFileRootBuildItem.java @@ -1,6 +1,6 @@ package io.quarkus.jaxb.deployment; -import org.jboss.builder.item.MultiBuildItem; +import io.quarkus.builder.item.MultiBuildItem; /** * A location that should be scanned for jaxb.index files 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 52d3422535fb4..f87a1790329d3 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 @@ -113,7 +113,12 @@ class JaxbProcessor { @BuildStep void process(BuildProducer substrateProps, CombinedIndexBuildItem combinedIndexBuildItem, - List fileRoots) { + List fileRoots, + List enabled) { + + if (enabled.isEmpty()) { + return; + } Collection xmlRoot = combinedIndexBuildItem.getIndex().getAnnotations(XML_ROOT); for (AnnotationInstance i : xmlRoot) { @@ -133,6 +138,7 @@ void process(BuildProducer substrateProps, .produce(new RuntimeInitializedClassBuildItem("com.sun.xml.internal.bind.v2.runtime.reflect.opt.Injector")); addResourceBundle("javax.xml.bind.Messages"); + addResourceBundle("javax.xml.bind.helpers.Messages"); addResourceBundle("com.sun.org.apache.xml.internal.serializer.utils.SerializerMessages"); addResourceBundle("com.sun.org.apache.xml.internal.res.XMLErrorResources"); substrateProps diff --git a/extensions/jaxb/runtime/pom.xml b/extensions/jaxb/runtime/pom.xml index 02eee2192fe9e..a83a38ec83062 100644 --- a/extensions/jaxb/runtime/pom.xml +++ b/extensions/jaxb/runtime/pom.xml @@ -26,7 +26,7 @@ 4.0.0 - quarkus-jaxb-runtime + quarkus-jaxb Quarkus - JAXB - Runtime @@ -36,7 +36,7 @@ io.quarkus - quarkus-core-runtime + quarkus-core javax.xml.bind @@ -47,7 +47,8 @@ - maven-dependency-plugin + io.quarkus + quarkus-bootstrap-maven-plugin maven-compiler-plugin diff --git a/extensions/jdbc/jdbc-h2/jdbc-h2-deployment/pom.xml b/extensions/jdbc/jdbc-h2/jdbc-h2-deployment/pom.xml index 28c4648ec9ccf..742a7c2187da2 100644 --- a/extensions/jdbc/jdbc-h2/jdbc-h2-deployment/pom.xml +++ b/extensions/jdbc/jdbc-h2/jdbc-h2-deployment/pom.xml @@ -25,17 +25,17 @@ 4.0.0 - quarkus-jdbc-h2 + quarkus-jdbc-h2-deployment Quarkus - JDBC - H2 - Deployment io.quarkus - quarkus-core + quarkus-core-deployment io.quarkus - quarkus-jdbc-h2-runtime + quarkus-jdbc-h2 diff --git a/extensions/jdbc/jdbc-h2/jdbc-h2-runtime/pom.xml b/extensions/jdbc/jdbc-h2/jdbc-h2-runtime/pom.xml index f1c6ab417125e..e7ed03e96aa35 100644 --- a/extensions/jdbc/jdbc-h2/jdbc-h2-runtime/pom.xml +++ b/extensions/jdbc/jdbc-h2/jdbc-h2-runtime/pom.xml @@ -25,7 +25,7 @@ 4.0.0 - quarkus-jdbc-h2-runtime + quarkus-jdbc-h2 Quarkus - JDBC - H2 - Runtime @@ -46,15 +46,14 @@ com.oracle.substratevm svm - compile - - maven-dependency-plugin + io.quarkus + quarkus-bootstrap-maven-plugin maven-compiler-plugin diff --git a/extensions/jdbc/jdbc-mariadb/jdbc-mariadb-deployment/pom.xml b/extensions/jdbc/jdbc-mariadb/jdbc-mariadb-deployment/pom.xml index 259a678d6a372..b25445cefc7d8 100644 --- a/extensions/jdbc/jdbc-mariadb/jdbc-mariadb-deployment/pom.xml +++ b/extensions/jdbc/jdbc-mariadb/jdbc-mariadb-deployment/pom.xml @@ -25,17 +25,17 @@ 4.0.0 - quarkus-jdbc-mariadb + quarkus-jdbc-mariadb-deployment Quarkus - JDBC - MariaDB - Deployment io.quarkus - quarkus-core + quarkus-core-deployment io.quarkus - quarkus-jdbc-mariadb-runtime + quarkus-jdbc-mariadb diff --git a/extensions/jdbc/jdbc-mariadb/jdbc-mariadb-runtime/pom.xml b/extensions/jdbc/jdbc-mariadb/jdbc-mariadb-runtime/pom.xml index 7d50c925d6ada..051cafc034b6f 100644 --- a/extensions/jdbc/jdbc-mariadb/jdbc-mariadb-runtime/pom.xml +++ b/extensions/jdbc/jdbc-mariadb/jdbc-mariadb-runtime/pom.xml @@ -25,7 +25,7 @@ 4.0.0 - quarkus-jdbc-mariadb-runtime + quarkus-jdbc-mariadb Quarkus - JDBC - MariaDB - Runtime @@ -40,10 +40,10 @@ - - maven-dependency-plugin + io.quarkus + quarkus-bootstrap-maven-plugin maven-compiler-plugin diff --git a/extensions/jdbc/jdbc-mariadb/jdbc-mariadb-runtime/src/main/java/io/quarkus/jdbc/mariadb/runtime/graal/DefaultAuthenticationProvider_Substitutions.java b/extensions/jdbc/jdbc-mariadb/jdbc-mariadb-runtime/src/main/java/io/quarkus/jdbc/mariadb/runtime/graal/DefaultAuthenticationProvider_Substitutions.java index 9c58e48cfeda0..e8f9a350bc9d3 100644 --- a/extensions/jdbc/jdbc-mariadb/jdbc-mariadb-runtime/src/main/java/io/quarkus/jdbc/mariadb/runtime/graal/DefaultAuthenticationProvider_Substitutions.java +++ b/extensions/jdbc/jdbc-mariadb/jdbc-mariadb-runtime/src/main/java/io/quarkus/jdbc/mariadb/runtime/graal/DefaultAuthenticationProvider_Substitutions.java @@ -2,14 +2,15 @@ import java.sql.SQLException; -import org.mariadb.jdbc.internal.com.send.InterfaceAuthSwitchSendResponsePacket; -import org.mariadb.jdbc.internal.com.send.SendClearPasswordAuthPacket; -import org.mariadb.jdbc.internal.com.send.SendEd25519PasswordAuthPacket; -import org.mariadb.jdbc.internal.com.send.SendGssApiAuthPacket; -import org.mariadb.jdbc.internal.com.send.SendNativePasswordAuthPacket; -import org.mariadb.jdbc.internal.com.send.SendOldPasswordAuthPacket; -import org.mariadb.jdbc.internal.io.input.PacketInputStream; +import org.mariadb.jdbc.internal.com.send.authentication.AuthenticationPlugin; +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; @@ -26,27 +27,25 @@ public final class DefaultAuthenticationProvider_Substitutions { private static final String DIALOG = "dialog"; @Substitute - public static InterfaceAuthSwitchSendResponsePacket processAuthPlugin(PacketInputStream reader, - String plugin, String password, - byte[] authData, int seqNo, String passwordCharacterEncoding) + public static AuthenticationPlugin processAuthPlugin(String plugin, + String password, + byte[] authData, + Options options) throws SQLException { switch (plugin) { case MYSQL_NATIVE_PASSWORD: - return new SendNativePasswordAuthPacket(password, authData, seqNo, - passwordCharacterEncoding); + return new NativePasswordPlugin(password, authData, options.passwordCharacterEncoding); case MYSQL_OLD_PASSWORD: - return new SendOldPasswordAuthPacket(password, authData, seqNo, passwordCharacterEncoding); + return new OldPasswordPlugin(password, authData); case MYSQL_CLEAR_PASSWORD: - return new SendClearPasswordAuthPacket(password, authData, seqNo, - passwordCharacterEncoding); + return new ClearPasswordPlugin(password, options.passwordCharacterEncoding); case DIALOG: throw new UnsupportedOperationException("Authentication strategy 'dialog' is not supported in GraalVM"); + //return new SendPamAuthPacket(password, authData, options.passwordCharacterEncoding); case GSSAPI_CLIENT: - return new SendGssApiAuthPacket(reader, password, authData, seqNo, - passwordCharacterEncoding); + return new SendGssApiAuthPacket(authData, options.servicePrincipalName); case MYSQL_ED25519_PASSWORD: - return new SendEd25519PasswordAuthPacket(password, authData, seqNo, - passwordCharacterEncoding); + return new Ed25519PasswordPlugin(password, authData, options.passwordCharacterEncoding); default: throw new SQLException( diff --git a/extensions/jdbc/jdbc-mariadb/jdbc-mariadb-runtime/src/main/java/io/quarkus/jdbc/mariadb/runtime/graal/SendPamAuthPacket_Removal.java b/extensions/jdbc/jdbc-mariadb/jdbc-mariadb-runtime/src/main/java/io/quarkus/jdbc/mariadb/runtime/graal/SendPamAuthPacket_Removal.java index b87f75dce3c07..06ec3829f6fac 100644 --- a/extensions/jdbc/jdbc-mariadb/jdbc-mariadb-runtime/src/main/java/io/quarkus/jdbc/mariadb/runtime/graal/SendPamAuthPacket_Removal.java +++ b/extensions/jdbc/jdbc-mariadb/jdbc-mariadb-runtime/src/main/java/io/quarkus/jdbc/mariadb/runtime/graal/SendPamAuthPacket_Removal.java @@ -3,7 +3,7 @@ import com.oracle.svm.core.annotate.Delete; import com.oracle.svm.core.annotate.TargetClass; -@TargetClass(className = "org.mariadb.jdbc.internal.com.send.SendPamAuthPacket") +@TargetClass(className = "org.mariadb.jdbc.internal.com.send.authentication.SendPamAuthPacket") @Delete public final class SendPamAuthPacket_Removal { } diff --git a/extensions/jdbc/jdbc-mssql/jdbc-mssql-deployment/pom.xml b/extensions/jdbc/jdbc-mssql/jdbc-mssql-deployment/pom.xml index edd7886bd1074..c84c192771fbb 100644 --- a/extensions/jdbc/jdbc-mssql/jdbc-mssql-deployment/pom.xml +++ b/extensions/jdbc/jdbc-mssql/jdbc-mssql-deployment/pom.xml @@ -25,17 +25,17 @@ 4.0.0 - quarkus-jdbc-mssql + quarkus-jdbc-mssql-deployment Quarkus - JDBC - MSSQL - Deployment io.quarkus - quarkus-core + quarkus-core-deployment io.quarkus - quarkus-jdbc-mssql-runtime + quarkus-jdbc-mssql diff --git a/extensions/jdbc/jdbc-mssql/jdbc-mssql-runtime/pom.xml b/extensions/jdbc/jdbc-mssql/jdbc-mssql-runtime/pom.xml index 1bb4573e93577..1fb98a07b28f6 100644 --- a/extensions/jdbc/jdbc-mssql/jdbc-mssql-runtime/pom.xml +++ b/extensions/jdbc/jdbc-mssql/jdbc-mssql-runtime/pom.xml @@ -25,7 +25,7 @@ 4.0.0 - quarkus-jdbc-mssql-runtime + quarkus-jdbc-mssql Quarkus - JDBC - MSSQL - Runtime @@ -83,7 +83,6 @@ com.oracle.substratevm svm - compile @@ -91,7 +90,8 @@ - maven-dependency-plugin + io.quarkus + quarkus-bootstrap-maven-plugin maven-compiler-plugin diff --git a/extensions/jdbc/jdbc-postgresql/jdbc-postgresql-deployment/pom.xml b/extensions/jdbc/jdbc-postgresql/jdbc-postgresql-deployment/pom.xml index 2f23d5ec9dd64..0843dc177f45b 100644 --- a/extensions/jdbc/jdbc-postgresql/jdbc-postgresql-deployment/pom.xml +++ b/extensions/jdbc/jdbc-postgresql/jdbc-postgresql-deployment/pom.xml @@ -25,17 +25,17 @@ 4.0.0 - quarkus-jdbc-postgresql + quarkus-jdbc-postgresql-deployment Quarkus - JDBC - PostgreSQL - Deployment io.quarkus - quarkus-core + quarkus-core-deployment io.quarkus - quarkus-jdbc-postgresql-runtime + quarkus-jdbc-postgresql diff --git a/extensions/jdbc/jdbc-postgresql/jdbc-postgresql-runtime/pom.xml b/extensions/jdbc/jdbc-postgresql/jdbc-postgresql-runtime/pom.xml index caab9fc46141b..8041a68e0f3ef 100644 --- a/extensions/jdbc/jdbc-postgresql/jdbc-postgresql-runtime/pom.xml +++ b/extensions/jdbc/jdbc-postgresql/jdbc-postgresql-runtime/pom.xml @@ -25,7 +25,7 @@ 4.0.0 - quarkus-jdbc-postgresql-runtime + quarkus-jdbc-postgresql Quarkus - JDBC - PostgreSQL - Runtime @@ -40,10 +40,10 @@ - - maven-dependency-plugin + io.quarkus + quarkus-bootstrap-maven-plugin maven-compiler-plugin diff --git a/extensions/jsonb/deployment/pom.xml b/extensions/jsonb/deployment/pom.xml new file mode 100644 index 0000000000000..41fc132061181 --- /dev/null +++ b/extensions/jsonb/deployment/pom.xml @@ -0,0 +1,48 @@ + + + + quarkus-jsonb-parent + io.quarkus + 999-SNAPSHOT + ../ + + 4.0.0 + + quarkus-jsonb-deployment + Quarkus - JSON-B - Deployment + + + + io.quarkus + quarkus-core-deployment + + + io.quarkus + quarkus-jsonp-deployment + + + io.quarkus + quarkus-jsonb + + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + + diff --git a/extensions/jsonb/deployment/src/main/java/io/quarkus/jsonb/deployment/JsonbProcessor.java b/extensions/jsonb/deployment/src/main/java/io/quarkus/jsonb/deployment/JsonbProcessor.java new file mode 100755 index 0000000000000..727b6894b4b8b --- /dev/null +++ b/extensions/jsonb/deployment/src/main/java/io/quarkus/jsonb/deployment/JsonbProcessor.java @@ -0,0 +1,35 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Licensed 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.jsonb.deployment; + +import org.eclipse.yasson.JsonBindingProvider; + +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.substrate.ReflectiveClassBuildItem; +import io.quarkus.deployment.builditem.substrate.SubstrateResourceBundleBuildItem; + +public class JsonbProcessor { + + @BuildStep + void build(BuildProducer reflectiveClass, + BuildProducer resourceBundle) { + reflectiveClass.produce(new ReflectiveClassBuildItem(false, false, + JsonBindingProvider.class.getName())); + + resourceBundle.produce(new SubstrateResourceBundleBuildItem("yasson-messages")); + } +} diff --git a/extensions/jsonb/pom.xml b/extensions/jsonb/pom.xml new file mode 100644 index 0000000000000..b7d000623493d --- /dev/null +++ b/extensions/jsonb/pom.xml @@ -0,0 +1,20 @@ + + + + quarkus-build-parent + io.quarkus + 999-SNAPSHOT + ../../build-parent/pom.xml + + 4.0.0 + + quarkus-jsonb-parent + Quarkus - JSON-B + pom + + deployment + runtime + + diff --git a/extensions/jsonb/runtime/pom.xml b/extensions/jsonb/runtime/pom.xml new file mode 100644 index 0000000000000..38c504d7b6770 --- /dev/null +++ b/extensions/jsonb/runtime/pom.xml @@ -0,0 +1,55 @@ + + + + quarkus-jsonb-parent + io.quarkus + 999-SNAPSHOT + ../ + + 4.0.0 + + quarkus-jsonb + Quarkus - JSON-B - Runtime + + + + org.eclipse + yasson + + + javax.json.bind + javax.json.bind-api + + + io.quarkus + quarkus-core + + + io.quarkus + quarkus-jsonp + + + + + + + io.quarkus + quarkus-bootstrap-maven-plugin + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + diff --git a/extensions/jsonp/deployment/pom.xml b/extensions/jsonp/deployment/pom.xml new file mode 100644 index 0000000000000..e36f70a9ba179 --- /dev/null +++ b/extensions/jsonp/deployment/pom.xml @@ -0,0 +1,44 @@ + + + + quarkus-jsonp-parent + io.quarkus + 999-SNAPSHOT + ../ + + 4.0.0 + + quarkus-jsonp-deployment + Quarkus - JSON-P - Deployment + + + + io.quarkus + quarkus-core-deployment + + + io.quarkus + quarkus-jsonp + + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + + diff --git a/extensions/jsonp/deployment/src/main/java/io/quarkus/jsonp/deployment/JsonpProcessor.java b/extensions/jsonp/deployment/src/main/java/io/quarkus/jsonp/deployment/JsonpProcessor.java new file mode 100755 index 0000000000000..da431d29319c1 --- /dev/null +++ b/extensions/jsonp/deployment/src/main/java/io/quarkus/jsonp/deployment/JsonpProcessor.java @@ -0,0 +1,34 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Licensed 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.jsonp.deployment; + +import org.glassfish.json.JsonProviderImpl; + +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.FeatureBuildItem; +import io.quarkus.deployment.builditem.substrate.ReflectiveClassBuildItem; +import io.quarkus.deployment.builditem.substrate.SubstrateResourceBundleBuildItem; + +public class JsonpProcessor { + + @BuildStep + void build(BuildProducer feature, + BuildProducer reflectiveClass, + BuildProducer resourceBundle) { + reflectiveClass.produce(new ReflectiveClassBuildItem(false, false, JsonProviderImpl.class.getName())); + } +} diff --git a/extensions/jsonp/pom.xml b/extensions/jsonp/pom.xml new file mode 100644 index 0000000000000..7674aac523727 --- /dev/null +++ b/extensions/jsonp/pom.xml @@ -0,0 +1,20 @@ + + + + quarkus-build-parent + io.quarkus + 999-SNAPSHOT + ../../build-parent/pom.xml + + 4.0.0 + + quarkus-jsonp-parent + Quarkus - JSON-P + pom + + deployment + runtime + + diff --git a/extensions/jsonp/runtime/pom.xml b/extensions/jsonp/runtime/pom.xml new file mode 100644 index 0000000000000..25ed29e78b940 --- /dev/null +++ b/extensions/jsonp/runtime/pom.xml @@ -0,0 +1,47 @@ + + + + quarkus-jsonp-parent + io.quarkus + 999-SNAPSHOT + ../ + + 4.0.0 + + quarkus-jsonp + Quarkus - JSON-P - Runtime + + + + io.quarkus + quarkus-core + + + org.glassfish + javax.json + + + + + + + io.quarkus + quarkus-bootstrap-maven-plugin + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + diff --git a/extensions/kafka-client/deployment/pom.xml b/extensions/kafka-client/deployment/pom.xml index 40418d211ed2c..ecba013364d1c 100644 --- a/extensions/kafka-client/deployment/pom.xml +++ b/extensions/kafka-client/deployment/pom.xml @@ -10,17 +10,17 @@ 999-SNAPSHOT - quarkus-kafka-client + quarkus-kafka-client-deployment Quarkus - Kafka - Client - Deployment io.quarkus - quarkus-core + quarkus-core-deployment io.quarkus - quarkus-kafka-client-runtime + quarkus-kafka-client io.quarkus diff --git a/extensions/kafka-client/runtime/pom.xml b/extensions/kafka-client/runtime/pom.xml index 433bc6bab4d8d..d3b6af0639df6 100644 --- a/extensions/kafka-client/runtime/pom.xml +++ b/extensions/kafka-client/runtime/pom.xml @@ -10,11 +10,16 @@ 999-SNAPSHOT - quarkus-kafka-client-runtime + quarkus-kafka-client Quarkus - Kafka - Client - Runtime + + io.quarkus + quarkus-core + + org.apache.kafka kafka-clients @@ -30,7 +35,8 @@ - maven-dependency-plugin + io.quarkus + quarkus-bootstrap-maven-plugin maven-compiler-plugin diff --git a/extensions/keycloak/deployment/pom.xml b/extensions/keycloak/deployment/pom.xml new file mode 100644 index 0000000000000..d3c3b6ec09499 --- /dev/null +++ b/extensions/keycloak/deployment/pom.xml @@ -0,0 +1,79 @@ + + + + + + quarkus-keycloak-parent + io.quarkus + 999-SNAPSHOT + ../pom.xml + + 4.0.0 + + quarkus-keycloak-deployment + Quarkus - Keycloak - Deployment + + + + io.quarkus + quarkus-keycloak + + + io.quarkus + quarkus-core-deployment + + + io.quarkus + quarkus-arc-deployment + + + io.quarkus + quarkus-undertow-deployment + + + io.quarkus + quarkus-elytron-security-deployment + + + com.fasterxml.jackson.core + jackson-databind + + + org.keycloak + keycloak-undertow-adapter + + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + diff --git a/extensions/keycloak/deployment/src/main/java/io/quarkus/keycloak/KeycloakAdapterProcessor.java b/extensions/keycloak/deployment/src/main/java/io/quarkus/keycloak/KeycloakAdapterProcessor.java new file mode 100644 index 0000000000000..54234477d8cc1 --- /dev/null +++ b/extensions/keycloak/deployment/src/main/java/io/quarkus/keycloak/KeycloakAdapterProcessor.java @@ -0,0 +1,194 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Licensed 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.keycloak; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.keycloak.representations.adapters.config.AdapterConfig; +import org.keycloak.representations.adapters.config.PolicyEnforcerConfig; + +import io.quarkus.arc.deployment.BeanContainerListenerBuildItem; +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.ConfigurationCustomConverterBuildItem; +import io.quarkus.deployment.builditem.HotDeploymentConfigFileBuildItem; +import io.quarkus.elytron.security.deployment.AuthConfigBuildItem; +import io.quarkus.elytron.security.runtime.AuthConfig; +import io.quarkus.undertow.deployment.ServletExtensionBuildItem; + +public class KeycloakAdapterProcessor { + + KeycloakConfig keycloakConfig; + + @BuildStep + @Record(ExecutionTime.STATIC_INIT) + BeanContainerListenerBuildItem configureAdapter(KeycloakTemplate template, BuildProducer authConfig, + BuildProducer resources, + BuildProducer servletExtension) { + // configure login info + authConfig.produce(new AuthConfigBuildItem(new AuthConfig("KEYCLOAK", "KEYCLOAK", Object.class))); + + // in case keycloak.json is used, register it as a hot deployment config file + resources.produce(new HotDeploymentConfigFileBuildItem("keycloak.json")); + + AdapterConfig adapterConfig = null; + + // check if the adapter config is set in quarkus config and create the adapter configuration + if (keycloakConfig.resource.isPresent()) { + adapterConfig = createAdapterConfig(keycloakConfig); + } + + QuarkusDeploymentContext deploymentContext = template.createKeycloakDeploymentContext(adapterConfig); + + // register keycloak servlet extension + servletExtension.produce(new ServletExtensionBuildItem(template.createServletExtension(deploymentContext))); + + return new BeanContainerListenerBuildItem(template.createBeanContainerListener(deploymentContext)); + } + + private AdapterConfig createAdapterConfig(KeycloakConfig keycloakConfig) { + AdapterConfig config = new AdapterConfig(); + + config.setRealm(keycloakConfig.realm); + config.setRealmKey(keycloakConfig.realmKey.orElse(null)); + config.setAuthServerUrl(keycloakConfig.authServerUrl); + config.setSslRequired(keycloakConfig.sslRequired); + config.setConfidentialPort(keycloakConfig.confidentialPort); + config.setResource(keycloakConfig.resource.get()); + config.setUseResourceRoleMappings(keycloakConfig.useResourceRoleMappings); + config.setCors(keycloakConfig.cors); + config.setCorsMaxAge(keycloakConfig.corsMaxAge); + config.setCorsAllowedHeaders(keycloakConfig.corsAllowedHeaders); + config.setCorsAllowedMethods(keycloakConfig.corsAllowedMethods); + config.setCorsExposedHeaders(keycloakConfig.corsExposedHeaders); + config.setBearerOnly(keycloakConfig.bearerOnly); + config.setAutodetectBearerOnly(keycloakConfig.autodetectBearerOnly); + config.setPublicClient(keycloakConfig.publicClient); + + Map credentials = new HashMap<>(); + + if (keycloakConfig.credentials != null) { + if (keycloakConfig.credentials.secret.isPresent()) { + credentials.put("secret", keycloakConfig.credentials.secret.get()); + } else if (keycloakConfig.credentials.jwt != null && !keycloakConfig.credentials.jwt.isEmpty()) { + Map jwt = new HashMap<>(); + jwt.putAll(keycloakConfig.credentials.jwt); + credentials.put("jwt", jwt); + } else if (keycloakConfig.credentials.secretJwt != null && !keycloakConfig.credentials.secretJwt.isEmpty()) { + Map secretJwt = new HashMap<>(); + secretJwt.putAll(keycloakConfig.credentials.secretJwt); + credentials.put("secret-jwt", secretJwt); + } + } + + config.setCredentials(credentials); + + config.setRedirectRewriteRules(keycloakConfig.redirectRewriteRules); + config.setAllowAnyHostname(keycloakConfig.allowAnyHostname); + config.setDisableTrustManager(keycloakConfig.disableTrustManager); + config.setTruststore(keycloakConfig.truststore.orElse(null)); + config.setTruststorePassword(keycloakConfig.truststorePassword); + config.setClientKeystore(keycloakConfig.clientKeystore.orElse(null)); + config.setClientKeystorePassword(keycloakConfig.clientKeystorePassword); + config.setClientKeyPassword(keycloakConfig.clientKeyPassword); + config.setConnectionPoolSize(keycloakConfig.connectionPoolSize); + config.setAlwaysRefreshToken(keycloakConfig.alwaysRefreshToken); + config.setRegisterNodeAtStartup(keycloakConfig.registerNodeAtStartup); + config.setRegisterNodePeriod(keycloakConfig.registerNodePeriod); + config.setTokenStore(keycloakConfig.tokenStore.orElse(null)); + config.setTokenCookiePath(keycloakConfig.tokenCookiePath.orElse(null)); + config.setPrincipalAttribute(keycloakConfig.principalAttribute); + config.setTurnOffChangeSessionIdOnLogin(keycloakConfig.turnOffChangeSessionIdOnLogin); + config.setTokenMinimumTimeToLive(keycloakConfig.tokenMinimumTimeToLive); + config.setMinTimeBetweenJwksRequests(keycloakConfig.minTimeBetweenJwksRequests); + config.setPublicKeyCacheTtl(keycloakConfig.publicKeyCacheTtl); + config.setProxyUrl(keycloakConfig.proxyUrl.orElse(null)); + config.setVerifyTokenAudience(keycloakConfig.verifyTokenAudience); + config.setIgnoreOAuthQueryParameter(keycloakConfig.ignoreOAuthQueryParameter); + + if (keycloakConfig.policyEnforcer != null && keycloakConfig.policyEnforcer.enable) { + PolicyEnforcerConfig enforcerConfig = new PolicyEnforcerConfig(); + + enforcerConfig.setLazyLoadPaths(keycloakConfig.policyEnforcer.lazyLoadPaths); + enforcerConfig.setEnforcementMode( + PolicyEnforcerConfig.EnforcementMode.valueOf(keycloakConfig.policyEnforcer.enforcementMode)); + enforcerConfig.setHttpMethodAsScope(keycloakConfig.policyEnforcer.httpMethodAsScope); + enforcerConfig.setOnDenyRedirectTo(keycloakConfig.policyEnforcer.onDenyRedirectTo.orElse(null)); + + PolicyEnforcerConfig.PathCacheConfig pathCacheConfig = new PolicyEnforcerConfig.PathCacheConfig(); + + pathCacheConfig.setLifespan(keycloakConfig.policyEnforcer.pathCacheConfig.lifespan); + pathCacheConfig.setMaxEntries(keycloakConfig.policyEnforcer.pathCacheConfig.maxEntries); + + enforcerConfig.setPathCacheConfig(pathCacheConfig); + + if (keycloakConfig.policyEnforcer.userManagedAccess) { + enforcerConfig.setUserManagedAccess(new PolicyEnforcerConfig.UserManagedAccessConfig()); + } + + enforcerConfig.setClaimInformationPointConfig( + getClaimInformationPointConfig(keycloakConfig.policyEnforcer.claimInformationPointConfig)); + enforcerConfig.setPaths(keycloakConfig.policyEnforcer.paths.values().stream().map( + pathConfig -> { + PolicyEnforcerConfig.PathConfig config1 = new PolicyEnforcerConfig.PathConfig(); + + config1.setName(pathConfig.name.orElse(null)); + config1.setPath(pathConfig.path.orElse(null)); + config1.setEnforcementMode(pathConfig.enforcementMode); + config1.setMethods(pathConfig.methods.values().stream().map( + methodConfig -> { + PolicyEnforcerConfig.MethodConfig mConfig = new PolicyEnforcerConfig.MethodConfig(); + + mConfig.setMethod(methodConfig.method); + mConfig.setScopes(methodConfig.scopes); + mConfig.setScopesEnforcementMode(methodConfig.scopesEnforcementMode); + + return mConfig; + }).collect(Collectors.toList())); + config1.setClaimInformationPointConfig( + getClaimInformationPointConfig(pathConfig.claimInformationPointConfig)); + + return config1; + }).collect(Collectors.toList())); + + config.setPolicyEnforcerConfig(enforcerConfig); + } + + return config; + } + + private Map> getClaimInformationPointConfig( + KeycloakConfig.KeycloakConfigPolicyEnforcer.ClaimInformationPointConfig config) { + Map> cipConfig = new HashMap<>(); + + for (Map.Entry> entry : config.simpleConfig.entrySet()) { + cipConfig.put(entry.getKey(), new HashMap<>(entry.getValue())); + } + + for (Map.Entry>> entry : config.complexConfig.entrySet()) { + cipConfig.computeIfAbsent(entry.getKey(), s -> new HashMap<>()).putAll(new HashMap<>(entry.getValue())); + } + + return cipConfig; + } +} diff --git a/extensions/keycloak/deployment/src/main/java/io/quarkus/keycloak/KeycloakConfig.java b/extensions/keycloak/deployment/src/main/java/io/quarkus/keycloak/KeycloakConfig.java new file mode 100644 index 0000000000000..7e94e45102124 --- /dev/null +++ b/extensions/keycloak/deployment/src/main/java/io/quarkus/keycloak/KeycloakConfig.java @@ -0,0 +1,490 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Licensed 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.keycloak; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import org.keycloak.representations.adapters.config.PolicyEnforcerConfig; + +import io.quarkus.runtime.annotations.ConfigGroup; +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigRoot; + +@ConfigRoot(name = "keycloak") +public final class KeycloakConfig { + + /** + * Name of the realm. + */ + @ConfigItem(name = "realm") + String realm; + + /** + * Name of the realm. + */ + @ConfigItem(name = "realm-public-key") + Optional realmKey; + + /** + * The client-id of the application. Each application has a client-id that is used to identify the application + */ + @ConfigItem(name = "resource") + Optional resource; + + /** + * The base URL of the Keycloak server. All other Keycloak pages and REST service endpoints are derived from this. + * It is usually of the form https://host:port/auth + */ + @ConfigItem(name = "auth-server-url") + String authServerUrl; + + /** + * Ensures that all communication to and from the Keycloak server is over HTTPS. In production this should be set to all. + * This is OPTIONAL. The default value is external meaning that HTTPS is required by default for external requests. + * Valid values are 'all', 'external' and 'none' + */ + @ConfigItem(name = "ssl-required", defaultValue = "external") + String sslRequired; + + /** + * The confidential port used by the Keycloak server for secure connections over SSL/TLS + */ + @ConfigItem(name = "confidential-port", defaultValue = "8443") + int confidentialPort; + + /** + * If set to true, the adapter will look inside the token for application level role mappings for the user. + * If false, it will look at the realm level for user role mappings + */ + @ConfigItem(name = "use-resource-role-mappings", defaultValue = "false") + boolean useResourceRoleMappings; + + /** + * This enables CORS support. It will handle CORS preflight requests. It will also look into the access token to + * determine valid origins + */ + @ConfigItem(name = "enable-cors", defaultValue = "false") + boolean cors; + + /** + * If CORS is enabled, this sets the value of the Access-Control-Max-Age header. This is OPTIONAL. If not set, + * this header is not returned in CORS responses + */ + @ConfigItem(name = "cors-max-age", defaultValue = "-1") + int corsMaxAge; + + /** + * If CORS is enabled, this sets the value of the Access-Control-Allow-Headers header. This should be a comma-separated + * string + */ + @ConfigItem(name = "cors-allowed-headers") + String corsAllowedHeaders; + + /** + * If CORS is enabled, this sets the value of the Access-Control-Allow-Methods header. This should be a comma-separated + * string + */ + @ConfigItem(name = "cors-allowed-methods") + String corsAllowedMethods; + + /** + * If CORS is enabled, this sets the value of the Access-Control-Expose-Headers header. This should be a comma-separated + * string + */ + @ConfigItem(name = "cors-exposed-headers") + String corsExposedHeaders; + + /** + * This should be set to true for services. If enabled the adapter will not attempt to authenticate users, + * but only verify bearer tokens + */ + @ConfigItem(name = "bearer-only", defaultValue = "true") + boolean bearerOnly; + + /** + * This should be set to true if your application serves both a web application and web services (e.g. SOAP or REST). + * It allows you to redirect unauthenticated users of the web application to the Keycloak login page, but send an HTTP 401 + * status code to unauthenticated SOAP or REST clients instead as they would not understand a redirect to the login page. + * Keycloak auto-detects SOAP or REST clients based on typical headers like X-Requested-With, SOAPAction or Accept + */ + @ConfigItem(name = "autodetect-bearer-only", defaultValue = "false") + boolean autodetectBearerOnly; + + /** + * If this application is a public client + */ + @ConfigItem(name = "public-client", defaultValue = "false") + boolean publicClient; + + /** + * Specify the credentials of the application. This is an object notation where the key is the credential type and the + * value is the value of the credential type. Currently password and jwt is supported + */ + @ConfigItem(name = "credentials") + KeycloakConfigCredentials credentials; + + /** + * If the Keycloak server requires HTTPS and this config option is set to true the Keycloak server’s certificate is + * validated via the truststore, but host name validation is not done. This setting should only be used during development + * and never in production as it will disable verification of SSL certificates. This seting may be useful in test + * environments + */ + @ConfigItem(name = "allow-any-hostname", defaultValue = "false") + boolean allowAnyHostname; + + /** + * If the Keycloak server requires HTTPS and this config option is set to true you do not have to specify a truststore. + * This setting should only be used during development and never in production as it will disable verification + * of SSL certificates + */ + @ConfigItem(name = "disable-trust-manager", defaultValue = "false") + boolean disableTrustManager; + + /** + * If the adapter should refresh the access token for each request + */ + @ConfigItem(name = "always-refresh-token", defaultValue = "false") + boolean alwaysRefreshToken; + + /** + * The value is the file path to a keystore file. If you prefix the path with classpath:, then the truststore will be + * obtained from the deployment’s classpath instead. Used for outgoing HTTPS communications to the Keycloak server + */ + @ConfigItem(name = "truststore") + Optional truststore; + + /** + * Password for the truststore keystore + */ + @ConfigItem(name = "truststore-password") + String truststorePassword; + + /** + * This is the file path to a keystore file. This keystore contains client certificate for two-way SSL when the adapter + * makes HTTPS requests to the Keycloak server + */ + @ConfigItem(name = "client-keystore") + Optional clientKeystore; + + /** + * Password for the client keystore + */ + @ConfigItem(name = "client-keystore-password") + String clientKeystorePassword; + + /** + * Password for the client’s key + */ + @ConfigItem(name = "client-key-password") + String clientKeyPassword; + + /** + * Adapters will make separate HTTP invocations to the Keycloak server to turn an access code into an access token. + * This config option defines how many connections to the Keycloak server should be pooled + */ + @ConfigItem(name = "connection-pool-size", defaultValue = "20") + int connectionPoolSize; + + /** + * If true, then adapter will send registration request to Keycloak. It’s false by default and useful only when application + * is clustered + */ + @ConfigItem(name = "register-node-at-startup", defaultValue = "false") + boolean registerNodeAtStartup; + + /** + * Period for re-registration adapter to Keycloak. Useful when application is clustered + */ + @ConfigItem(name = "register-node-period", defaultValue = "-1") + int registerNodePeriod; + + /** + * Possible values are session and cookie. Default is session, which means that adapter stores account info in HTTP Session. + * Alternative cookie means storage of info in cookie + */ + @ConfigItem(name = "token-store") + Optional tokenStore; + + /** + * When using a cookie store, this option sets the path of the cookie used to store account info. If it’s a relative path, + * then it is assumed that the application is running in a context root, and is interpreted relative to that context root. + * If it’s an absolute path, then the absolute path is used to set the cookie path. Defaults to use paths relative to the + * context root + */ + @ConfigItem(name = "adapter-state-cookie-path") + Optional tokenCookiePath; + + /** + * OpenID Connect ID Token attribute to populate the UserPrincipal name with. If token attribute is null. Possible values + * are sub, preferred_username, email, name, nickname, given_name, family_name + */ + @ConfigItem(name = "principal-attribute", defaultValue = "sub") + String principalAttribute; + + /** + * The session id is changed by default on a successful login on some platforms to plug a security attack vector. + * Change this to true if you want to turn this off + */ + @ConfigItem(name = "turn-off-change-session-id-on-login", defaultValue = "false") + boolean turnOffChangeSessionIdOnLogin; + + /** + * Amount of time, in seconds, to preemptively refresh an active access token with the Keycloak server before it expires. + * This is especially useful when the access token is sent to another REST client where it could expire before being + * evaluated. This value should never exceed the realm’s access token lifespan + */ + @ConfigItem(name = "token-minimum-time-to-live", defaultValue = "0") + int tokenMinimumTimeToLive; + + /** + * Amount of time, in seconds, specifying minimum interval between two requests to Keycloak to retrieve new public keys. + * It is 10 seconds by default. Adapter will always try to download new public key when it recognize token with unknown kid. + * However it won’t try it more than once per 10 seconds (by default). This is to avoid DoS when attacker sends lots of + * tokens with bad kid forcing adapter to send lots of requests to Keycloak + */ + @ConfigItem(name = "min-time-between-jwks-requests", defaultValue = "10") + int minTimeBetweenJwksRequests; + + /** + * Amount of time, in seconds, specifying maximum interval between two requests to Keycloak to retrieve new public keys. + * It is 86400 seconds (1 day) by default. Adapter will always try to download new public key when it recognize token + * with unknown kid . If it recognize token with known kid, it will just use the public key downloaded previously. + * However at least once per this configured interval (1 day by default) will be new public key always downloaded even if + * the kid of token is already known + */ + @ConfigItem(name = "public-key-cache-ttl", defaultValue = "86400") + int publicKeyCacheTtl; + + /** + * If set to true, then during authentication with the bearer token, the adapter will verify whether the token contains + * this client name (resource) as an audience. The option is especially useful for services, which primarily serve + * requests authenticated by the bearer token. This is set to false by default, however for improved security, it is + * recommended to enable this. See Audience Support for more details about audience support + */ + @ConfigItem(name = "verify-token-audience", defaultValue = "false") + boolean verifyTokenAudience; + + /** + * If set to true will turn off processing of the access_token query parameter for bearer token processing. + * Users will not be able to authenticate if they only pass in an access_token + */ + @ConfigItem(name = "ignore-oauth-query-parameter", defaultValue = "false") + boolean ignoreOAuthQueryParameter; + + /** + * The proxy url to use for requests to the auth-server. + */ + @ConfigItem(name = "proxy-url") + Optional proxyUrl; + + /** + * If needed, specify the Redirect URI rewrite rule. This is an object notation where the key is the regular expression to + * which the Redirect URI is to be matched and the value is the replacement String. $ character can be used for + * backreferences in the replacement String + */ + @ConfigItem(name = "redirect-rewrite-rules") + Map redirectRewriteRules; + + /** + * Policy enforcement configuration when using Keycloak Authorization Services + */ + @ConfigItem(name = "policy-enforcer") + KeycloakConfigPolicyEnforcer policyEnforcer; + + @ConfigGroup + public static class KeycloakConfigCredentials { + + /** + * The client secret + */ + @ConfigItem(name = "secret") + Optional secret; + + /** + * The settings for client authentication with signed JWT + */ + @ConfigItem(name = "jwt") + Map jwt; + + /** + * The settings for client authentication with JWT using client secret + */ + @ConfigItem(name = "secret-jwt") + Map secretJwt; + } + + @ConfigGroup + public static class KeycloakConfigPolicyEnforcer { + + /** + * Specifies how policies are enforced. + */ + @ConfigItem(name = "enable", defaultValue = "true") + boolean enable; + + /** + * Specifies how policies are enforced. + */ + @ConfigItem(name = "enforcement-mode", defaultValue = "ENFORCING") + String enforcementMode; + + /** + * Specifies the paths to protect. + */ + @ConfigItem(name = "paths") + Map paths; + + /** + * Defines how the policy enforcer should track associations between paths in your application and resources defined in + * Keycloak. + * The cache is needed to avoid unnecessary requests to a Keycloak server by caching associations between paths and + * protected resources + */ + @ConfigItem(name = "path-cache") + PathCacheConfig pathCacheConfig; + + /** + * Specifies how the adapter should fetch the server for resources associated with paths in your application. If true, + * the + * policy + * enforcer is going to fetch resources on-demand accordingly with the path being requested + */ + @ConfigItem(name = "lazy-load-paths", defaultValue = "true") + Boolean lazyLoadPaths; + + /** + * Defines a URL where a client request is redirected when an "access denied" message is obtained from the server. + * By default, the adapter responds with a 403 HTTP status code + */ + @ConfigItem(name = "on-deny-redirect-to") + Optional onDenyRedirectTo; + + /** + * Specifies that the adapter uses the UMA protocol. + */ + @ConfigItem(name = "user-managed-access", defaultValue = "false") + boolean userManagedAccess; + + /** + * 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(name = "claim-information-point") + ClaimInformationPointConfig claimInformationPointConfig; + + /** + * Specifies how scopes should be mapped to HTTP methods. If set to true, the policy enforcer will use the HTTP method + * from + * the current request to check whether or not access should be granted + */ + @ConfigItem(name = "http-method-as-scope", defaultValue = "false") + boolean httpMethodAsScope; + + @ConfigGroup + public static class PathConfig { + + /** + * The name of a resource on the server that is to be associated with a given path + */ + @ConfigItem + Optional name; + + /** + * A URI relative to the application’s context path that should be protected by the policy enforcer + */ + @ConfigItem + Optional path; + + /** + * The HTTP methods (for example, GET, POST, PATCH) to protect and how they are associated with the scopes for a + * given + * resource in the server + */ + @ConfigItem + Map methods; + + /** + * Specifies how policies are enforced + */ + @ConfigItem(name = "enforcement-mode", defaultValue = "ENFORCING") + 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 + * these + * claims available to policies + */ + @ConfigItem(name = "claim-information-point") + ClaimInformationPointConfig claimInformationPointConfig; + } + + @ConfigGroup + public static class MethodConfig { + + /** + * The name of the HTTP method + */ + @ConfigItem + String method; + + /** + * An array of strings with the scopes associated with the method + */ + @ConfigItem + List scopes; + + /** + * A string referencing the enforcement mode for the scopes associated with a method + */ + @ConfigItem(name = "scopes-enforcement-mode", defaultValue = "ALL") + PolicyEnforcerConfig.ScopeEnforcementMode scopesEnforcementMode; + } + + @ConfigGroup + public static class PathCacheConfig { + + /** + * Defines the time in milliseconds when the entry should be expired + */ + @ConfigItem(name = "max-entries", defaultValue = "1000") + int maxEntries = 1000; + + /** + * Defines the limit of entries that should be kept in the cache + */ + @ConfigItem(defaultValue = "30000") + long lifespan = 30000; + } + + @ConfigGroup + public static class ClaimInformationPointConfig { + + /** + * + */ + @ConfigItem(name = ConfigItem.PARENT) + Map>> complexConfig; + + /** + * + */ + @ConfigItem(name = ConfigItem.PARENT) + Map> simpleConfig; + } + } +} diff --git a/extensions/keycloak/deployment/src/main/java/io/quarkus/keycloak/KeycloakDeploymentProcessor.java b/extensions/keycloak/deployment/src/main/java/io/quarkus/keycloak/KeycloakDeploymentProcessor.java new file mode 100644 index 0000000000000..051c4009d56f3 --- /dev/null +++ b/extensions/keycloak/deployment/src/main/java/io/quarkus/keycloak/KeycloakDeploymentProcessor.java @@ -0,0 +1,43 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Licensed 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.keycloak; + +import io.quarkus.arc.deployment.AdditionalBeanBuildItem; +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.ExtensionSslNativeSupportBuildItem; +import io.quarkus.deployment.builditem.FeatureBuildItem; + +/** + * @author Pedro Igor + */ +class KeycloakDeploymentProcessor { + + @BuildStep + @Record(ExecutionTime.STATIC_INIT) + void configureKeycloakAdapter(BuildProducer feature, + BuildProducer extensionSslNativeSupport, + BuildProducer beans) { + feature.produce(new FeatureBuildItem(FeatureBuildItem.KEYCLOAK)); + extensionSslNativeSupport.produce(new ExtensionSslNativeSupportBuildItem(FeatureBuildItem.KEYCLOAK)); + + // register producers for injection and configuration of keycloak components + beans.produce(new AdditionalBeanBuildItem(KeycloakSecurityContextProducer.class)); + beans.produce(new AdditionalBeanBuildItem(QuarkusKeycloakConfigResolver.class)); + } +} diff --git a/extensions/keycloak/deployment/src/main/java/io/quarkus/keycloak/KeycloakReflectionProcessor.java b/extensions/keycloak/deployment/src/main/java/io/quarkus/keycloak/KeycloakReflectionProcessor.java new file mode 100644 index 0000000000000..fa37e2e2a8d53 --- /dev/null +++ b/extensions/keycloak/deployment/src/main/java/io/quarkus/keycloak/KeycloakReflectionProcessor.java @@ -0,0 +1,96 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Licensed 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.keycloak; + +import org.apache.commons.logging.impl.Jdk14Logger; +import org.apache.commons.logging.impl.LogFactoryImpl; +import org.keycloak.adapters.authentication.ClientCredentialsProvider; +import org.keycloak.adapters.authentication.ClientIdAndSecretCredentialsProvider; +import org.keycloak.adapters.authentication.JWTClientCredentialsProvider; +import org.keycloak.adapters.authentication.JWTClientSecretCredentialsProvider; +import org.keycloak.adapters.authorization.ClaimInformationPointProviderFactory; +import org.keycloak.adapters.authorization.cip.ClaimsInformationPointProviderFactory; +import org.keycloak.adapters.authorization.cip.HttpClaimInformationPointProviderFactory; +import org.keycloak.authorization.client.representation.ServerConfiguration; +import org.keycloak.jose.jwk.JSONWebKeySet; +import org.keycloak.jose.jwk.JWK; +import org.keycloak.jose.jws.JWSHeader; +import org.keycloak.json.StringListMapDeserializer; +import org.keycloak.json.StringOrArrayDeserializer; +import org.keycloak.representations.AccessToken; +import org.keycloak.representations.AccessTokenResponse; +import org.keycloak.representations.IDToken; +import org.keycloak.representations.JsonWebToken; +import org.keycloak.representations.RefreshToken; +import org.keycloak.representations.idm.authorization.AuthorizationRequest; +import org.keycloak.representations.idm.authorization.AuthorizationResponse; +import org.keycloak.representations.idm.authorization.Permission; +import org.keycloak.representations.idm.authorization.PermissionRequest; +import org.keycloak.representations.idm.authorization.PermissionResponse; +import org.keycloak.representations.idm.authorization.PermissionTicketToken; +import org.keycloak.representations.idm.authorization.ResourceOwnerRepresentation; +import org.keycloak.representations.idm.authorization.ResourceRepresentation; +import org.keycloak.representations.idm.authorization.ScopeRepresentation; + +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.substrate.ReflectiveClassBuildItem; +import io.quarkus.deployment.builditem.substrate.ServiceProviderBuildItem; + +public class KeycloakReflectionProcessor { + + @BuildStep + public void registerReflectionItems(BuildProducer reflectiveItems) { + reflectiveItems.produce(new ReflectiveClassBuildItem(true, true, + JsonWebToken.class.getName(), + JWSHeader.class.getName(), + AccessToken.class.getName(), + IDToken.class.getName(), + RefreshToken.class.getName(), + AccessTokenResponse.class.getName(), + JSONWebKeySet.class.getName(), + JWK.class.getName(), + StringOrArrayDeserializer.class.getName(), + AccessToken.Access.class.getName(), + AccessToken.Authorization.class.getName(), + AuthorizationRequest.class.getName(), + AuthorizationResponse.class.getName(), + PermissionRequest.class.getName(), + PermissionResponse.class.getName(), + PermissionTicketToken.class.getName(), + Permission.class.getName(), + ServerConfiguration.class.getName(), + ResourceRepresentation.class.getName(), + ScopeRepresentation.class.getName(), + ResourceOwnerRepresentation.class.getName(), + StringListMapDeserializer.class.getName(), + StringOrArrayDeserializer.class.getName(), + LogFactoryImpl.class.getName(), + Jdk14Logger.class.getName())); + } + + @BuildStep + public void registerServiceProviders(BuildProducer serviceProvider) { + serviceProvider.produce(new ServiceProviderBuildItem(ClientCredentialsProvider.class.getName(), + ClientIdAndSecretCredentialsProvider.class.getName(), + JWTClientCredentialsProvider.class.getName(), + JWTClientSecretCredentialsProvider.class.getName())); + serviceProvider.produce(new ServiceProviderBuildItem(ClaimInformationPointProviderFactory.class.getName(), + HttpClaimInformationPointProviderFactory.class.getName(), + ClaimsInformationPointProviderFactory.class.getName())); + + } +} diff --git a/extensions/keycloak/pom.xml b/extensions/keycloak/pom.xml new file mode 100644 index 0000000000000..bb2e30e49d452 --- /dev/null +++ b/extensions/keycloak/pom.xml @@ -0,0 +1,37 @@ + + + + + + quarkus-build-parent + io.quarkus + 999-SNAPSHOT + ../../build-parent/pom.xml + + 4.0.0 + + quarkus-keycloak-parent + Quarkus - Keycloak Adapter + pom + + + deployment + runtime + + diff --git a/extensions/keycloak/runtime/pom.xml b/extensions/keycloak/runtime/pom.xml new file mode 100644 index 0000000000000..5be9e7c5ccb8f --- /dev/null +++ b/extensions/keycloak/runtime/pom.xml @@ -0,0 +1,83 @@ + + + + + + quarkus-keycloak-parent + io.quarkus + 999-SNAPSHOT + ../pom.xml + + 4.0.0 + + quarkus-keycloak + Quarkus - Keycloak - Runtime + + + + io.quarkus + quarkus-core + + + io.quarkus + quarkus-arc + + + io.quarkus + quarkus-undertow + + + io.quarkus + quarkus-elytron-security + + + org.keycloak + keycloak-undertow-adapter + + + org.keycloak + keycloak-authz-client + + + com.fasterxml.jackson.core + jackson-databind + + + + + + + io.quarkus + quarkus-bootstrap-maven-plugin + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + diff --git a/extensions/keycloak/runtime/src/main/java/io/quarkus/keycloak/KeycloakSecurityContextProducer.java b/extensions/keycloak/runtime/src/main/java/io/quarkus/keycloak/KeycloakSecurityContextProducer.java new file mode 100644 index 0000000000000..a2ae6f200af2c --- /dev/null +++ b/extensions/keycloak/runtime/src/main/java/io/quarkus/keycloak/KeycloakSecurityContextProducer.java @@ -0,0 +1,41 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Licensed 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.keycloak; + +import javax.enterprise.context.RequestScoped; +import javax.enterprise.inject.Produces; +import javax.inject.Inject; +import javax.servlet.http.HttpServletRequest; + +import org.keycloak.KeycloakSecurityContext; + +/** + * This class is responsible for producing {@link KeycloakSecurityContext} instances so that applications are able to inject + * those instance into their beans in order to obtain information about the security context created by the Keycloak Extension. + * + * @author Pedro Igor + */ +public class KeycloakSecurityContextProducer { + + @Inject + HttpServletRequest request; + + @Produces + @RequestScoped + public KeycloakSecurityContext produceSecurityContext() { + return (KeycloakSecurityContext) request.getAttribute(KeycloakSecurityContext.class.getName()); + } +} diff --git a/extensions/keycloak/runtime/src/main/java/io/quarkus/keycloak/KeycloakTemplate.java b/extensions/keycloak/runtime/src/main/java/io/quarkus/keycloak/KeycloakTemplate.java new file mode 100644 index 0000000000000..b10557ef85a12 --- /dev/null +++ b/extensions/keycloak/runtime/src/main/java/io/quarkus/keycloak/KeycloakTemplate.java @@ -0,0 +1,71 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Licensed 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.keycloak; + +import java.io.InputStream; + +import org.keycloak.adapters.AdapterDeploymentContext; +import org.keycloak.adapters.KeycloakDeployment; +import org.keycloak.adapters.KeycloakDeploymentBuilder; +import org.keycloak.adapters.undertow.KeycloakServletExtension; +import org.keycloak.representations.adapters.config.AdapterConfig; + +import io.quarkus.arc.runtime.BeanContainer; +import io.quarkus.arc.runtime.BeanContainerListener; +import io.quarkus.runtime.annotations.Template; +import io.undertow.servlet.ServletExtension; + +/** + * @author Pedro Igor + */ +@Template +public class KeycloakTemplate { + + public ServletExtension createServletExtension(AdapterDeploymentContext deploymentContext) { + return new KeycloakServletExtension(deploymentContext); + } + + public QuarkusDeploymentContext createKeycloakDeploymentContext(AdapterConfig defaultConfig) { + KeycloakDeployment deployment; + + if (defaultConfig == null) { + InputStream config = loadConfig(Thread.currentThread().getContextClassLoader()); + + if (config == null) { + config = loadConfig(getClass().getClassLoader()); + } + + deployment = KeycloakDeploymentBuilder.build(config); + } else { + deployment = KeycloakDeploymentBuilder.build(defaultConfig); + } + + return new QuarkusDeploymentContext(deployment); + } + + public BeanContainerListener createBeanContainerListener(QuarkusDeploymentContext deploymentContext) { + return new BeanContainerListener() { + @Override + public void created(BeanContainer container) { + deploymentContext.setConfigResolver(container.instance(QuarkusKeycloakConfigResolver.class)); + } + }; + } + + private InputStream loadConfig(ClassLoader classLoader) { + return classLoader.getResourceAsStream("keycloak.json"); + } +} diff --git a/extensions/keycloak/runtime/src/main/java/io/quarkus/keycloak/QuarkusDeploymentContext.java b/extensions/keycloak/runtime/src/main/java/io/quarkus/keycloak/QuarkusDeploymentContext.java new file mode 100644 index 0000000000000..3983e562eb279 --- /dev/null +++ b/extensions/keycloak/runtime/src/main/java/io/quarkus/keycloak/QuarkusDeploymentContext.java @@ -0,0 +1,44 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Licensed 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.keycloak; + +import org.keycloak.adapters.AdapterDeploymentContext; +import org.keycloak.adapters.KeycloakDeployment; +import org.keycloak.representations.adapters.config.AdapterConfig; + +/** + * An extension to Keycloak default {@code AdapterDeploymentContext} so that any additional requirement on how + * {@link KeycloakDeployment} + * instances are resolved. + * + * @author Pedro Igor + */ +public class QuarkusDeploymentContext extends AdapterDeploymentContext { + + private KeycloakDeployment defaultDeployment; + + public QuarkusDeploymentContext() { + } + + public QuarkusDeploymentContext(KeycloakDeployment defaultDeployment) { + this.defaultDeployment = defaultDeployment; + } + + void setConfigResolver(QuarkusKeycloakConfigResolver configResolver) { + super.configResolver = configResolver; + configResolver.init(defaultDeployment); + } +} diff --git a/extensions/keycloak/runtime/src/main/java/io/quarkus/keycloak/QuarkusKeycloakConfigResolver.java b/extensions/keycloak/runtime/src/main/java/io/quarkus/keycloak/QuarkusKeycloakConfigResolver.java new file mode 100644 index 0000000000000..5ab8b953507e9 --- /dev/null +++ b/extensions/keycloak/runtime/src/main/java/io/quarkus/keycloak/QuarkusKeycloakConfigResolver.java @@ -0,0 +1,61 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Licensed 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.keycloak; + +import javax.enterprise.inject.Instance; +import javax.inject.Inject; + +import org.keycloak.adapters.KeycloakConfigResolver; +import org.keycloak.adapters.KeycloakDeployment; +import org.keycloak.adapters.spi.HttpFacade; + +/** + *

+ * A {@link KeycloakConfigResolver} that is installed to applications so that any custom {@code KeycloakConfigResolver} instance + * produced by applications are considered when resolving {@link KeycloakDeployment} instances. + * + * @author Pedro Igor + */ +public class QuarkusKeycloakConfigResolver implements KeycloakConfigResolver { + + @Inject + Instance configResolvers; + + private KeycloakConfigResolver delegate; + private KeycloakDeployment defaultDeployment; + + @Override + public KeycloakDeployment resolve(HttpFacade.Request facade) { + if (delegate == null) { + return defaultDeployment; + } + + KeycloakDeployment deployment = delegate.resolve(facade); + + if (deployment == null) { + deployment = defaultDeployment; + } + + return deployment; + } + + void init(KeycloakDeployment defaultDeployment) { + this.defaultDeployment = defaultDeployment; + delegate = configResolvers.stream() + .filter(keycloakConfigResolver -> !QuarkusKeycloakConfigResolver.class.isInstance(keycloakConfigResolver)) + .findAny().orElse(null); + } +} diff --git a/extensions/kotlin/deployment/pom.xml b/extensions/kotlin/deployment/pom.xml index 7555b1b405c38..5a1dfd7829802 100644 --- a/extensions/kotlin/deployment/pom.xml +++ b/extensions/kotlin/deployment/pom.xml @@ -26,10 +26,15 @@ 4.0.0 - quarkus-kotlin + quarkus-kotlin-deployment Quarkus - Kotlin - Deployment + + io.quarkus + quarkus-kotlin + + io.quarkus quarkus-development-mode 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 77f95f1ebb4b2..e29727281862c 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 @@ -2,6 +2,7 @@ import java.io.File; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Set; import java.util.stream.Collectors; @@ -19,8 +20,8 @@ public class KotlinCompilationProvider implements CompilationProvider { @Override - public String handledExtension() { - return ".kt"; + public Set handledExtensions() { + return Collections.singleton(".kt"); } @Override diff --git a/extensions/kotlin/pom.xml b/extensions/kotlin/pom.xml index 44f907d33cb4e..cda5e842467e4 100644 --- a/extensions/kotlin/pom.xml +++ b/extensions/kotlin/pom.xml @@ -31,6 +31,7 @@ pom deployment + runtime diff --git a/extensions/kotlin/runtime/pom.xml b/extensions/kotlin/runtime/pom.xml new file mode 100644 index 0000000000000..53ad46804d434 --- /dev/null +++ b/extensions/kotlin/runtime/pom.xml @@ -0,0 +1,24 @@ + + + 4.0.0 + + + io.quarkus + quarkus-kotlin-parent + 999-SNAPSHOT + + + quarkus-kotlin + Quarkus - Kotlin - Runtime + + + + + io.quarkus + quarkus-bootstrap-maven-plugin + + + + \ No newline at end of file diff --git a/extensions/kubernetes/deployment/pom.xml b/extensions/kubernetes/deployment/pom.xml new file mode 100644 index 0000000000000..b52a51cc43a34 --- /dev/null +++ b/extensions/kubernetes/deployment/pom.xml @@ -0,0 +1,65 @@ + + + + + + quarkus-kubernetes-parent + io.quarkus + 999-SNAPSHOT + + 4.0.0 + + quarkus-kubernetes-deployment + Quarkus - Kubernetes - Deployment + + + + io.quarkus + quarkus-kubernetes-spi + + + io.ap4k + kubernetes-annotations + noapt + + + com.sun + tools + + + + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + diff --git a/extensions/kubernetes/deployment/src/main/java/io/quarkus/kubernetes/deployment/DockerConfig.java b/extensions/kubernetes/deployment/src/main/java/io/quarkus/kubernetes/deployment/DockerConfig.java new file mode 100644 index 0000000000000..1df46a06798fa --- /dev/null +++ b/extensions/kubernetes/deployment/src/main/java/io/quarkus/kubernetes/deployment/DockerConfig.java @@ -0,0 +1,12 @@ +package io.quarkus.kubernetes.deployment; + +import io.quarkus.runtime.annotations.ConfigGroup; + +@ConfigGroup +public class DockerConfig { + + /** + * The docker registry to which the images will be pushed + */ + public String registry = "docker.io"; +} diff --git a/extensions/kubernetes/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesConfig.java b/extensions/kubernetes/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesConfig.java new file mode 100644 index 0000000000000..342dc44a2ac0f --- /dev/null +++ b/extensions/kubernetes/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesConfig.java @@ -0,0 +1,24 @@ +package io.quarkus.kubernetes.deployment; + +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 KubernetesConfig { + + /** + * The group of the application. + * This value will be use as: + * - docker image repo + * - labeling resources + */ + @ConfigItem + public String group; + + /** + * Configuration that is relevant to docker images + */ + @ConfigItem + public DockerConfig docker; +} diff --git a/extensions/kubernetes/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesProcessor.java b/extensions/kubernetes/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesProcessor.java new file mode 100644 index 0000000000000..53353ace89978 --- /dev/null +++ b/extensions/kubernetes/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesProcessor.java @@ -0,0 +1,127 @@ +package io.quarkus.kubernetes.deployment; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.inject.Inject; + +import io.ap4k.Session; +import io.ap4k.SessionWriter; +import io.ap4k.kubernetes.annotation.KubernetesApplication; +import io.ap4k.kubernetes.generator.DefaultKubernetesApplicationGenerator; +import io.ap4k.processor.SimpleFileWriter; +import io.ap4k.project.Project; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.ApplicationInfoBuildItem; +import io.quarkus.deployment.builditem.FeatureBuildItem; +import io.quarkus.deployment.builditem.GeneratedResourceBuildItem; +import io.quarkus.kubernetes.spi.KubernetesPortBuildItem; + +class KubernetesProcessor { + + @Inject + BuildProducer generatedResourceProducer; + + @Inject + BuildProducer featureProducer; + + @BuildStep + public void build(ApplicationInfoBuildItem applicationInfo, + KubernetesConfig kubernetesConfig, + List kubernetesPortBuildItems) throws UnsupportedEncodingException { + + if (kubernetesPortBuildItems.isEmpty()) { + return; + } + + // The resources that ap4k's execution will result in, will later-on be written + // by quarkus in the 'wiring-classes' directory + // The location is needed in order to properly support s2i build triggering + + // by passing false to SimpleFileWriter, we ensure that no files are actually written during this phase + final Path root; + try { + root = Files.createTempDirectory("quarkus-kubernetes"); + } catch (IOException e) { + throw new RuntimeException("Unable to setup environment for generating Kubernetes resources", e); + } + + final SessionWriter sessionWriter = new SimpleFileWriter(root, false); + sessionWriter.setProject(new Project()); + final Session session = Session.getSession(); + session.setWriter(sessionWriter); + + final Map ports = verifyPorts(kubernetesPortBuildItems); + enableKubernetes(applicationInfo, kubernetesConfig, ports); + + // write the generated resources to the filesystem + final Map generatedResourcesMap = session.close(); + for (Map.Entry resourceEntry : generatedResourcesMap.entrySet()) { + generatedResourceProducer.produce( + new GeneratedResourceBuildItem( + // we need to make sure we are only passing the relative path to the build item + resourceEntry.getKey().replace(root.toAbsolutePath() + "/", "META-INF/kubernetes/"), + resourceEntry.getValue().getBytes("UTF-8"))); + } + + featureProducer.produce(new FeatureBuildItem(FeatureBuildItem.KUBERNETES)); + } + + private Map verifyPorts(List kubernetesPortBuildItems) { + final Map result = new HashMap<>(); + final Set usedPorts = new HashSet<>(); + for (KubernetesPortBuildItem entry : kubernetesPortBuildItems) { + final String name = entry.getName(); + if (result.containsKey(name)) { + throw new IllegalArgumentException( + "All Kubernetes ports must have unique names - " + name + "has been used multiple times"); + } + final Integer port = entry.getPort(); + if (usedPorts.contains(port)) { + throw new IllegalArgumentException( + "All Kubernetes ports must be unique - " + port + "has been used multiple times"); + } + result.put(name, port); + usedPorts.add(port); + } + return result; + } + + private void enableKubernetes(ApplicationInfoBuildItem applicationInfo, KubernetesConfig kubernetesConfig, + Map portsMap) { + final Map kubernetesProperties = new HashMap<>(); + kubernetesProperties.put("group", kubernetesConfig.group); + kubernetesProperties.put("name", applicationInfo.getName()); + kubernetesProperties.put("version", applicationInfo.getVersion()); + + final List> ports = new ArrayList<>(); + for (Map.Entry entry : portsMap.entrySet()) { + final Map portProperties = new HashMap<>(); + portProperties.put("name", entry.getKey()); + portProperties.put("containerPort", entry.getValue()); + ports.add(portProperties); + } + + kubernetesProperties.put("ports", toArray(ports)); + + final DefaultKubernetesApplicationGenerator generator = new DefaultKubernetesApplicationGenerator(); + final Map generatorInput = new HashMap<>(); + generatorInput.put(KubernetesApplication.class.getName(), kubernetesProperties); + generator.add(generatorInput); + } + + private T[] toArray(List list) { + Class clazz = list.get(0).getClass(); + T[] array = (T[]) java.lang.reflect.Array.newInstance(clazz, list.size()); + return list.toArray(array); + } +} diff --git a/extensions/kubernetes/pom.xml b/extensions/kubernetes/pom.xml new file mode 100644 index 0000000000000..cd2c12ff48f98 --- /dev/null +++ b/extensions/kubernetes/pom.xml @@ -0,0 +1,37 @@ + + + + + + quarkus-build-parent + io.quarkus + 999-SNAPSHOT + ../../build-parent/pom.xml + + 4.0.0 + + quarkus-kubernetes-parent + Quarkus - Kubernetes + pom + + deployment + runtime + spi + + diff --git a/extensions/kubernetes/runtime/pom.xml b/extensions/kubernetes/runtime/pom.xml new file mode 100644 index 0000000000000..e4298a772f397 --- /dev/null +++ b/extensions/kubernetes/runtime/pom.xml @@ -0,0 +1,38 @@ + + + 4.0.0 + + + io.quarkus + quarkus-kubernetes-parent + 999-SNAPSHOT + + + quarkus-kubernetes + Quarkus - Kubernetes - Runtime + + + + + 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/kubernetes/spi/pom.xml b/extensions/kubernetes/spi/pom.xml new file mode 100644 index 0000000000000..42e7d170422a4 --- /dev/null +++ b/extensions/kubernetes/spi/pom.xml @@ -0,0 +1,21 @@ + + + quarkus-kubernetes-parent + io.quarkus + 999-SNAPSHOT + + 4.0.0 + + quarkus-kubernetes-spi + Quarkus - Kubernetes - SPI + Extensions that provide Kubernetes native features should include this module and the corresponding BuildItems + + + + io.quarkus + quarkus-core-deployment + + + \ No newline at end of file diff --git a/extensions/kubernetes/spi/src/main/java/io/quarkus/kubernetes/spi/KubernetesPortBuildItem.java b/extensions/kubernetes/spi/src/main/java/io/quarkus/kubernetes/spi/KubernetesPortBuildItem.java new file mode 100644 index 0000000000000..be24eb9c2b52e --- /dev/null +++ b/extensions/kubernetes/spi/src/main/java/io/quarkus/kubernetes/spi/KubernetesPortBuildItem.java @@ -0,0 +1,22 @@ +package io.quarkus.kubernetes.spi; + +import io.quarkus.builder.item.MultiBuildItem; + +public final class KubernetesPortBuildItem extends MultiBuildItem { + + private final int port; + private final String name; + + public KubernetesPortBuildItem(int port, String name) { + this.port = port; + this.name = name; + } + + public int getPort() { + return port; + } + + public String getName() { + return name; + } +} diff --git a/extensions/narayana-jta/deployment/pom.xml b/extensions/narayana-jta/deployment/pom.xml index a1df971e1b81c..c1c64f7572e9d 100644 --- a/extensions/narayana-jta/deployment/pom.xml +++ b/extensions/narayana-jta/deployment/pom.xml @@ -26,21 +26,21 @@ 4.0.0 - quarkus-narayana-jta + quarkus-narayana-jta-deployment Quarkus - Narayana JTA - Deployment io.quarkus - quarkus-core + quarkus-core-deployment io.quarkus - quarkus-arc + quarkus-arc-deployment io.quarkus - quarkus-narayana-jta-runtime + quarkus-narayana-jta 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 1c494400573f4..2296aa14d9284 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 @@ -76,12 +76,14 @@ public void build(NarayanaJtaTemplate tt, BuildProducer featur TransactionManagerImple.class.getName(), TransactionSynchronizationRegistryImple.class.getName())); - additionalBeans.produce(new AdditionalBeanBuildItem(TransactionalInterceptorSupports.class)); - additionalBeans.produce(new AdditionalBeanBuildItem(TransactionalInterceptorNever.class)); - additionalBeans.produce(new AdditionalBeanBuildItem(TransactionalInterceptorRequired.class)); - additionalBeans.produce(new AdditionalBeanBuildItem(TransactionalInterceptorRequiresNew.class)); - additionalBeans.produce(new AdditionalBeanBuildItem(TransactionalInterceptorMandatory.class)); - additionalBeans.produce(new AdditionalBeanBuildItem(TransactionalInterceptorNotSupported.class)); + AdditionalBeanBuildItem.Builder builder = AdditionalBeanBuildItem.builder(); + builder.addBeanClass(TransactionalInterceptorSupports.class); + builder.addBeanClass(TransactionalInterceptorNever.class); + builder.addBeanClass(TransactionalInterceptorRequired.class); + builder.addBeanClass(TransactionalInterceptorRequiresNew.class); + builder.addBeanClass(TransactionalInterceptorMandatory.class); + builder.addBeanClass(TransactionalInterceptorNotSupported.class); + additionalBeans.produce(builder.build()); //we want to force Arjuna to init at static init time Properties defaultProperties = PropertiesFactory.getDefaultProperties(); diff --git a/extensions/narayana-jta/runtime/pom.xml b/extensions/narayana-jta/runtime/pom.xml index 03375ed3e24f9..86ba23490234c 100644 --- a/extensions/narayana-jta/runtime/pom.xml +++ b/extensions/narayana-jta/runtime/pom.xml @@ -26,17 +26,17 @@ 4.0.0 - quarkus-narayana-jta-runtime + quarkus-narayana-jta Quarkus - Narayana JTA - Runtime io.quarkus - quarkus-core-runtime + quarkus-core io.quarkus - quarkus-arc-runtime + quarkus-arc com.oracle.substratevm @@ -59,7 +59,8 @@ - maven-dependency-plugin + io.quarkus + quarkus-bootstrap-maven-plugin maven-compiler-plugin diff --git a/extensions/netty/deployment/pom.xml b/extensions/netty/deployment/pom.xml index fb7ab7ca3dba9..2df9849116efe 100644 --- a/extensions/netty/deployment/pom.xml +++ b/extensions/netty/deployment/pom.xml @@ -25,17 +25,21 @@ 4.0.0 - quarkus-netty + quarkus-netty-deployment Quarkus - Netty - Deployment io.quarkus - quarkus-core + quarkus-core-deployment io.quarkus - quarkus-netty-runtime + quarkus-netty + + + io.quarkus + quarkus-arc-deployment diff --git a/extensions/netty/deployment/src/main/java/io/quarkus/netty/deployment/NettyProcessor.java b/extensions/netty/deployment/src/main/java/io/quarkus/netty/deployment/NettyProcessor.java index 89bee01e328ab..0b5838a1dd3e7 100644 --- a/extensions/netty/deployment/src/main/java/io/quarkus/netty/deployment/NettyProcessor.java +++ b/extensions/netty/deployment/src/main/java/io/quarkus/netty/deployment/NettyProcessor.java @@ -16,14 +16,23 @@ package io.quarkus.netty.deployment; +import java.util.function.Supplier; + +import javax.enterprise.context.ApplicationScoped; import javax.inject.Inject; import org.jboss.logging.Logger; +import io.netty.channel.EventLoopGroup; +import io.quarkus.arc.deployment.RuntimeBeanBuildItem; 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.substrate.ReflectiveClassBuildItem; import io.quarkus.deployment.builditem.substrate.SubstrateConfigBuildItem; +import io.quarkus.netty.BossGroup; +import io.quarkus.netty.runtime.NettyTemplate; class NettyProcessor { @@ -59,4 +68,23 @@ SubstrateConfigBuildItem build() { .build(); } + @BuildStep + @Record(ExecutionTime.STATIC_INIT) + void createExecutors(BuildProducer runtimeBeanBuildItemBuildProducer, + NettyTemplate template) { + //TODO: configuration + Supplier boss = template.createEventLoop(1); + Supplier worker = template.createEventLoop(0); + + runtimeBeanBuildItemBuildProducer.produce(RuntimeBeanBuildItem.builder(EventLoopGroup.class) + .setSupplier(boss) + .setScope(ApplicationScoped.class) + .addQualifier(BossGroup.class) + .build()); + runtimeBeanBuildItemBuildProducer.produce(RuntimeBeanBuildItem.builder(EventLoopGroup.class) + .setSupplier(worker) + .setScope(ApplicationScoped.class) + .build()); + } + } diff --git a/extensions/netty/runtime/pom.xml b/extensions/netty/runtime/pom.xml index c8f0751336db5..9cec628095348 100644 --- a/extensions/netty/runtime/pom.xml +++ b/extensions/netty/runtime/pom.xml @@ -25,7 +25,7 @@ 4.0.0 - quarkus-netty-runtime + quarkus-netty Quarkus - Netty - Runtime @@ -38,23 +38,30 @@ io.netty netty-codec + + io.quarkus + quarkus-core + io.netty netty-handler + + javax.enterprise + cdi-api + com.oracle.substratevm svm - compile - - maven-dependency-plugin + io.quarkus + quarkus-bootstrap-maven-plugin maven-compiler-plugin diff --git a/extensions/netty/runtime/src/main/java/io/quarkus/netty/BossGroup.java b/extensions/netty/runtime/src/main/java/io/quarkus/netty/BossGroup.java new file mode 100644 index 0000000000000..aa7f58f1fe817 --- /dev/null +++ b/extensions/netty/runtime/src/main/java/io/quarkus/netty/BossGroup.java @@ -0,0 +1,11 @@ +package io.quarkus.netty; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.inject.Qualifier; + +@Qualifier +@Retention(RetentionPolicy.RUNTIME) +public @interface BossGroup { +} diff --git a/extensions/netty/runtime/src/main/java/io/quarkus/netty/runtime/NettyTemplate.java b/extensions/netty/runtime/src/main/java/io/quarkus/netty/runtime/NettyTemplate.java new file mode 100644 index 0000000000000..0b20396329441 --- /dev/null +++ b/extensions/netty/runtime/src/main/java/io/quarkus/netty/runtime/NettyTemplate.java @@ -0,0 +1,30 @@ +package io.quarkus.netty.runtime; + +import java.util.function.Supplier; + +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.quarkus.runtime.annotations.Template; + +@Template +public class NettyTemplate { + + public Supplier createEventLoop(int nThreads) { + return new Supplier() { + + volatile EventLoopGroup val; + + @Override + public EventLoopGroup get() { + if (val == null) { + synchronized (this) { + if (val == null) { + val = new NioEventLoopGroup(nThreads); + } + } + } + return val; + } + }; + } +} diff --git a/extensions/panache/hibernate-orm-panache/deployment/pom.xml b/extensions/panache/hibernate-orm-panache/deployment/pom.xml index 7cad072a684bb..e81bd8643372e 100644 --- a/extensions/panache/hibernate-orm-panache/deployment/pom.xml +++ b/extensions/panache/hibernate-orm-panache/deployment/pom.xml @@ -26,20 +26,20 @@ 4.0.0 - quarkus-hibernate-orm-panache + quarkus-hibernate-orm-panache-deployment Quarkus - Hibernate ORM with Panache - Deployment io.quarkus - quarkus-core + quarkus-core-deployment io.quarkus - quarkus-hibernate-orm + quarkus-hibernate-orm-deployment io.quarkus - quarkus-hibernate-orm-panache-runtime + quarkus-hibernate-orm-panache org.ow2.asm @@ -54,15 +54,10 @@ asm-analysis + - junit - junit - test - - - io.quarkus.gizmo - gizmo - test-jar + io.quarkus + quarkus-junit5-internal test diff --git a/extensions/panache/hibernate-orm-panache/deployment/src/main/java/io/quarkus/hibernate/orm/panache/deployment/PanacheJpaEntityEnhancer.java b/extensions/panache/hibernate-orm-panache/deployment/src/main/java/io/quarkus/hibernate/orm/panache/deployment/PanacheJpaEntityEnhancer.java index beb78ba9d3a92..5c30a43ff93cc 100644 --- a/extensions/panache/hibernate-orm-panache/deployment/src/main/java/io/quarkus/hibernate/orm/panache/deployment/PanacheJpaEntityEnhancer.java +++ b/extensions/panache/hibernate-orm-panache/deployment/src/main/java/io/quarkus/hibernate/orm/panache/deployment/PanacheJpaEntityEnhancer.java @@ -9,10 +9,13 @@ import javax.persistence.Transient; +import org.hibernate.bytecode.enhance.spi.EnhancerConstants; import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; import org.jboss.jandex.FieldInfo; +import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; @@ -46,6 +49,9 @@ public class PanacheJpaEntityEnhancer implements BiFunction entities = new HashMap<>(); @@ -71,6 +77,32 @@ public ModelEnhancingClassVisitor(String className, ClassVisitor outputClassVisi fields = entityModel != null ? entityModel.fields : null; } + @Override + public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) { + FieldVisitor superVisitor = super.visitField(access, name, descriptor, signature, value); + if (fields == null || !fields.containsKey(name)) + return superVisitor; + // if we have a mapped field, let's add some annotations + return new FieldVisitor(Opcodes.ASM6, superVisitor) { + private Set descriptors = new HashSet<>(); + + @Override + public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { + descriptors.add(descriptor); + return super.visitAnnotation(descriptor, visible); + } + + @Override + public void visitEnd() { + // add the @JaxbTransient property to the field so that Jackson prefers the generated getter + // jsonb will already use the getter so we're good + if (!descriptors.contains(JAXB_TRANSIENT_SIGNATURE)) + super.visitAnnotation(JAXB_TRANSIENT_SIGNATURE, true); + super.visitEnd(); + } + }; + } + @Override public MethodVisitor visitMethod(int access, String methodName, String descriptor, String signature, String[] exceptions) { @@ -312,7 +344,16 @@ private void generateAccessors() { getterName, getterDescriptor, null, null); mv.visitCode(); mv.visitIntInsn(Opcodes.ALOAD, 0); - mv.visitFieldInsn(Opcodes.GETFIELD, thisClass.getInternalName(), field.name, field.descriptor); + // Due to https://github.com/quarkusio/quarkus/issues/1376 we generate Hibernate read/write calls + // directly rather than rely on Hibernate to see our generated accessor because it does not + mv.visitMethodInsn( + Opcodes.INVOKEVIRTUAL, + thisClass.getInternalName(), + EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + field.name, + Type.getMethodDescriptor(Type.getType(field.descriptor)), + false); + // instead of: + // mv.visitFieldInsn(Opcodes.GETFIELD, thisClass.getInternalName(), field.name, field.descriptor); int returnCode; switch (field.descriptor) { case "Z": @@ -371,7 +412,16 @@ private void generateAccessors() { break; } mv.visitIntInsn(loadCode, 1); - mv.visitFieldInsn(Opcodes.PUTFIELD, thisClass.getInternalName(), field.name, field.descriptor); + // Due to https://github.com/quarkusio/quarkus/issues/1376 we generate Hibernate read/write calls + // directly rather than rely on Hibernate to see our generated accessor because it does not + mv.visitMethodInsn( + Opcodes.INVOKEVIRTUAL, + thisClass.getInternalName(), + EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX + field.name, + Type.getMethodDescriptor(Type.getType(void.class), Type.getType(field.descriptor)), + false); + // instead of: + // mv.visitFieldInsn(Opcodes.PUTFIELD, thisClass.getInternalName(), field.name, field.descriptor); mv.visitInsn(Opcodes.RETURN); mv.visitMaxs(0, 0); mv.visitEnd(); diff --git a/extensions/panache/hibernate-orm-panache/deployment/src/test/java/io/quarkus/hibernate/orm/panache/test/NoConfigTest.java b/extensions/panache/hibernate-orm-panache/deployment/src/test/java/io/quarkus/hibernate/orm/panache/test/NoConfigTest.java new file mode 100644 index 0000000000000..b5c290ed30370 --- /dev/null +++ b/extensions/panache/hibernate-orm-panache/deployment/src/test/java/io/quarkus/hibernate/orm/panache/test/NoConfigTest.java @@ -0,0 +1,20 @@ +package io.quarkus.hibernate.orm.panache.test; + +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 NoConfigTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest().setArchiveProducer( + () -> ShrinkWrap.create(JavaArchive.class)); + + @Test + public void testNoConfig() { + // we should be able to start the application, even with no configuration at all + } +} diff --git a/extensions/panache/hibernate-orm-panache/runtime/pom.xml b/extensions/panache/hibernate-orm-panache/runtime/pom.xml index f54ba5937ec4f..a0456d1b943f8 100644 --- a/extensions/panache/hibernate-orm-panache/runtime/pom.xml +++ b/extensions/panache/hibernate-orm-panache/runtime/pom.xml @@ -25,32 +25,51 @@ 4.0.0 - quarkus-hibernate-orm-panache-runtime + quarkus-hibernate-orm-panache Quarkus - Hibernate ORM with Panache - Runtime io.quarkus - quarkus-core-runtime + quarkus-core io.quarkus - quarkus-hibernate-orm-runtime + quarkus-hibernate-orm io.quarkus - quarkus-arc-runtime + quarkus-arc io.quarkus - quarkus-panache-common-runtime + quarkus-panache-common + + + javax.json.bind + javax.json.bind-api + true - maven-dependency-plugin + io.quarkus + quarkus-bootstrap-maven-plugin + + + + org.jboss.jandex + jandex-maven-plugin + + + make-index + + jandex + + + diff --git a/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/PanacheEntity.java b/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/PanacheEntity.java index 342a30f4a1ea9..dc24bba940c21 100644 --- a/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/PanacheEntity.java +++ b/extensions/panache/hibernate-orm-panache/runtime/src/main/java/io/quarkus/hibernate/orm/panache/PanacheEntity.java @@ -22,12 +22,12 @@ * @see PanacheEntityBase */ @MappedSuperclass -public class PanacheEntity extends PanacheEntityBase { +public abstract class PanacheEntity extends PanacheEntityBase { /** * The auto-generated ID field. This field is set by Hibernate ORM when this entity * is persisted. - * + * * @see #persist() */ @Id 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 b3fface00b656..b32ba03a83d5a 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 @@ -4,6 +4,7 @@ import java.util.Map; import java.util.stream.Stream; +import javax.json.bind.annotation.JsonbTransient; import javax.persistence.Transient; import io.quarkus.hibernate.orm.panache.runtime.JpaOperations; @@ -21,13 +22,13 @@ * @author Stéphane Épardaud * @see PanacheEntity */ -public class PanacheEntityBase { +public abstract class PanacheEntityBase { // Operations /** * Persist this entity in the database, if not already persisted. This will set your ID field if it is not already set. - * + * * @see #isPersistent() * @see #persist(Iterable) * @see #persist(Stream) @@ -39,7 +40,7 @@ public void persist() { /** * Delete this entity from the database, if it is already persisted. - * + * * @see #isPersistent() * @see #delete(String, Object...) * @see #delete(String, Map) @@ -54,9 +55,10 @@ public void delete() { * Returns true if this entity is persistent in the database. If yes, all modifications to * its persistent fields will be automatically committed to the database at transaction * commit time. - * + * * @return true if this entity is persistent in the database. */ + @JsonbTransient public boolean isPersistent() { return JpaOperations.isPersistent(this); } @@ -65,7 +67,7 @@ public boolean isPersistent() { /** * Find an entity of this type by ID. - * + * * @param id the ID of the entity to find. * @return the entity found, or null if not found. */ @@ -75,7 +77,7 @@ public static T findById(Object id) { /** * Find entities using a query, with optional indexed parameters. - * + * * @param query a {@link io.quarkus.hibernate.orm.panache query string} * @param params optional sequence of indexed parameters * @return a new {@link PanacheQuery} instance for the given query @@ -91,7 +93,7 @@ public static PanacheQuery find(String query, O /** * Find entities using a query and the given sort options, with optional indexed parameters. - * + * * @param query a {@link io.quarkus.hibernate.orm.panache query string} * @param sort the sort strategy to use * @param params optional sequence of indexed parameters @@ -108,7 +110,7 @@ public static PanacheQuery find(String query, S /** * Find entities using a query, with named parameters. - * + * * @param query a {@link io.quarkus.hibernate.orm.panache query string} * @param params {@link Map} of named parameters * @return a new {@link PanacheQuery} instance for the given query @@ -124,7 +126,7 @@ public static PanacheQuery find(String query, M /** * Find entities using a query and the given sort options, with named parameters. - * + * * @param query a {@link io.quarkus.hibernate.orm.panache query string} * @param sort the sort strategy to use * @param params {@link Map} of indexed parameters @@ -141,7 +143,7 @@ public static PanacheQuery find(String query, S /** * Find entities using a query, with named parameters. - * + * * @param query a {@link io.quarkus.hibernate.orm.panache query string} * @param params {@link Parameters} of named parameters * @return a new {@link PanacheQuery} instance for the given query @@ -157,7 +159,7 @@ public static PanacheQuery find(String query, P /** * Find entities using a query and the given sort options, with named parameters. - * + * * @param query a {@link io.quarkus.hibernate.orm.panache query string} * @param sort the sort strategy to use * @param params {@link Parameters} of indexed parameters @@ -174,7 +176,7 @@ public static PanacheQuery find(String query, S /** * Find all entities of this type. - * + * * @return a new {@link PanacheQuery} instance to find all entities of this type. * @see #findAll(Sort) * @see #listAll() @@ -186,7 +188,7 @@ public static PanacheQuery findAll() { /** * Find all entities of this type, in the given order. - * + * * @param sort the sort order to use * @return a new {@link PanacheQuery} instance to find all entities of this type. * @see #findAll() @@ -200,7 +202,7 @@ public static PanacheQuery findAll(Sort sort) { /** * Find entities matching a query, with optional indexed parameters. * This method is a shortcut for find(query, params).list(). - * + * * @param query a {@link io.quarkus.hibernate.orm.panache query string} * @param params optional sequence of indexed parameters * @return a {@link List} containing all results, without paging @@ -217,7 +219,7 @@ public static List list(String query, Object... /** * Find entities matching a query and the given sort options, with optional indexed parameters. * This method is a shortcut for find(query, sort, params).list(). - * + * * @param query a {@link io.quarkus.hibernate.orm.panache query string} * @param sort the sort strategy to use * @param params optional sequence of indexed parameters @@ -235,7 +237,7 @@ public static List list(String query, Sort sort /** * Find entities matching a query, with named parameters. * This method is a shortcut for find(query, params).list(). - * + * * @param query a {@link io.quarkus.hibernate.orm.panache query string} * @param params {@link Map} of named parameters * @return a {@link List} containing all results, without paging @@ -252,7 +254,7 @@ public static List list(String query, Mapfind(query, sort, params).list(). - * + * * @param query a {@link io.quarkus.hibernate.orm.panache query string} * @param sort the sort strategy to use * @param params {@link Map} of indexed parameters @@ -270,7 +272,7 @@ public static List list(String query, Sort sort /** * Find entities matching a query, with named parameters. * This method is a shortcut for find(query, params).list(). - * + * * @param query a {@link io.quarkus.hibernate.orm.panache query string} * @param params {@link Parameters} of named parameters * @return a {@link List} containing all results, without paging @@ -287,7 +289,7 @@ public static List list(String query, Parameter /** * Find entities matching a query and the given sort options, with named parameters. * This method is a shortcut for find(query, sort, params).list(). - * + * * @param query a {@link io.quarkus.hibernate.orm.panache query string} * @param sort the sort strategy to use * @param params {@link Parameters} of indexed parameters @@ -305,7 +307,7 @@ public static List list(String query, Sort sort /** * Find all entities of this type. * This method is a shortcut for findAll().list(). - * + * * @return a {@link List} containing all results, without paging * @see #listAll(Sort) * @see #findAll() @@ -318,7 +320,7 @@ public static List listAll() { /** * Find all entities of this type, in the given order. * This method is a shortcut for findAll(sort).list(). - * + * * @param sort the sort order to use * @return a {@link List} containing all results, without paging * @see #listAll() @@ -332,7 +334,7 @@ public static List listAll(Sort sort) { /** * Find entities matching a query, with optional indexed parameters. * This method is a shortcut for find(query, params).stream(). - * + * * @param query a {@link io.quarkus.hibernate.orm.panache query string} * @param params optional sequence of indexed parameters * @return a {@link Stream} containing all results, without paging @@ -349,7 +351,7 @@ public static Stream stream(String query, Objec /** * Find entities matching a query and the given sort options, with optional indexed parameters. * This method is a shortcut for find(query, sort, params).stream(). - * + * * @param query a {@link io.quarkus.hibernate.orm.panache query string} * @param sort the sort strategy to use * @param params optional sequence of indexed parameters @@ -367,7 +369,7 @@ public static Stream stream(String query, Sort /** * Find entities matching a query, with named parameters. * This method is a shortcut for find(query, params).stream(). - * + * * @param query a {@link io.quarkus.hibernate.orm.panache query string} * @param params {@link Map} of named parameters * @return a {@link Stream} containing all results, without paging @@ -384,7 +386,7 @@ public static Stream stream(String query, Mapfind(query, sort, params).stream(). - * + * * @param query a {@link io.quarkus.hibernate.orm.panache query string} * @param sort the sort strategy to use * @param params {@link Map} of indexed parameters @@ -402,7 +404,7 @@ public static Stream stream(String query, Sort /** * Find entities matching a query, with named parameters. * This method is a shortcut for find(query, params).stream(). - * + * * @param query a {@link io.quarkus.hibernate.orm.panache query string} * @param params {@link Parameters} of named parameters * @return a {@link Stream} containing all results, without paging @@ -419,7 +421,7 @@ public static Stream stream(String query, Param /** * Find entities matching a query and the given sort options, with named parameters. * This method is a shortcut for find(query, sort, params).stream(). - * + * * @param query a {@link io.quarkus.hibernate.orm.panache query string} * @param sort the sort strategy to use * @param params {@link Parameters} of indexed parameters @@ -437,7 +439,7 @@ public static Stream stream(String query, Sort /** * Find all entities of this type. * This method is a shortcut for findAll().stream(). - * + * * @return a {@link Stream} containing all results, without paging * @see #streamAll(Sort) * @see #findAll() @@ -450,7 +452,7 @@ public static Stream streamAll() { /** * Find all entities of this type, in the given order. * This method is a shortcut for findAll(sort).stream(). - * + * * @param sort the sort order to use * @return a {@link Stream} containing all results, without paging * @see #streamAll() @@ -463,7 +465,7 @@ public static Stream streamAll(Sort sort) { /** * Counts the number of this type of entity in the database. - * + * * @return the number of this type of entity in the database. * @see #count(String, Object...) * @see #count(String, Map) @@ -475,7 +477,7 @@ public static long count() { /** * Counts the number of this type of entity 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 counted. @@ -489,7 +491,7 @@ public static long count(String query, Object... params) { /** * Counts the number of this type of entity 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 counted. @@ -503,7 +505,7 @@ public static long count(String query, Map params) { /** * Counts the number of this type of entity 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 counted. @@ -517,7 +519,7 @@ public static long count(String query, Parameters params) { /** * Delete all entities of this type from the database. - * + * * @return the number of entities deleted. * @see #delete(String, Object...) * @see #delete(String, Map) @@ -529,7 +531,7 @@ public static long deleteAll() { /** * Delete 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 deleted. @@ -543,7 +545,7 @@ public static long delete(String query, Object... params) { /** * Delete 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 deleted. @@ -557,7 +559,7 @@ public static long delete(String query, Map params) { /** * Delete 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 deleted. @@ -571,7 +573,7 @@ public static long delete(String query, Parameters params) { /** * Persist all given entities. - * + * * @param entities the entities to persist * @see #persist() * @see #persist(Stream) @@ -583,7 +585,7 @@ public static void persist(Iterable entities) { /** * Persist all given entities. - * + * * @param entities the entities to persist * @see #persist() * @see #persist(Iterable) @@ -595,7 +597,7 @@ public static void persist(Stream entities) { /** * Persist all given entities. - * + * * @param entities the entities to persist * @see #persist() * @see #persist(Stream) 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 96ed266a3b8bc..2ff74a2aa6465 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 @@ -327,7 +327,7 @@ public static long delete(Class entityClass, String query, Parameters params) public static IllegalStateException implementationInjectionMissing() { return new IllegalStateException( - "This method is normally automatically overridden in subclasses: did you forget to annotated your entity with @Entity?"); + "This method is normally automatically overridden in subclasses: did you forget to annotate your entity with @Entity?"); } public static int executeUpdate(String query, Object... params) { diff --git a/extensions/panache/panache-common/runtime/pom.xml b/extensions/panache/panache-common/runtime/pom.xml index 96a6c3569899b..73e1d0adbb860 100644 --- a/extensions/panache/panache-common/runtime/pom.xml +++ b/extensions/panache/panache-common/runtime/pom.xml @@ -25,33 +25,22 @@ 4.0.0 - quarkus-panache-common-runtime + quarkus-panache-common Quarkus - Panache - Common - Runtime io.quarkus - quarkus-core-runtime + quarkus-core io.quarkus - quarkus-vertx-runtime + quarkus-hibernate-orm io.quarkus - quarkus-hibernate-orm-runtime - - - io.quarkus - quarkus-arc-runtime + quarkus-arc - - - - maven-dependency-plugin - - - diff --git a/extensions/pom.xml b/extensions/pom.xml index 14eacc9a26c88..75b8d72676c4d 100644 --- a/extensions/pom.xml +++ b/extensions/pom.xml @@ -33,7 +33,11 @@ arc scheduler + + jaxb + jsonp + jsonb undertow @@ -48,17 +52,22 @@ resteasy-common + resteasy-server-common resteasy resteasy-jsonb smallrye-rest-client + smallrye-openapi-common smallrye-openapi + swagger-ui vertx + vertx-web netty reactive-streams-operators smallrye-reactive-messaging smallrye-reactive-messaging-kafka + reactive-pg-client narayana-jta @@ -67,6 +76,8 @@ hibernate-orm hibernate-validator panache + hibernate-search-elasticsearch + elasticsearch-rest-client kafka-client @@ -75,6 +86,7 @@ elytron-security smallrye-jwt + keycloak infinispan-client @@ -82,11 +94,21 @@ caffeine + amazon-lambda + amazon-lambda-resteasy + camel + kotlin + + + kubernetes + + + flyway diff --git a/extensions/reactive-pg-client/deployment/pom.xml b/extensions/reactive-pg-client/deployment/pom.xml new file mode 100644 index 0000000000000..11f678fb7cba4 --- /dev/null +++ b/extensions/reactive-pg-client/deployment/pom.xml @@ -0,0 +1,68 @@ + + + + + 4.0.0 + + + io.quarkus + quarkus-reactive-pg-client-parent + 999-SNAPSHOT + ../ + + + quarkus-reactive-pg-client-deployment + + Quarkus - Reactive Postgres Client - Deployment + + + + io.quarkus + quarkus-vertx-deployment + + + io.quarkus + quarkus-reactive-pg-client + ${project.version} + + + + io.quarkus + quarkus-junit5-internal + + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + + diff --git a/extensions/reactive-pg-client/deployment/src/main/java/io/quarkus/reactive/pg/client/deployment/PgPoolBuildItem.java b/extensions/reactive-pg-client/deployment/src/main/java/io/quarkus/reactive/pg/client/deployment/PgPoolBuildItem.java new file mode 100644 index 0000000000000..731985a8c29ba --- /dev/null +++ b/extensions/reactive-pg-client/deployment/src/main/java/io/quarkus/reactive/pg/client/deployment/PgPoolBuildItem.java @@ -0,0 +1,35 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Red Hat 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.reactive.pg.client.deployment; + +import io.quarkus.builder.item.SimpleBuildItem; +import io.quarkus.runtime.RuntimeValue; +import io.reactiverse.pgclient.PgPool; + +public final class PgPoolBuildItem extends SimpleBuildItem { + + private final RuntimeValue pgPool; + + public PgPoolBuildItem(RuntimeValue pgPool) { + this.pgPool = pgPool; + } + + public RuntimeValue getPgPool() { + return pgPool; + } + +} diff --git a/extensions/reactive-pg-client/deployment/src/main/java/io/quarkus/reactive/pg/client/deployment/ReactivePgClientProcessor.java b/extensions/reactive-pg-client/deployment/src/main/java/io/quarkus/reactive/pg/client/deployment/ReactivePgClientProcessor.java new file mode 100644 index 0000000000000..240bafffa37d8 --- /dev/null +++ b/extensions/reactive-pg-client/deployment/src/main/java/io/quarkus/reactive/pg/client/deployment/ReactivePgClientProcessor.java @@ -0,0 +1,60 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Red Hat 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.reactive.pg.client.deployment; + +import org.jboss.logging.Logger; + +import io.quarkus.arc.deployment.AdditionalBeanBuildItem; +import io.quarkus.arc.deployment.BeanContainerBuildItem; +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.LaunchModeBuildItem; +import io.quarkus.deployment.builditem.ShutdownContextBuildItem; +import io.quarkus.reactive.pg.client.runtime.DataSourceConfig; +import io.quarkus.reactive.pg.client.runtime.PgPoolConfig; +import io.quarkus.reactive.pg.client.runtime.PgPoolProducer; +import io.quarkus.reactive.pg.client.runtime.PgPoolTemplate; +import io.quarkus.runtime.RuntimeValue; +import io.quarkus.vertx.deployment.VertxBuildItem; +import io.reactiverse.pgclient.PgPool; + +class ReactivePgClientProcessor { + + private static final Logger LOGGER = Logger.getLogger(ReactivePgClientProcessor.class.getName()); + + @BuildStep + AdditionalBeanBuildItem registerBean() { + return AdditionalBeanBuildItem.unremovableOf(PgPoolProducer.class); + } + + @BuildStep + @Record(ExecutionTime.RUNTIME_INIT) + PgPoolBuildItem build(BuildProducer feature, PgPoolTemplate template, VertxBuildItem vertx, + BeanContainerBuildItem beanContainer, LaunchModeBuildItem launchMode, ShutdownContextBuildItem shutdown, + DataSourceConfig dataSourceConfig, PgPoolConfig pgPoolConfig) { + + feature.produce(new FeatureBuildItem(FeatureBuildItem.REACTIVE_PG_CLIENT)); + + RuntimeValue pgPool = template.configurePgPool(vertx.getVertx(), beanContainer.getValue(), dataSourceConfig, + pgPoolConfig, launchMode.getLaunchMode(), shutdown); + + return new PgPoolBuildItem(pgPool); + } +} diff --git a/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/PgPoolProducerTest.java b/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/PgPoolProducerTest.java new file mode 100644 index 0000000000000..af4dbcc3f7d84 --- /dev/null +++ b/extensions/reactive-pg-client/deployment/src/test/java/io/quarkus/reactive/pg/client/PgPoolProducerTest.java @@ -0,0 +1,103 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Red Hat 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.reactive.pg.client; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + +import javax.enterprise.context.ApplicationScoped; +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.test.QuarkusUnitTest; +import io.reactiverse.pgclient.PgPool; + +public class PgPoolProducerTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(BeanUsingBarePgClient.class) + .addClasses(BeanUsingAxlePgClient.class) + .addClasses(BeanUsingRXPgClient.class)); + + @Inject + BeanUsingBarePgClient beanUsingBare; + + @Inject + BeanUsingAxlePgClient beanUsingAxle; + + @Inject + BeanUsingRXPgClient beanUsingRx; + + @Test + public void testVertxInjection() throws Exception { + beanUsingBare.verify() + .thenCompose(v -> beanUsingAxle.verify()) + .thenCompose(v -> beanUsingRx.verify()) + .toCompletableFuture() + .join(); + } + + @ApplicationScoped + static class BeanUsingBarePgClient { + + @Inject + PgPool pgClient; + + public CompletionStage verify() { + CompletableFuture cf = new CompletableFuture<>(); + pgClient.query("SELECT 1", ar -> { + cf.complete(null); + }); + return cf; + } + } + + @ApplicationScoped + static class BeanUsingAxlePgClient { + + @Inject + io.reactiverse.axle.pgclient.PgPool pgClient; + + public CompletionStage verify() { + return pgClient.query("SELECT 1") + . thenApply(rs -> null) + .exceptionally(t -> null); + } + } + + @ApplicationScoped + static class BeanUsingRXPgClient { + + @Inject + io.reactiverse.reactivex.pgclient.PgPool pgClient; + + public CompletionStage verify() { + CompletableFuture cf = new CompletableFuture<>(); + pgClient.rxQuery("SELECT 1") + .ignoreElement() + .onErrorComplete() + .subscribe(() -> cf.complete(null)); + return cf; + } + } +} diff --git a/extensions/reactive-pg-client/pom.xml b/extensions/reactive-pg-client/pom.xml new file mode 100644 index 0000000000000..0c43081087988 --- /dev/null +++ b/extensions/reactive-pg-client/pom.xml @@ -0,0 +1,41 @@ + + + + + 4.0.0 + + + quarkus-build-parent + io.quarkus + 999-SNAPSHOT + ../../build-parent/pom.xml + + + quarkus-reactive-pg-client-parent + pom + + Quarkus - Reactive Postgres Client + + + deployment + runtime + + + + \ No newline at end of file diff --git a/extensions/reactive-pg-client/runtime/pom.xml b/extensions/reactive-pg-client/runtime/pom.xml new file mode 100644 index 0000000000000..8cbe64d99dde3 --- /dev/null +++ b/extensions/reactive-pg-client/runtime/pom.xml @@ -0,0 +1,69 @@ + + + + + 4.0.0 + + + io.quarkus + quarkus-reactive-pg-client-parent + 999-SNAPSHOT + ../ + + + quarkus-reactive-pg-client + + Quarkus - Reactive Postgres Client - Runtime + + + + io.quarkus + quarkus-vertx + + + io.reactiverse + reactive-pg-client + + + io.smallrye.reactive + smallrye-axle-postgres-client + + + + + + + io.quarkus + quarkus-bootstrap-maven-plugin + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + diff --git a/extensions/reactive-pg-client/runtime/src/main/java/io/quarkus/reactive/pg/client/runtime/DataSourceConfig.java b/extensions/reactive-pg-client/runtime/src/main/java/io/quarkus/reactive/pg/client/runtime/DataSourceConfig.java new file mode 100644 index 0000000000000..84830accb61d1 --- /dev/null +++ b/extensions/reactive-pg-client/runtime/src/main/java/io/quarkus/reactive/pg/client/runtime/DataSourceConfig.java @@ -0,0 +1,52 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Red Hat 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.reactive.pg.client.runtime; + +import java.util.Optional; +import java.util.OptionalInt; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; + +@ConfigRoot(name = "datasource", phase = ConfigPhase.RUN_TIME) +public class DataSourceConfig { + + /** + * The datasource URL. + */ + @ConfigItem + public Optional url; + + /** + * The datasource username. + */ + @ConfigItem + public Optional username; + + /** + * The datasource password. + */ + @ConfigItem + public Optional password; + + /** + * The datasource pool maximum size. + */ + @ConfigItem + public OptionalInt maxSize; +} diff --git a/extensions/reactive-pg-client/runtime/src/main/java/io/quarkus/reactive/pg/client/runtime/PgPoolConfig.java b/extensions/reactive-pg-client/runtime/src/main/java/io/quarkus/reactive/pg/client/runtime/PgPoolConfig.java new file mode 100644 index 0000000000000..5c5fef46da0b3 --- /dev/null +++ b/extensions/reactive-pg-client/runtime/src/main/java/io/quarkus/reactive/pg/client/runtime/PgPoolConfig.java @@ -0,0 +1,40 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Red Hat 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.reactive.pg.client.runtime; + +import java.util.Optional; +import java.util.OptionalInt; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; + +@ConfigRoot(name = "reactive-pg-client", phase = ConfigPhase.RUN_TIME) +public class PgPoolConfig { + + /** + * Whether prepared statements should be cached on the client side. + */ + @ConfigItem + public Optional cachePreparedStatements; + + /** + * The maximum number of inflight database commands that can be pipelined. + */ + @ConfigItem + public OptionalInt pipeliningLimit; +} diff --git a/extensions/reactive-pg-client/runtime/src/main/java/io/quarkus/reactive/pg/client/runtime/PgPoolProducer.java b/extensions/reactive-pg-client/runtime/src/main/java/io/quarkus/reactive/pg/client/runtime/PgPoolProducer.java new file mode 100644 index 0000000000000..15aea267a9e76 --- /dev/null +++ b/extensions/reactive-pg-client/runtime/src/main/java/io/quarkus/reactive/pg/client/runtime/PgPoolProducer.java @@ -0,0 +1,55 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Red Hat 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.reactive.pg.client.runtime; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Produces; +import javax.inject.Singleton; + +import io.reactiverse.pgclient.PgPool; + +@ApplicationScoped +public class PgPoolProducer { + + private volatile PgPool pgPool; + private volatile io.reactiverse.axle.pgclient.PgPool axlePgPool; + private volatile io.reactiverse.reactivex.pgclient.PgPool rxPgPool; + + void initialize(PgPool pgPool) { + this.pgPool = pgPool; + this.axlePgPool = io.reactiverse.axle.pgclient.PgPool.newInstance(pgPool); + this.rxPgPool = io.reactiverse.reactivex.pgclient.PgPool.newInstance(pgPool); + } + + @Singleton + @Produces + public PgPool pgPool() { + return pgPool; + } + + @Singleton + @Produces + public io.reactiverse.axle.pgclient.PgPool axlePgPool() { + return axlePgPool; + } + + @Singleton + @Produces + public io.reactiverse.reactivex.pgclient.PgPool rxPgPool() { + return rxPgPool; + } +} diff --git a/extensions/reactive-pg-client/runtime/src/main/java/io/quarkus/reactive/pg/client/runtime/PgPoolTemplate.java b/extensions/reactive-pg-client/runtime/src/main/java/io/quarkus/reactive/pg/client/runtime/PgPoolTemplate.java new file mode 100644 index 0000000000000..18bab9bb2ad9c --- /dev/null +++ b/extensions/reactive-pg-client/runtime/src/main/java/io/quarkus/reactive/pg/client/runtime/PgPoolTemplate.java @@ -0,0 +1,90 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Red Hat 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.reactive.pg.client.runtime; + +import io.quarkus.arc.runtime.BeanContainer; +import io.quarkus.runtime.LaunchMode; +import io.quarkus.runtime.RuntimeValue; +import io.quarkus.runtime.ShutdownContext; +import io.quarkus.runtime.annotations.Template; +import io.reactiverse.pgclient.PgClient; +import io.reactiverse.pgclient.PgPool; +import io.reactiverse.pgclient.PgPoolOptions; +import io.vertx.core.Vertx; + +@Template +public class PgPoolTemplate { + + // Visible for testing + static volatile PgPool pgPool; + + public RuntimeValue configurePgPool(RuntimeValue vertx, BeanContainer container, + DataSourceConfig dataSourceConfig, PgPoolConfig pgPoolConfig, LaunchMode launchMode, ShutdownContext shutdown) { + + initialize(vertx.getValue(), dataSourceConfig, pgPoolConfig); + + PgPoolProducer producer = container.instance(PgPoolProducer.class); + producer.initialize(pgPool); + + if (!launchMode.isDevOrTest()) { + shutdown.addShutdownTask(this::destroy); + } + return new RuntimeValue<>(pgPool); + } + + // Visible for testing + void initialize(Vertx vertx, DataSourceConfig dataSourceConfig, PgPoolConfig pgPoolConfig) { + if (pgPool != null) { + return; + } + PgPoolOptions pgPoolOptions = toPgPoolOptions(dataSourceConfig, pgPoolConfig); + pgPool = PgClient.pool(vertx, pgPoolOptions); + } + + private PgPoolOptions toPgPoolOptions(DataSourceConfig dataSourceConfig, PgPoolConfig pgPoolConfig) { + PgPoolOptions pgPoolOptions; + if (dataSourceConfig != null) { + + pgPoolOptions = dataSourceConfig.url + .filter(s -> s.matches("^vertx-reactive:postgre(?:s|sql)://.*$")) + .map(s -> s.substring("vertx-reactive:".length())) + .map(PgPoolOptions::fromUri) + .orElse(new PgPoolOptions()); + + dataSourceConfig.username.ifPresent(value -> pgPoolOptions.setUser(value)); + dataSourceConfig.password.ifPresent(value -> pgPoolOptions.setPassword(value)); + dataSourceConfig.maxSize.ifPresent(value -> pgPoolOptions.setMaxSize(value)); + + } else { + pgPoolOptions = new PgPoolOptions(); + } + + if (pgPoolConfig != null) { + pgPoolConfig.cachePreparedStatements.ifPresent(value -> pgPoolOptions.setCachePreparedStatements(value)); + pgPoolConfig.pipeliningLimit.ifPresent(value -> pgPoolOptions.setPipeliningLimit(value)); + } + + return pgPoolOptions; + } + + // Visible for testing + void destroy() { + if (pgPool != null) { + pgPool.close(); + } + } +} diff --git a/extensions/reactive-streams-operators/smallrye-reactive-streams-operators/deployment/pom.xml b/extensions/reactive-streams-operators/smallrye-reactive-streams-operators/deployment/pom.xml index b39a8d54f3f22..37994d9398bda 100644 --- a/extensions/reactive-streams-operators/smallrye-reactive-streams-operators/deployment/pom.xml +++ b/extensions/reactive-streams-operators/smallrye-reactive-streams-operators/deployment/pom.xml @@ -25,17 +25,17 @@ 4.0.0 - quarkus-smallrye-reactive-streams-operators + quarkus-smallrye-reactive-streams-operators-deployment Quarkus - SmallRye Reactive Streams Operators - Deployment io.quarkus - quarkus-core + quarkus-core-deployment io.quarkus - quarkus-smallrye-reactive-streams-operators-runtime + quarkus-smallrye-reactive-streams-operators diff --git a/extensions/reactive-streams-operators/smallrye-reactive-streams-operators/runtime/pom.xml b/extensions/reactive-streams-operators/smallrye-reactive-streams-operators/runtime/pom.xml index 1a7ad330dcf4a..6191db071679a 100644 --- a/extensions/reactive-streams-operators/smallrye-reactive-streams-operators/runtime/pom.xml +++ b/extensions/reactive-streams-operators/smallrye-reactive-streams-operators/runtime/pom.xml @@ -25,7 +25,7 @@ 4.0.0 - quarkus-smallrye-reactive-streams-operators-runtime + quarkus-smallrye-reactive-streams-operators Quarkus - SmallRye Reactive Streams Operators - Runtime @@ -44,10 +44,10 @@ - - maven-dependency-plugin + io.quarkus + quarkus-bootstrap-maven-plugin maven-compiler-plugin diff --git a/extensions/reactive-streams-operators/smallrye-reactive-type-converters/deployment/pom.xml b/extensions/reactive-streams-operators/smallrye-reactive-type-converters/deployment/pom.xml index 8f649909da25e..e557102493ca2 100644 --- a/extensions/reactive-streams-operators/smallrye-reactive-type-converters/deployment/pom.xml +++ b/extensions/reactive-streams-operators/smallrye-reactive-type-converters/deployment/pom.xml @@ -10,17 +10,17 @@ 999-SNAPSHOT - quarkus-smallrye-reactive-type-converters + quarkus-smallrye-reactive-type-converters-deployment Quarkus - SmallRye Reactive Type Converters - Deployment io.quarkus - quarkus-core + quarkus-core-deployment io.quarkus - quarkus-smallrye-reactive-type-converters-runtime + quarkus-smallrye-reactive-type-converters diff --git a/extensions/reactive-streams-operators/smallrye-reactive-type-converters/runtime/pom.xml b/extensions/reactive-streams-operators/smallrye-reactive-type-converters/runtime/pom.xml index a34b0bdefd0c0..b4cfbe6a0b1d8 100644 --- a/extensions/reactive-streams-operators/smallrye-reactive-type-converters/runtime/pom.xml +++ b/extensions/reactive-streams-operators/smallrye-reactive-type-converters/runtime/pom.xml @@ -25,23 +25,22 @@ 999-SNAPSHOT - quarkus-smallrye-reactive-type-converters-runtime + quarkus-smallrye-reactive-type-converters Quarkus - SmallRye Reactive Type Converters - Runtime io.smallrye.reactive - smallrye-converter-api + smallrye-reactive-converter-api - - maven-dependency-plugin + io.quarkus + quarkus-bootstrap-maven-plugin - diff --git a/extensions/resteasy-common/deployment/pom.xml b/extensions/resteasy-common/deployment/pom.xml index 2a4d6d43c0446..479b3ae728fae 100644 --- a/extensions/resteasy-common/deployment/pom.xml +++ b/extensions/resteasy-common/deployment/pom.xml @@ -26,13 +26,17 @@ 4.0.0 - quarkus-resteasy-common + quarkus-resteasy-common-deployment Quarkus - RESTEasy - Common - Deployment io.quarkus - quarkus-resteasy-common-runtime + quarkus-core-deployment + + + io.quarkus + quarkus-resteasy-common com.oracle.substratevm diff --git a/extensions/resteasy-common/deployment/src/main/java/io/quarkus/resteasy/common/deployment/JaxrsProvidersToRegisterBuildItem.java b/extensions/resteasy-common/deployment/src/main/java/io/quarkus/resteasy/common/deployment/JaxrsProvidersToRegisterBuildItem.java new file mode 100644 index 0000000000000..183e4892ebdc0 --- /dev/null +++ b/extensions/resteasy-common/deployment/src/main/java/io/quarkus/resteasy/common/deployment/JaxrsProvidersToRegisterBuildItem.java @@ -0,0 +1,30 @@ +package io.quarkus.resteasy.common.deployment; + +import java.util.Set; + +import io.quarkus.builder.item.SimpleBuildItem; + +public final class JaxrsProvidersToRegisterBuildItem extends SimpleBuildItem { + + private final Set providers; + private final Set contributedProviders; + private final boolean useBuiltIn; + + public JaxrsProvidersToRegisterBuildItem(Set providers, Set contributedProviders, boolean useBuiltIn) { + this.providers = providers; + this.contributedProviders = contributedProviders; + this.useBuiltIn = useBuiltIn; + } + + public Set getProviders() { + return this.providers; + } + + public Set getContributedProviders() { + return this.contributedProviders; + } + + public boolean useBuiltIn() { + return useBuiltIn; + } +} diff --git a/extensions/resteasy-common/deployment/src/main/java/io/quarkus/resteasy/common/deployment/ResteasyCommonProcessor.java b/extensions/resteasy-common/deployment/src/main/java/io/quarkus/resteasy/common/deployment/ResteasyCommonProcessor.java new file mode 100644 index 0000000000000..48166e4738f8c --- /dev/null +++ b/extensions/resteasy-common/deployment/src/main/java/io/quarkus/resteasy/common/deployment/ResteasyCommonProcessor.java @@ -0,0 +1,268 @@ +package io.quarkus.resteasy.common.deployment; + +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.OptionalInt; +import java.util.Set; + +import javax.ws.rs.Consumes; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.ext.*; + +import org.jboss.jandex.*; +import org.jboss.resteasy.core.MediaTypeMap; +import org.jboss.resteasy.plugins.interceptors.AcceptEncodingGZIPFilter; +import org.jboss.resteasy.plugins.interceptors.GZIPDecodingInterceptor; +import org.jboss.resteasy.plugins.interceptors.GZIPEncodingInterceptor; + +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.CombinedIndexBuildItem; +import io.quarkus.deployment.builditem.substrate.ReflectiveClassBuildItem; +import io.quarkus.deployment.util.ServiceUtil; +import io.quarkus.runtime.annotations.ConfigGroup; +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigRoot; + +public class ResteasyCommonProcessor { + + private static final ProviderDiscoverer[] PROVIDER_DISCOVERERS = { + new ProviderDiscoverer(ResteasyDotNames.GET, false, true), + new ProviderDiscoverer(ResteasyDotNames.HEAD, false, false), + new ProviderDiscoverer(ResteasyDotNames.DELETE, true, false), + new ProviderDiscoverer(ResteasyDotNames.OPTIONS, false, true), + new ProviderDiscoverer(ResteasyDotNames.PATCH, true, false), + new ProviderDiscoverer(ResteasyDotNames.POST, true, true), + new ProviderDiscoverer(ResteasyDotNames.PUT, true, false) + }; + + private ResteasyCommonConfig resteasyCommonConfig; + + @ConfigRoot(name = "resteasy") + public static final class ResteasyCommonConfig { + /** + * Enable gzip support for REST + */ + public ResteasyCommonConfigGzip gzip; + } + + @ConfigGroup + public static final class ResteasyCommonConfigGzip { + /** + * If gzip is enabled + */ + @ConfigItem + public boolean enabled; + /** + * Maximum deflated file bytes size + *

+ * If the limit is exceeded, Resteasy will return Response + * with status 413("Request Entity Too Large") + */ + @ConfigItem + public OptionalInt maxInput; + } + + @BuildStep + void setupGzipProviders(BuildProducer providers) { + // If GZIP support is enabled, enable it + if (resteasyCommonConfig.gzip.enabled) { + providers.produce(new ResteasyJaxrsProviderBuildItem(AcceptEncodingGZIPFilter.class.getName())); + providers.produce(new ResteasyJaxrsProviderBuildItem(GZIPDecodingInterceptor.class.getName())); + providers.produce(new ResteasyJaxrsProviderBuildItem(GZIPEncodingInterceptor.class.getName())); + } + } + + @BuildStep + JaxrsProvidersToRegisterBuildItem setupProviders(BuildProducer reflectiveClass, + CombinedIndexBuildItem indexBuildItem, + List contributedProviderBuildItems) throws Exception { + + Set contributedProviders = new HashSet<>(); + for (ResteasyJaxrsProviderBuildItem contributedProviderBuildItem : contributedProviderBuildItems) { + contributedProviders.add(contributedProviderBuildItem.getName()); + } + for (AnnotationInstance i : indexBuildItem.getIndex().getAnnotations(ResteasyDotNames.PROVIDER)) { + if (i.target().kind() == AnnotationTarget.Kind.CLASS) { + contributedProviders.add(i.target().asClass().name().toString()); + } + } + + Set availableProviders = ServiceUtil.classNamesNamedIn(getClass().getClassLoader(), + "META-INF/services/" + Providers.class.getName()); + + MediaTypeMap categorizedReaders = new MediaTypeMap<>(); + MediaTypeMap categorizedWriters = new MediaTypeMap<>(); + MediaTypeMap categorizedContextResolvers = new MediaTypeMap<>(); + Set otherProviders = new HashSet<>(); + + categorizeProviders(availableProviders, categorizedReaders, categorizedWriters, categorizedContextResolvers, + otherProviders); + + // add the other providers detected + Set providersToRegister = new HashSet<>(otherProviders); + + IndexView index = indexBuildItem.getIndex(); + + // find the providers declared in our services + boolean useBuiltinProviders = collectDeclaredProviders(providersToRegister, categorizedReaders, categorizedWriters, + categorizedContextResolvers, index); + + if (useBuiltinProviders) { + providersToRegister = new HashSet<>(contributedProviders); + providersToRegister.addAll(availableProviders); + } else { + providersToRegister.addAll(contributedProviders); + } + + if (providersToRegister.contains("org.jboss.resteasy.plugins.providers.jsonb.JsonBindingProvider")) { + // This abstract one is also accessed directly via reflection + reflectiveClass.produce(new ReflectiveClassBuildItem(true, true, + "org.jboss.resteasy.plugins.providers.jsonb.AbstractJsonBindingProvider")); + } + + return new JaxrsProvidersToRegisterBuildItem(providersToRegister, contributedProviders, useBuiltinProviders); + } + + private static void categorizeProviders(Set availableProviders, MediaTypeMap categorizedReaders, + MediaTypeMap categorizedWriters, MediaTypeMap categorizedContextResolvers, + Set otherProviders) { + for (String availableProvider : availableProviders) { + try { + Class providerClass = Class.forName(availableProvider); + if (MessageBodyReader.class.isAssignableFrom(providerClass) + || MessageBodyWriter.class.isAssignableFrom(providerClass)) { + if (MessageBodyReader.class.isAssignableFrom(providerClass)) { + Consumes consumes = providerClass.getAnnotation(Consumes.class); + if (consumes != null) { + for (String consumesMediaType : consumes.value()) { + categorizedReaders.add(MediaType.valueOf(consumesMediaType), providerClass.getName()); + } + } else { + categorizedReaders.add(MediaType.WILDCARD_TYPE, providerClass.getName()); + } + } + if (MessageBodyWriter.class.isAssignableFrom(providerClass)) { + Produces produces = providerClass.getAnnotation(Produces.class); + if (produces != null) { + for (String producesMediaType : produces.value()) { + categorizedWriters.add(MediaType.valueOf(producesMediaType), providerClass.getName()); + } + } else { + categorizedWriters.add(MediaType.WILDCARD_TYPE, providerClass.getName()); + } + } + } else if (ContextResolver.class.isAssignableFrom(providerClass)) { + Produces produces = providerClass.getAnnotation(Produces.class); + if (produces != null) { + for (String producesMediaType : produces.value()) { + categorizedContextResolvers.add(MediaType.valueOf(producesMediaType), + providerClass.getName()); + } + } else { + categorizedContextResolvers.add(MediaType.WILDCARD_TYPE, providerClass.getName()); + } + } else { + otherProviders.add(providerClass.getName()); + } + } catch (ClassNotFoundException e) { + // Ignore + } + } + } + + private static boolean collectDeclaredProviders(Set providersToRegister, + MediaTypeMap categorizedReaders, MediaTypeMap categorizedWriters, + MediaTypeMap categorizedContextResolvers, IndexView index) { + for (ProviderDiscoverer providerDiscoverer : PROVIDER_DISCOVERERS) { + Collection getMethods = index.getAnnotations(providerDiscoverer.getMethodAnnotation()); + for (AnnotationInstance getMethod : getMethods) { + MethodInfo methodTarget = getMethod.target().asMethod(); + if (collectDeclaredProvidersForMethodAndMediaTypeAnnotation(providersToRegister, categorizedReaders, + methodTarget, ResteasyDotNames.CONSUMES, providerDiscoverer.noConsumesDefaultsToAll())) { + return true; + } + if (collectDeclaredProvidersForMethodAndMediaTypeAnnotation(providersToRegister, categorizedWriters, + methodTarget, ResteasyDotNames.PRODUCES, providerDiscoverer.noProducesDefaultsToAll())) { + return true; + } + if (collectDeclaredProvidersForMethodAndMediaTypeAnnotation(providersToRegister, + categorizedContextResolvers, methodTarget, ResteasyDotNames.PRODUCES, + providerDiscoverer.noProducesDefaultsToAll())) { + return true; + } + } + } + + return false; + } + + private static boolean collectDeclaredProvidersForMethodAndMediaTypeAnnotation(Set providersToRegister, + MediaTypeMap categorizedProviders, MethodInfo methodTarget, DotName mediaTypeAnnotation, + boolean defaultsToAll) { + AnnotationInstance mediaTypeAnnotationInstance = methodTarget.annotation(mediaTypeAnnotation); + if (mediaTypeAnnotationInstance == null) { + // let's consider the class + Collection classAnnotations = methodTarget.declaringClass().classAnnotations(); + for (AnnotationInstance classAnnotation : classAnnotations) { + if (mediaTypeAnnotation.equals(classAnnotation.name())) { + if (collectDeclaredProvidersForMediaTypeAnnotationInstance(providersToRegister, categorizedProviders, + classAnnotation)) { + return true; + } + return false; + } + } + return defaultsToAll; + } + if (collectDeclaredProvidersForMediaTypeAnnotationInstance(providersToRegister, categorizedProviders, + mediaTypeAnnotationInstance)) { + return true; + } + + return false; + } + + private static boolean collectDeclaredProvidersForMediaTypeAnnotationInstance(Set providersToRegister, + MediaTypeMap categorizedProviders, AnnotationInstance mediaTypeAnnotationInstance) { + for (String media : mediaTypeAnnotationInstance.value().asStringArray()) { + MediaType mediaType = MediaType.valueOf(media); + if (MediaType.WILDCARD_TYPE.equals(mediaType)) { + // exit early if we have the wildcard type + return true; + } + providersToRegister.addAll(categorizedProviders.getPossible(mediaType)); + } + return false; + } + + private static class ProviderDiscoverer { + + private final DotName methodAnnotation; + + private final boolean noConsumesDefaultsToAll; + + private final boolean noProducesDefaultsToAll; + + private ProviderDiscoverer(DotName methodAnnotation, boolean noConsumesDefaultsToAll, + boolean noProducesDefaultsToAll) { + this.methodAnnotation = methodAnnotation; + this.noConsumesDefaultsToAll = noConsumesDefaultsToAll; + this.noProducesDefaultsToAll = noProducesDefaultsToAll; + } + + public DotName getMethodAnnotation() { + return methodAnnotation; + } + + public boolean noConsumesDefaultsToAll() { + return noConsumesDefaultsToAll; + } + + public boolean noProducesDefaultsToAll() { + return noProducesDefaultsToAll; + } + } +} diff --git a/extensions/resteasy-common/deployment/src/main/java/io/quarkus/resteasy/common/deployment/ResteasyDotNames.java b/extensions/resteasy-common/deployment/src/main/java/io/quarkus/resteasy/common/deployment/ResteasyDotNames.java new file mode 100644 index 0000000000000..24575a463a96d --- /dev/null +++ b/extensions/resteasy-common/deployment/src/main/java/io/quarkus/resteasy/common/deployment/ResteasyDotNames.java @@ -0,0 +1,42 @@ +package io.quarkus.resteasy.common.deployment; + +import javax.ws.rs.ApplicationPath; +import javax.ws.rs.Consumes; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.container.DynamicFeature; +import javax.ws.rs.core.Context; +import javax.ws.rs.ext.Provider; + +import org.jboss.jandex.DotName; + +public final class ResteasyDotNames { + + public static final DotName CONSUMES = DotName.createSimple(Consumes.class.getName()); + public static final DotName PRODUCES = DotName.createSimple(Produces.class.getName()); + public static final DotName PROVIDER = DotName.createSimple(Provider.class.getName()); + public static final DotName GET = DotName.createSimple(javax.ws.rs.GET.class.getName()); + public static final DotName HEAD = DotName.createSimple(javax.ws.rs.HEAD.class.getName()); + public static final DotName DELETE = DotName.createSimple(javax.ws.rs.DELETE.class.getName()); + public static final DotName OPTIONS = DotName.createSimple(javax.ws.rs.OPTIONS.class.getName()); + public static final DotName PATCH = DotName.createSimple(javax.ws.rs.PATCH.class.getName()); + public static final DotName POST = DotName.createSimple(javax.ws.rs.POST.class.getName()); + public static final DotName PUT = DotName.createSimple(javax.ws.rs.PUT.class.getName()); + public static final DotName APPLICATION_PATH = DotName.createSimple(ApplicationPath.class.getName()); + public static final DotName PATH = DotName.createSimple(Path.class.getName()); + public static final DotName DYNAMIC_FEATURE = DotName.createSimple(DynamicFeature.class.getName()); + public static final DotName CONTEXT = DotName.createSimple(Context.class.getName()); + public static final DotName RESTEASY_QUERY_PARAM = DotName + .createSimple(org.jboss.resteasy.annotations.jaxrs.QueryParam.class.getName()); + public static final DotName RESTEASY_FORM_PARAM = DotName + .createSimple(org.jboss.resteasy.annotations.jaxrs.FormParam.class.getName()); + public static final DotName RESTEASY_COOKIE_PARAM = DotName + .createSimple(org.jboss.resteasy.annotations.jaxrs.CookieParam.class.getName()); + public static final DotName RESTEASY_PATH_PARAM = DotName + .createSimple(org.jboss.resteasy.annotations.jaxrs.PathParam.class.getName()); + public static final DotName RESTEASY_HEADER_PARAM = DotName + .createSimple(org.jboss.resteasy.annotations.jaxrs.HeaderParam.class.getName()); + public static final DotName RESTEASY_MATRIX_PARAM = DotName + .createSimple(org.jboss.resteasy.annotations.jaxrs.MatrixParam.class.getName()); + +} diff --git a/extensions/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyJaxrsProviderBuildItem.java b/extensions/resteasy-common/deployment/src/main/java/io/quarkus/resteasy/common/deployment/ResteasyJaxrsProviderBuildItem.java similarity index 79% rename from extensions/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyJaxrsProviderBuildItem.java rename to extensions/resteasy-common/deployment/src/main/java/io/quarkus/resteasy/common/deployment/ResteasyJaxrsProviderBuildItem.java index 8930644c1b817..01c4cdbb41a4e 100644 --- a/extensions/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyJaxrsProviderBuildItem.java +++ b/extensions/resteasy-common/deployment/src/main/java/io/quarkus/resteasy/common/deployment/ResteasyJaxrsProviderBuildItem.java @@ -14,13 +14,12 @@ * limitations under the License. */ -package io.quarkus.resteasy.deployment; +package io.quarkus.resteasy.common.deployment; -import org.jboss.builder.item.MultiBuildItem; +import io.quarkus.builder.item.MultiBuildItem; /** - * A build item that represents a JAX-RS provider class, these items will be merged - * into the 'resteasy.providers' context param. + * A build item that represents a JAX-RS provider class. */ public final class ResteasyJaxrsProviderBuildItem extends MultiBuildItem { diff --git a/extensions/resteasy-common/runtime/pom.xml b/extensions/resteasy-common/runtime/pom.xml index 0b852ec3007dc..86c8b75dce37b 100644 --- a/extensions/resteasy-common/runtime/pom.xml +++ b/extensions/resteasy-common/runtime/pom.xml @@ -26,7 +26,7 @@ 4.0.0 - quarkus-resteasy-common-runtime + quarkus-resteasy-common Quarkus - RESTEasy - Common - Runtime @@ -36,7 +36,7 @@ io.quarkus - quarkus-core-runtime + quarkus-core org.jboss.resteasy @@ -47,7 +47,8 @@ - maven-dependency-plugin + io.quarkus + quarkus-bootstrap-maven-plugin maven-compiler-plugin diff --git a/extensions/resteasy-jsonb/deployment/pom.xml b/extensions/resteasy-jsonb/deployment/pom.xml index e6fe935305d9d..6971642400be1 100644 --- a/extensions/resteasy-jsonb/deployment/pom.xml +++ b/extensions/resteasy-jsonb/deployment/pom.xml @@ -26,17 +26,21 @@ 4.0.0 - quarkus-resteasy-jsonb + quarkus-resteasy-jsonb-deployment Quarkus - RESTEasy - JSON-B - Deployment io.quarkus - quarkus-resteasy + quarkus-resteasy-deployment io.quarkus - quarkus-resteasy-jsonb-runtime + quarkus-resteasy-jsonb + + + io.quarkus + quarkus-jsonb-deployment diff --git a/extensions/resteasy-jsonb/deployment/src/main/java/io/quarkus/resteasy/jsonb/deployment/ResteasyJsonbProcessor.java b/extensions/resteasy-jsonb/deployment/src/main/java/io/quarkus/resteasy/jsonb/deployment/ResteasyJsonbProcessor.java index 92e7fe57d0c9f..07be69d6dc02f 100755 --- a/extensions/resteasy-jsonb/deployment/src/main/java/io/quarkus/resteasy/jsonb/deployment/ResteasyJsonbProcessor.java +++ b/extensions/resteasy-jsonb/deployment/src/main/java/io/quarkus/resteasy/jsonb/deployment/ResteasyJsonbProcessor.java @@ -15,23 +15,14 @@ */ package io.quarkus.resteasy.jsonb.deployment; -import org.eclipse.yasson.JsonBindingProvider; -import org.glassfish.json.JsonProviderImpl; - import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.FeatureBuildItem; -import io.quarkus.deployment.builditem.substrate.ReflectiveClassBuildItem; public class ResteasyJsonbProcessor { @BuildStep - void build(BuildProducer feature, - BuildProducer reflectiveClass) { + void build(BuildProducer feature) { feature.produce(new FeatureBuildItem(FeatureBuildItem.RESTEASY_JSONB)); - - reflectiveClass.produce(new ReflectiveClassBuildItem(false, false, - JsonBindingProvider.class.getName(), - JsonProviderImpl.class.getName())); } } diff --git a/extensions/resteasy-jsonb/runtime/pom.xml b/extensions/resteasy-jsonb/runtime/pom.xml index 353cfead93894..cfad59dda8c3c 100644 --- a/extensions/resteasy-jsonb/runtime/pom.xml +++ b/extensions/resteasy-jsonb/runtime/pom.xml @@ -26,13 +26,17 @@ 4.0.0 - quarkus-resteasy-jsonb-runtime + quarkus-resteasy-jsonb Quarkus - RESTEasy - JSON-B - Runtime io.quarkus - quarkus-resteasy-runtime + quarkus-resteasy + + + io.quarkus + quarkus-jsonb org.jboss.resteasy @@ -42,12 +46,17 @@ org.jboss.resteasy resteasy-json-p-provider + + commons-io + commons-io + - maven-dependency-plugin + io.quarkus + quarkus-bootstrap-maven-plugin maven-compiler-plugin diff --git a/extensions/resteasy-server-common/deployment/pom.xml b/extensions/resteasy-server-common/deployment/pom.xml new file mode 100644 index 0000000000000..c52fcc180e504 --- /dev/null +++ b/extensions/resteasy-server-common/deployment/pom.xml @@ -0,0 +1,72 @@ + + + + + + quarkus-resteasy-server-common-parent + io.quarkus + 999-SNAPSHOT + ../ + + 4.0.0 + + quarkus-resteasy-server-common-deployment + Quarkus - RESTEasy - Server common - Deployment + + + + io.quarkus + quarkus-core-deployment + + + io.quarkus + quarkus-resteasy-common-deployment + + + io.quarkus + quarkus-arc-deployment + + + io.quarkus + quarkus-jaxb-deployment + + + io.quarkus + quarkus-resteasy-server-common + + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + + diff --git a/extensions/resteasy-server-common/deployment/src/main/java/io/quarkus/resteasy/server/common/deployment/ResteasyInjectionReadyBuildItem.java b/extensions/resteasy-server-common/deployment/src/main/java/io/quarkus/resteasy/server/common/deployment/ResteasyInjectionReadyBuildItem.java new file mode 100644 index 0000000000000..b0a58af334cbc --- /dev/null +++ b/extensions/resteasy-server-common/deployment/src/main/java/io/quarkus/resteasy/server/common/deployment/ResteasyInjectionReadyBuildItem.java @@ -0,0 +1,25 @@ +/* + * Copyright 2018 Red Hat, Inc. + * + * Licensed 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.resteasy.server.common.deployment; + +import io.quarkus.builder.item.SimpleBuildItem; + +/** + * Simple build item marker indicating the RESTEasy injection has been properly set up. + */ +public final class ResteasyInjectionReadyBuildItem extends SimpleBuildItem { + +} diff --git a/extensions/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyLogFilterBuildStep.java b/extensions/resteasy-server-common/deployment/src/main/java/io/quarkus/resteasy/server/common/deployment/ResteasyLogFilterBuildStep.java similarity index 95% rename from extensions/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyLogFilterBuildStep.java rename to extensions/resteasy-server-common/deployment/src/main/java/io/quarkus/resteasy/server/common/deployment/ResteasyLogFilterBuildStep.java index 5e24a7fdb15d0..846b192a2b2bd 100644 --- a/extensions/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyLogFilterBuildStep.java +++ b/extensions/resteasy-server-common/deployment/src/main/java/io/quarkus/resteasy/server/common/deployment/ResteasyLogFilterBuildStep.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.quarkus.resteasy.deployment; +package io.quarkus.resteasy.server.common.deployment; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; 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 new file mode 100755 index 0000000000000..875a967ee98e2 --- /dev/null +++ b/extensions/resteasy-server-common/deployment/src/main/java/io/quarkus/resteasy/server/common/deployment/ResteasyServerCommonProcessor.java @@ -0,0 +1,555 @@ +/* + * Copyright 2018 Red Hat, Inc. + * + * Licensed 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.resteasy.server.common.deployment; + +import static io.quarkus.deployment.annotations.ExecutionTime.STATIC_INIT; + +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.BiFunction; +import java.util.function.Function; + +import javax.ws.rs.container.AsyncResponse; +import javax.ws.rs.core.Response; + +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationTarget; +import org.jboss.jandex.AnnotationTarget.Kind; +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.DotName; +import org.jboss.jandex.IndexView; +import org.jboss.jandex.MethodInfo; +import org.jboss.jandex.MethodParameterInfo; +import org.jboss.jandex.Type; +import org.jboss.logging.Logger; +import org.jboss.resteasy.api.validation.ResteasyConstraintViolation; +import org.jboss.resteasy.api.validation.ViolationReport; +import org.jboss.resteasy.microprofile.config.FilterConfigSource; +import org.jboss.resteasy.microprofile.config.ServletConfigSource; +import org.jboss.resteasy.microprofile.config.ServletContextConfigSource; +import org.jboss.resteasy.plugins.server.servlet.ResteasyContextParameters; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem; +import io.quarkus.arc.deployment.BeanContainerBuildItem; +import io.quarkus.arc.deployment.BeanDefiningAnnotationBuildItem; +import io.quarkus.arc.deployment.UnremovableBeanBuildItem; +import io.quarkus.arc.processor.AnnotationsTransformer; +import io.quarkus.arc.processor.BuiltinScope; +import io.quarkus.arc.processor.DotNames; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.Record; +import io.quarkus.deployment.builditem.BytecodeTransformerBuildItem; +import io.quarkus.deployment.builditem.CombinedIndexBuildItem; +import io.quarkus.deployment.builditem.ProxyUnwrapperBuildItem; +import io.quarkus.deployment.builditem.substrate.ReflectiveClassBuildItem; +import io.quarkus.deployment.builditem.substrate.ReflectiveHierarchyBuildItem; +import io.quarkus.deployment.builditem.substrate.RuntimeInitializedClassBuildItem; +import io.quarkus.deployment.builditem.substrate.SubstrateConfigBuildItem; +import io.quarkus.deployment.builditem.substrate.SubstrateProxyDefinitionBuildItem; +import io.quarkus.deployment.builditem.substrate.SubstrateResourceBuildItem; +import io.quarkus.jaxb.deployment.JaxbEnabledBuildItem; +import io.quarkus.resteasy.common.deployment.JaxrsProvidersToRegisterBuildItem; +import io.quarkus.resteasy.common.deployment.ResteasyCommonProcessor.ResteasyCommonConfig; +import io.quarkus.resteasy.common.deployment.ResteasyDotNames; +import io.quarkus.resteasy.server.common.runtime.QuarkusInjectorFactory; +import io.quarkus.resteasy.server.common.runtime.ResteasyServerCommonTemplate; +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigRoot; + +/** + * Processor that builds the RESTEasy server configuration. + */ +public class ResteasyServerCommonProcessor { + + private static final Logger log = Logger.getLogger("io.quarkus.resteasy"); + + private static final String JAX_RS_APPLICATION_PARAMETER_NAME = "javax.ws.rs.Application"; + + private static final DotName JSONB_ANNOTATION = DotName.createSimple("javax.json.bind.annotation.JsonbAnnotation"); + + private static final Set TYPES_IGNORED_FOR_REFLECTION = new HashSet<>(Arrays.asList( + // javax.json + DotName.createSimple("javax.json.JsonObject"), + DotName.createSimple("javax.json.JsonArray"), + + // JAX-RS + DotName.createSimple(Response.class.getName()), + DotName.createSimple(AsyncResponse.class.getName()), + + // Vert-x + DotName.createSimple("io.vertx.core.json.JsonArray"), + DotName.createSimple("io.vertx.core.json.JsonObject"))); + + private static final DotName[] METHOD_ANNOTATIONS = { + ResteasyDotNames.GET, + ResteasyDotNames.HEAD, + ResteasyDotNames.DELETE, + ResteasyDotNames.OPTIONS, + ResteasyDotNames.PATCH, + ResteasyDotNames.POST, + ResteasyDotNames.PUT, + }; + + private static final DotName[] RESTEASY_PARAM_ANNOTATIONS = { + ResteasyDotNames.RESTEASY_QUERY_PARAM, + ResteasyDotNames.RESTEASY_FORM_PARAM, + ResteasyDotNames.RESTEASY_COOKIE_PARAM, + ResteasyDotNames.RESTEASY_PATH_PARAM, + ResteasyDotNames.RESTEASY_HEADER_PARAM, + ResteasyDotNames.RESTEASY_MATRIX_PARAM, + }; + + /** + * JAX-RS configuration. + */ + ResteasyConfig resteasyConfig; + ResteasyCommonConfig commonConfig; + + @ConfigRoot + static final class ResteasyConfig { + /** + * If this is true then JAX-RS will use only a single instance of a resource + * class to service all requests. + *

+ * If this is false then it will create a new instance of the resource per + * request. + *

+ * If the resource class has an explicit CDI scope annotation then the value of + * this annotation will always be used to control the lifecycle of the resource + * class. + *

+ * IMPLEMENTATION NOTE: {@code javax.ws.rs.Path} turns into a CDI stereotype + * with singleton scope. As a result, if a user annotates a JAX-RS resource with + * a stereotype which has a different default scope the deployment fails with + * IllegalStateException. + */ + @ConfigItem(defaultValue = "true") + boolean singletonResources; + + /** + * Set this to override the default path for JAX-RS resources if there are no + * annotated application classes. + */ + @ConfigItem(defaultValue = "/") + String path; + } + + @BuildStep + SubstrateConfigBuildItem config() { + return SubstrateConfigBuildItem.builder() + .addResourceBundle("messages") + .build(); + } + + @BuildStep + public void build( + BuildProducer reflectiveClass, + BuildProducer reflectiveHierarchy, + BuildProducer proxyDefinition, + BuildProducer resource, + BuildProducer runtimeClasses, + BuildProducer transformers, + BuildProducer resteasyServerConfig, + BuildProducer unremovableBeans, + JaxrsProvidersToRegisterBuildItem jaxrsProvidersToRegisterBuildItem, + CombinedIndexBuildItem combinedIndexBuildItem) throws Exception { + IndexView index = combinedIndexBuildItem.getIndex(); + + resource.produce(new SubstrateResourceBuildItem("META-INF/services/javax.ws.rs.client.ClientBuilder")); + + Collection applicationPaths = index.getAnnotations(ResteasyDotNames.APPLICATION_PATH); + + // currently we only examine the first class that is annotated with @ApplicationPath so best + // fail if the user code has multiple such annotations instead of surprising the user + // at runtime + if (applicationPaths.size() > 1) { + throw createMultipleApplicationsException(applicationPaths); + } + + Collection paths = index.getAnnotations(ResteasyDotNames.PATH); + + if (paths.isEmpty()) { + // no detected @Path, bail out + return; + } + + final String path; + final String appClass; + if (!applicationPaths.isEmpty()) { + AnnotationInstance applicationPath = applicationPaths.iterator().next(); + path = applicationPath.value().asString(); + appClass = applicationPath.target().asClass().name().toString(); + } else { + path = resteasyConfig.path; + appClass = null; + } + + Set resources = new HashSet<>(); + Set pathInterfaces = new HashSet<>(); + Set withoutDefaultCtor = new HashSet<>(); + for (AnnotationInstance annotation : paths) { + if (annotation.target().kind() == AnnotationTarget.Kind.CLASS) { + ClassInfo clazz = annotation.target().asClass(); + if (!Modifier.isInterface(clazz.flags())) { + String className = clazz.name().toString(); + resources.add(className); + reflectiveClass.produce(new ReflectiveClassBuildItem(true, true, className)); + + if (!clazz.hasNoArgsConstructor()) { + withoutDefaultCtor.add(clazz); + } + } else { + pathInterfaces.add(clazz.name()); + } + } + } + + // look for all implementations of interfaces annotated @Path + for (final DotName iface : pathInterfaces) { + final Collection implementors = index.getAllKnownImplementors(iface); + for (final ClassInfo implementor : implementors) { + String className = implementor.name().toString(); + reflectiveClass.produce(new ReflectiveClassBuildItem(true, true, className)); + resources.add(className); + } + } + + // generate default constructors for suitable concrete @Path classes that don't have them + // see https://issues.jboss.org/browse/RESTEASY-2183 + generateDefaultConstructors(transformers, withoutDefaultCtor); + + checkParameterNames(index); + + registerContextProxyDefinitions(index, proxyDefinition); + + registerReflectionForSerialization(reflectiveClass, reflectiveHierarchy, combinedIndexBuildItem); + + for (ClassInfo implementation : index.getAllKnownImplementors(ResteasyDotNames.DYNAMIC_FEATURE)) { + reflectiveClass.produce(new ReflectiveClassBuildItem(false, false, implementation.name().toString())); + } + + Map resteasyInitParameters = new HashMap<>(); + + registerProviders(resteasyInitParameters, reflectiveClass, unremovableBeans, jaxrsProvidersToRegisterBuildItem); + + if (!resources.isEmpty()) { + resteasyInitParameters.put(ResteasyContextParameters.RESTEASY_SCANNED_RESOURCES, String.join(",", resources)); + } + resteasyInitParameters.put(ResteasyContextParameters.RESTEASY_SERVLET_MAPPING_PREFIX, path); + if (appClass != null) { + resteasyInitParameters.put(JAX_RS_APPLICATION_PARAMETER_NAME, appClass); + } + resteasyInitParameters.put("resteasy.injector.factory", QuarkusInjectorFactory.class.getName()); + + if (commonConfig.gzip.enabled && commonConfig.gzip.maxInput.isPresent()) { + resteasyInitParameters.put(ResteasyContextParameters.RESTEASY_GZIP_MAX_INPUT, + Integer.toString(commonConfig.gzip.maxInput.getAsInt())); + } + + resteasyServerConfig.produce(new ResteasyServerConfigBuildItem(path, resteasyInitParameters)); + } + + @Record(STATIC_INIT) + @BuildStep + ResteasyInjectionReadyBuildItem setupInjection(ResteasyServerCommonTemplate template, + BeanContainerBuildItem beanContainerBuildItem, + List proxyUnwrappers) { + List> unwrappers = new ArrayList<>(); + for (ProxyUnwrapperBuildItem i : proxyUnwrappers) { + unwrappers.add(i.getUnwrapper()); + } + template.setupIntegration(beanContainerBuildItem.getValue(), unwrappers); + + return new ResteasyInjectionReadyBuildItem(); + } + + @BuildStep + void beanDefiningAnnotations(BuildProducer beanDefiningAnnotations) { + beanDefiningAnnotations + .produce(new BeanDefiningAnnotationBuildItem(ResteasyDotNames.PATH, + resteasyConfig.singletonResources ? BuiltinScope.SINGLETON.getName() : null)); + beanDefiningAnnotations + .produce(new BeanDefiningAnnotationBuildItem(ResteasyDotNames.APPLICATION_PATH, + BuiltinScope.SINGLETON.getName())); + } + + @BuildStep + AnnotationsTransformerBuildItem annotationTransformer() { + return new AnnotationsTransformerBuildItem(new AnnotationsTransformer() { + + @Override + public boolean appliesTo(Kind kind) { + return kind == Kind.CLASS; + } + + @Override + public void transform(TransformationContext transformationContext) { + ClassInfo clazz = transformationContext.getTarget().asClass(); + if (clazz.classAnnotation(ResteasyDotNames.PROVIDER) != null && clazz.annotations().containsKey(DotNames.INJECT) + && !BuiltinScope.isIn(clazz.classAnnotations())) { + // A provider with an injection point but no built-in scope is @Singleton + transformationContext.transform().add(BuiltinScope.SINGLETON.getName()).done(); + } + } + }); + } + + /** + * Indicates that JAXB support should be enabled + * + * @return + */ + @BuildStep + JaxbEnabledBuildItem enableJaxb() { + return new JaxbEnabledBuildItem(); + } + + private static void registerProviders(Map resteasyInitParameters, + BuildProducer reflectiveClass, + BuildProducer unremovableBeans, + JaxrsProvidersToRegisterBuildItem jaxrsProvidersToRegisterBuildItem) { + + if (jaxrsProvidersToRegisterBuildItem.useBuiltIn()) { + // if we find a wildcard media type, we just use the built-in providers + resteasyInitParameters.put(ResteasyContextParameters.RESTEASY_USE_BUILTIN_PROVIDERS, "true"); + + if (!jaxrsProvidersToRegisterBuildItem.getContributedProviders().isEmpty()) { + resteasyInitParameters.put(ResteasyContextParameters.RESTEASY_PROVIDERS, + String.join(",", jaxrsProvidersToRegisterBuildItem.getContributedProviders())); + } + } else { + resteasyInitParameters.put(ResteasyContextParameters.RESTEASY_USE_BUILTIN_PROVIDERS, "false"); + resteasyInitParameters.put(ResteasyContextParameters.RESTEASY_PROVIDERS, + String.join(",", jaxrsProvidersToRegisterBuildItem.getProviders())); + } + + // register the providers for reflection + for (String providerToRegister : jaxrsProvidersToRegisterBuildItem.getProviders()) { + reflectiveClass.produce(new ReflectiveClassBuildItem(false, false, providerToRegister)); + } + + // special case: our config providers + reflectiveClass.produce(new ReflectiveClassBuildItem(false, false, + ServletConfigSource.class, + ServletContextConfigSource.class, + FilterConfigSource.class)); + + // Providers that are also beans are unremovable + unremovableBeans.produce(new UnremovableBeanBuildItem( + b -> jaxrsProvidersToRegisterBuildItem.getProviders().contains(b.getBeanClass().toString()))); + } + + private static void generateDefaultConstructors(BuildProducer transformers, + Set withoutDefaultCtor) { + for (ClassInfo classInfo : withoutDefaultCtor) { + // keep it super simple - only generate default constructor is the object is a direct descendant of Object + if (!(classInfo.superClassType() != null && classInfo.superClassType().name().equals(DotNames.OBJECT))) { + return; + } + + boolean hasNonJaxRSAnnotations = false; + for (AnnotationInstance instance : classInfo.classAnnotations()) { + if (!instance.name().toString().startsWith("javax.ws.rs")) { + hasNonJaxRSAnnotations = true; + break; + } + } + + // again keep it very very simple, if there are any non JAX-RS annotations, we don't generate the constructor + if (hasNonJaxRSAnnotations) { + continue; + } + + final String name = classInfo.name().toString(); + transformers + .produce(new BytecodeTransformerBuildItem(name, new BiFunction() { + @Override + public ClassVisitor apply(String className, ClassVisitor classVisitor) { + ClassVisitor cv = new ClassVisitor(Opcodes.ASM6, classVisitor) { + + @Override + public void visit(int version, int access, String name, String signature, String superName, + String[] interfaces) { + super.visit(version, access, name, signature, superName, interfaces); + MethodVisitor ctor = visitMethod(Modifier.PUBLIC, "", "()V", null, + null); + ctor.visitCode(); + ctor.visitVarInsn(Opcodes.ALOAD, 0); + ctor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "", "()V", false); + ctor.visitInsn(Opcodes.RETURN); + ctor.visitMaxs(1, 1); + ctor.visitEnd(); + } + }; + return cv; + } + })); + } + } + + private static void checkParameterNames(IndexView index) { + OUTER: for (DotName annotationType : RESTEASY_PARAM_ANNOTATIONS) { + Collection instances = index.getAnnotations(annotationType); + for (AnnotationInstance instance : instances) { + MethodParameterInfo param = instance.target().asMethodParameter(); + if (param.name() == null) { + log.warnv( + "Detected RESTEasy annotation {0} on method parameter {1}.{2} with no name. Either specify its name," + + " or tell your compiler to enable debug info (-g) or parameter names (-parameters). This message is only" + + " logged for the first such parameter.", + instance.name(), + param.method().declaringClass(), param.method().name()); + break OUTER; + } + } + } + } + + private static void registerContextProxyDefinitions(IndexView index, + BuildProducer proxyDefinition) { + // @Context uses proxies for interface injection + for (AnnotationInstance annotation : index.getAnnotations(ResteasyDotNames.CONTEXT)) { + DotName typeName = null; + if (annotation.target().kind() == AnnotationTarget.Kind.METHOD) { + MethodInfo method = annotation.target().asMethod(); + if (method.parameters().size() == 1) { + typeName = method.parameters().get(0).name(); + } + } else if (annotation.target().kind() == AnnotationTarget.Kind.FIELD) { + typeName = annotation.target().asField().type().name(); + } else if (annotation.target().kind() == AnnotationTarget.Kind.METHOD_PARAMETER) { + int pos = annotation.target().asMethodParameter().position(); + typeName = annotation.target().asMethodParameter().method().parameters().get(pos).name(); + } + if (typeName != null) { + ClassInfo type = index.getClassByName(typeName); + if (type != null) { + if (Modifier.isInterface(type.flags())) { + proxyDefinition.produce(new SubstrateProxyDefinitionBuildItem(type.toString())); + } + } else { + //might be a framework class, which should be loadable + try { + Class typeClass = Class.forName(typeName.toString()); + if (typeClass.isInterface()) { + proxyDefinition.produce(new SubstrateProxyDefinitionBuildItem(typeName.toString())); + } + } catch (Exception e) { + //ignore + } + } + } + } + } + + private static void registerReflectionForSerialization(BuildProducer reflectiveClass, + BuildProducer reflectiveHierarchy, + CombinedIndexBuildItem combinedIndexBuildItem) { + IndexView index = combinedIndexBuildItem.getIndex(); + + // required by Jackson + reflectiveClass.produce(new ReflectiveClassBuildItem(true, false, + "com.fasterxml.jackson.module.jaxb.JaxbAnnotationIntrospector", + "com.fasterxml.jackson.databind.ser.std.SqlDateSerializer")); + + // This is probably redundant with the automatic resolution we do just below but better be safe + for (AnnotationInstance annotation : index.getAnnotations(JSONB_ANNOTATION)) { + if (annotation.target().kind() == AnnotationTarget.Kind.CLASS) { + reflectiveClass + .produce(new ReflectiveClassBuildItem(true, true, annotation.target().asClass().name().toString())); + } + } + + // Declare reflection for all the types implicated in the Rest end points (return types and parameters). + // It might be needed for serialization. + for (DotName annotationType : METHOD_ANNOTATIONS) { + Collection instances = index.getAnnotations(annotationType); + for (AnnotationInstance instance : instances) { + MethodInfo method = instance.target().asMethod(); + if (isReflectionDeclarationRequiredFor(method.returnType())) { + reflectiveHierarchy.produce(new ReflectiveHierarchyBuildItem(method.returnType())); + } + for (short i = 0; i < method.parameters().size(); i++) { + Type parameterType = method.parameters().get(i); + if (isReflectionDeclarationRequiredFor(parameterType) + && !hasAnnotation(method, i, ResteasyDotNames.CONTEXT)) { + reflectiveHierarchy.produce(new ReflectiveHierarchyBuildItem(parameterType)); + } + } + } + } + + // In the case of a constraint violation, these elements might be returned as entities and will be serialized + reflectiveClass.produce(new ReflectiveClassBuildItem(true, true, ViolationReport.class.getName())); + reflectiveClass.produce(new ReflectiveClassBuildItem(true, true, ResteasyConstraintViolation.class.getName())); + } + + private static boolean isReflectionDeclarationRequiredFor(Type type) { + DotName className = getClassName(type); + + return className != null && !TYPES_IGNORED_FOR_REFLECTION.contains(className); + } + + private static DotName getClassName(Type type) { + switch (type.kind()) { + case CLASS: + case PARAMETERIZED_TYPE: + return type.name(); + case ARRAY: + return getClassName(type.asArrayType().component()); + default: + return null; + } + } + + private static boolean hasAnnotation(MethodInfo method, short paramPosition, DotName annotation) { + for (AnnotationInstance annotationInstance : method.annotations()) { + AnnotationTarget target = annotationInstance.target(); + if (target != null && target.kind() == Kind.METHOD_PARAMETER + && target.asMethodParameter().position() == paramPosition + && annotationInstance.name().equals(annotation)) { + return true; + } + } + return false; + } + + private static RuntimeException createMultipleApplicationsException(Collection applicationPaths) { + StringBuilder sb = new StringBuilder(); + boolean first = true; + for (AnnotationInstance annotationInstance : applicationPaths) { + if (first) { + first = false; + } else { + sb.append(","); + } + sb.append(annotationInstance.target().asClass().name().toString()); + } + return new RuntimeException("Multiple classes ( " + sb.toString() + + ") have been annotated with @ApplicationPath which is currently not supported"); + } +} diff --git a/extensions/resteasy-server-common/deployment/src/main/java/io/quarkus/resteasy/server/common/deployment/ResteasyServerConfigBuildItem.java b/extensions/resteasy-server-common/deployment/src/main/java/io/quarkus/resteasy/server/common/deployment/ResteasyServerConfigBuildItem.java new file mode 100644 index 0000000000000..af36c2cab5f43 --- /dev/null +++ b/extensions/resteasy-server-common/deployment/src/main/java/io/quarkus/resteasy/server/common/deployment/ResteasyServerConfigBuildItem.java @@ -0,0 +1,44 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Licensed 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.resteasy.server.common.deployment; + +import java.util.Map; + +import io.quarkus.builder.item.SimpleBuildItem; + +/** + * A build item that represents the configuration of the RESTEasy server. + */ +public final class ResteasyServerConfigBuildItem extends SimpleBuildItem { + + private final String path; + + private final Map initParameters; + + public ResteasyServerConfigBuildItem(String path, Map initParameters) { + this.path = path; + this.initParameters = initParameters; + } + + public String getPath() { + return path; + } + + public Map getInitParameters() { + return initParameters; + } +} diff --git a/extensions/resteasy-server-common/pom.xml b/extensions/resteasy-server-common/pom.xml new file mode 100644 index 0000000000000..4a1179e93601c --- /dev/null +++ b/extensions/resteasy-server-common/pom.xml @@ -0,0 +1,37 @@ + + + + + + quarkus-build-parent + io.quarkus + 999-SNAPSHOT + ../../build-parent/pom.xml + + 4.0.0 + + quarkus-resteasy-server-common-parent + Quarkus - RESTEasy - Server common + pom + + deployment + runtime + + + diff --git a/extensions/resteasy-server-common/runtime/pom.xml b/extensions/resteasy-server-common/runtime/pom.xml new file mode 100644 index 0000000000000..720fabb9ece32 --- /dev/null +++ b/extensions/resteasy-server-common/runtime/pom.xml @@ -0,0 +1,79 @@ + + + + + + quarkus-resteasy-server-common-parent + io.quarkus + 999-SNAPSHOT + ../ + + 4.0.0 + + quarkus-resteasy-server-common + Quarkus - RESTEasy - Server common - Runtime + + + + io.quarkus + quarkus-core + + + io.quarkus + quarkus-arc + + + io.quarkus + quarkus-resteasy-common + + + javax.validation + validation-api + + + com.sun.activation + jakarta.activation + + + io.quarkus + quarkus-jaxb + + + + + + + io.quarkus + quarkus-bootstrap-maven-plugin + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + diff --git a/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/QuarkusConstructorInjector.java b/extensions/resteasy-server-common/runtime/src/main/java/io/quarkus/resteasy/server/common/runtime/QuarkusConstructorInjector.java similarity index 98% rename from extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/QuarkusConstructorInjector.java rename to extensions/resteasy-server-common/runtime/src/main/java/io/quarkus/resteasy/server/common/runtime/QuarkusConstructorInjector.java index 05d3e15e5759e..84ae5c349c79d 100644 --- a/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/QuarkusConstructorInjector.java +++ b/extensions/resteasy-server-common/runtime/src/main/java/io/quarkus/resteasy/server/common/runtime/QuarkusConstructorInjector.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.resteasy.runtime; +package io.quarkus.resteasy.server.common.runtime; import java.lang.reflect.Constructor; import java.util.concurrent.CompletableFuture; diff --git a/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/QuarkusInjectorFactory.java b/extensions/resteasy-server-common/runtime/src/main/java/io/quarkus/resteasy/server/common/runtime/QuarkusInjectorFactory.java similarity index 98% rename from extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/QuarkusInjectorFactory.java rename to extensions/resteasy-server-common/runtime/src/main/java/io/quarkus/resteasy/server/common/runtime/QuarkusInjectorFactory.java index b67a63dc09a93..56bdef79a7f10 100644 --- a/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/QuarkusInjectorFactory.java +++ b/extensions/resteasy-server-common/runtime/src/main/java/io/quarkus/resteasy/server/common/runtime/QuarkusInjectorFactory.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.resteasy.runtime; +package io.quarkus.resteasy.server.common.runtime; import java.lang.reflect.Constructor; import java.util.concurrent.CompletionStage; diff --git a/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/ResteasyTemplate.java b/extensions/resteasy-server-common/runtime/src/main/java/io/quarkus/resteasy/server/common/runtime/ResteasyServerCommonTemplate.java similarity index 93% rename from extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/ResteasyTemplate.java rename to extensions/resteasy-server-common/runtime/src/main/java/io/quarkus/resteasy/server/common/runtime/ResteasyServerCommonTemplate.java index 5ed90f04b537d..0219596a90029 100644 --- a/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/ResteasyTemplate.java +++ b/extensions/resteasy-server-common/runtime/src/main/java/io/quarkus/resteasy/server/common/runtime/ResteasyServerCommonTemplate.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.resteasy.runtime; +package io.quarkus.resteasy.server.common.runtime; import java.util.List; import java.util.function.Function; @@ -23,7 +23,7 @@ import io.quarkus.runtime.annotations.Template; @Template -public class ResteasyTemplate { +public class ResteasyServerCommonTemplate { public void setupIntegration(BeanContainer container, List> propertyUnwrappers) { QuarkusInjectorFactory.CONTAINER = container; @@ -38,5 +38,4 @@ public Object apply(Object o) { } }; } - } diff --git a/extensions/resteasy/deployment/pom.xml b/extensions/resteasy/deployment/pom.xml index 5904b2bc88922..38d2d83d47959 100644 --- a/extensions/resteasy/deployment/pom.xml +++ b/extensions/resteasy/deployment/pom.xml @@ -26,33 +26,21 @@ 4.0.0 - quarkus-resteasy + quarkus-resteasy-deployment Quarkus - RESTEasy - Deployment io.quarkus - quarkus-core + quarkus-resteasy-server-common-deployment io.quarkus - quarkus-resteasy-common + quarkus-undertow-deployment io.quarkus - quarkus-undertow - - - io.quarkus - quarkus-arc - - - io.quarkus - quarkus-jaxb - - - io.quarkus - quarkus-resteasy-runtime + quarkus-resteasy com.oracle.substratevm @@ -61,10 +49,12 @@ io.quarkus quarkus-junit5-internal + test io.rest-assured rest-assured + test diff --git a/extensions/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyJaxrsConfig.java b/extensions/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyJaxrsConfigBuildItem.java similarity index 80% rename from extensions/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyJaxrsConfig.java rename to extensions/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyJaxrsConfigBuildItem.java index cecbdcb7d3781..4f141b47b2cd2 100644 --- a/extensions/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyJaxrsConfig.java +++ b/extensions/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyJaxrsConfigBuildItem.java @@ -16,16 +16,16 @@ package io.quarkus.resteasy.deployment; -import org.jboss.builder.item.SimpleBuildItem; +import io.quarkus.builder.item.SimpleBuildItem; /** * A build item that represents a JAX-RS config. */ -public final class ResteasyJaxrsConfig extends SimpleBuildItem { +public final class ResteasyJaxrsConfigBuildItem extends SimpleBuildItem { public final String defaultPath; - public ResteasyJaxrsConfig(String defaultPath) { + public ResteasyJaxrsConfigBuildItem(String defaultPath) { this.defaultPath = defaultPath; } } diff --git a/extensions/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyProcessor.java b/extensions/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyProcessor.java new file mode 100755 index 0000000000000..293e8405eee29 --- /dev/null +++ b/extensions/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyProcessor.java @@ -0,0 +1,106 @@ +/* + * Copyright 2018 Red Hat, Inc. + * + * Licensed 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.resteasy.deployment; + +import java.util.Map.Entry; +import java.util.Optional; + +import javax.servlet.DispatcherType; +import javax.ws.rs.core.Application; + +import org.jboss.resteasy.plugins.server.servlet.HttpServlet30Dispatcher; + +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.FeatureBuildItem; +import io.quarkus.deployment.builditem.substrate.ReflectiveClassBuildItem; +import io.quarkus.resteasy.common.deployment.ResteasyJaxrsProviderBuildItem; +import io.quarkus.resteasy.runtime.ResteasyFilter; +import io.quarkus.resteasy.runtime.RolesFilterRegistrar; +import io.quarkus.resteasy.server.common.deployment.ResteasyInjectionReadyBuildItem; +import io.quarkus.resteasy.server.common.deployment.ResteasyServerConfigBuildItem; +import io.quarkus.undertow.deployment.FilterBuildItem; +import io.quarkus.undertow.deployment.ServletBuildItem; +import io.quarkus.undertow.deployment.ServletInitParamBuildItem; + +/** + * Processor that finds JAX-RS classes in the deployment + */ +public class ResteasyProcessor { + + private static final String JAVAX_WS_RS_APPLICATION = Application.class.getName(); + private static final String JAX_RS_FILTER_NAME = JAVAX_WS_RS_APPLICATION; + private static final String JAX_RS_SERVLET_NAME = JAVAX_WS_RS_APPLICATION; + + @BuildStep + public void jaxrsConfig(Optional resteasyServerConfig, + BuildProducer resteasyJaxrsConfig) { + if (resteasyServerConfig.isPresent()) { + resteasyJaxrsConfig.produce(new ResteasyJaxrsConfigBuildItem(resteasyServerConfig.get().getPath())); + } + } + + @BuildStep + public void build( + Optional resteasyServerConfig, + BuildProducer feature, + BuildProducer filter, + BuildProducer servlet, + BuildProducer reflectiveClass, + BuildProducer servletInitParameters, + ResteasyInjectionReadyBuildItem resteasyInjectionReady) throws Exception { + feature.produce(new FeatureBuildItem(FeatureBuildItem.RESTEASY)); + + if (resteasyServerConfig.isPresent()) { + String path = resteasyServerConfig.get().getPath(); + + //if JAX-RS is installed at the root location we use a filter, otherwise we use a Servlet and take over the whole mapped path + if (resteasyServerConfig.get().getPath().equals("/")) { + filter.produce(FilterBuildItem.builder(JAX_RS_FILTER_NAME, ResteasyFilter.class.getName()).setLoadOnStartup(1) + .addFilterServletNameMapping("default", DispatcherType.REQUEST).setAsyncSupported(true) + .build()); + reflectiveClass.produce(new ReflectiveClassBuildItem(false, false, ResteasyFilter.class.getName())); + } else { + String mappingPath = getMappingPath(path); + servlet.produce(ServletBuildItem.builder(JAX_RS_SERVLET_NAME, HttpServlet30Dispatcher.class.getName()) + .setLoadOnStartup(1).addMapping(mappingPath).setAsyncSupported(true).build()); + reflectiveClass.produce(new ReflectiveClassBuildItem(false, false, HttpServlet30Dispatcher.class.getName())); + } + + for (Entry initParameter : resteasyServerConfig.get().getInitParameters().entrySet()) { + servletInitParameters.produce(new ServletInitParamBuildItem(initParameter.getKey(), initParameter.getValue())); + } + } + } + + /** + * Install the JAX-RS security provider. + */ + @BuildStep + void setupFilter(BuildProducer providers) { + providers.produce(new ResteasyJaxrsProviderBuildItem(RolesFilterRegistrar.class.getName())); + } + + private String getMappingPath(String path) { + String mappingPath; + if (path.endsWith("/")) { + mappingPath = path + "*"; + } else { + mappingPath = path + "/*"; + } + return mappingPath; + } +} diff --git a/extensions/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyScanningProcessor.java b/extensions/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyScanningProcessor.java deleted file mode 100755 index ce8ac5f08558d..0000000000000 --- a/extensions/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyScanningProcessor.java +++ /dev/null @@ -1,714 +0,0 @@ -/* - * Copyright 2018 Red Hat, Inc. - * - * Licensed 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.resteasy.deployment; - -import static io.quarkus.deployment.annotations.ExecutionTime.STATIC_INIT; - -import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.function.Function; -import java.util.stream.Collectors; - -import javax.inject.Singleton; -import javax.servlet.DispatcherType; -import javax.ws.rs.ApplicationPath; -import javax.ws.rs.Consumes; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.container.AsyncResponse; -import javax.ws.rs.container.DynamicFeature; -import javax.ws.rs.core.Application; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import javax.ws.rs.ext.ContextResolver; -import javax.ws.rs.ext.MessageBodyReader; -import javax.ws.rs.ext.MessageBodyWriter; -import javax.ws.rs.ext.Provider; -import javax.ws.rs.ext.Providers; - -import org.jboss.jandex.AnnotationInstance; -import org.jboss.jandex.AnnotationTarget; -import org.jboss.jandex.AnnotationTarget.Kind; -import org.jboss.jandex.ClassInfo; -import org.jboss.jandex.DotName; -import org.jboss.jandex.IndexView; -import org.jboss.jandex.MethodInfo; -import org.jboss.jandex.MethodParameterInfo; -import org.jboss.jandex.Type; -import org.jboss.logging.Logger; -import org.jboss.resteasy.api.validation.ResteasyConstraintViolation; -import org.jboss.resteasy.api.validation.ViolationReport; -import org.jboss.resteasy.core.MediaTypeMap; -import org.jboss.resteasy.plugins.interceptors.AcceptEncodingGZIPFilter; -import org.jboss.resteasy.plugins.interceptors.GZIPDecodingInterceptor; -import org.jboss.resteasy.plugins.interceptors.GZIPEncodingInterceptor; -import org.jboss.resteasy.plugins.server.servlet.HttpServlet30Dispatcher; -import org.jboss.resteasy.plugins.server.servlet.ResteasyContextParameters; - -import io.quarkus.arc.deployment.BeanContainerBuildItem; -import io.quarkus.arc.deployment.BeanDefiningAnnotationBuildItem; -import io.quarkus.arc.deployment.UnremovableBeanBuildItem; -import io.quarkus.arc.deployment.UnremovableBeanBuildItem.BeanClassAnnotationExclusion; -import io.quarkus.deployment.annotations.BuildProducer; -import io.quarkus.deployment.annotations.BuildStep; -import io.quarkus.deployment.annotations.Record; -import io.quarkus.deployment.builditem.CombinedIndexBuildItem; -import io.quarkus.deployment.builditem.FeatureBuildItem; -import io.quarkus.deployment.builditem.ProxyUnwrapperBuildItem; -import io.quarkus.deployment.builditem.substrate.ReflectiveClassBuildItem; -import io.quarkus.deployment.builditem.substrate.ReflectiveHierarchyBuildItem; -import io.quarkus.deployment.builditem.substrate.RuntimeInitializedClassBuildItem; -import io.quarkus.deployment.builditem.substrate.SubstrateConfigBuildItem; -import io.quarkus.deployment.builditem.substrate.SubstrateProxyDefinitionBuildItem; -import io.quarkus.deployment.builditem.substrate.SubstrateResourceBuildItem; -import io.quarkus.deployment.util.ServiceUtil; -import io.quarkus.resteasy.runtime.QuarkusInjectorFactory; -import io.quarkus.resteasy.runtime.ResteasyFilter; -import io.quarkus.resteasy.runtime.ResteasyTemplate; -import io.quarkus.resteasy.runtime.RolesFilterRegistrar; -import io.quarkus.runtime.annotations.ConfigItem; -import io.quarkus.runtime.annotations.ConfigRoot; -import io.quarkus.undertow.deployment.FilterBuildItem; -import io.quarkus.undertow.deployment.ServletBuildItem; -import io.quarkus.undertow.deployment.ServletInitParamBuildItem; - -/** - * Processor that finds JAX-RS classes in the deployment - * - * @author Stuart Douglas - */ -public class ResteasyScanningProcessor { - - private static final String JAVAX_WS_RS_APPLICATION = Application.class.getName(); - private static final String JAX_RS_FILTER_NAME = JAVAX_WS_RS_APPLICATION; - private static final String JAX_RS_SERVLET_NAME = JAVAX_WS_RS_APPLICATION; - private static final String JAX_RS_APPLICATION_PARAMETER_NAME = JAVAX_WS_RS_APPLICATION; - - private static final DotName APPLICATION_PATH = DotName.createSimple(ApplicationPath.class.getName()); - - private static final DotName PATH = DotName.createSimple(Path.class.getName()); - private static final DotName PROVIDER = DotName.createSimple(Provider.class.getName()); - private static final DotName DYNAMIC_FEATURE = DotName.createSimple(DynamicFeature.class.getName()); - private static final DotName CONTEXT = DotName.createSimple(Context.class.getName()); - - private static final DotName GET = DotName.createSimple(javax.ws.rs.GET.class.getName()); - private static final DotName HEAD = DotName.createSimple(javax.ws.rs.HEAD.class.getName()); - private static final DotName DELETE = DotName.createSimple(javax.ws.rs.DELETE.class.getName()); - private static final DotName OPTIONS = DotName.createSimple(javax.ws.rs.OPTIONS.class.getName()); - private static final DotName PATCH = DotName.createSimple(javax.ws.rs.PATCH.class.getName()); - private static final DotName POST = DotName.createSimple(javax.ws.rs.POST.class.getName()); - private static final DotName PUT = DotName.createSimple(javax.ws.rs.PUT.class.getName()); - - private static final DotName CONSUMES = DotName.createSimple(Consumes.class.getName()); - private static final DotName PRODUCES = DotName.createSimple(Produces.class.getName()); - - private static final DotName RESTEASY_QUERY_PARAM = DotName - .createSimple(org.jboss.resteasy.annotations.jaxrs.QueryParam.class.getName()); - private static final DotName RESTEASY_FORM_PARAM = DotName - .createSimple(org.jboss.resteasy.annotations.jaxrs.FormParam.class.getName()); - private static final DotName RESTEASY_COOKIE_PARAM = DotName - .createSimple(org.jboss.resteasy.annotations.jaxrs.CookieParam.class.getName()); - private static final DotName RESTEASY_PATH_PARAM = DotName - .createSimple(org.jboss.resteasy.annotations.jaxrs.PathParam.class.getName()); - private static final DotName RESTEASY_HEADER_PARAM = DotName - .createSimple(org.jboss.resteasy.annotations.jaxrs.HeaderParam.class.getName()); - private static final DotName RESTEASY_MATRIX_PARAM = DotName - .createSimple(org.jboss.resteasy.annotations.jaxrs.MatrixParam.class.getName()); - - private static final DotName JSONB_ANNOTATION = DotName.createSimple("javax.json.bind.annotation.JsonbAnnotation"); - - private static final Set TYPES_IGNORED_FOR_REFLECTION = new HashSet<>(Arrays.asList( - // javax.json - DotName.createSimple("javax.json.JsonObject"), - DotName.createSimple("javax.json.JsonArray"), - // JAX-RS - DotName.createSimple(Response.class.getName()), - DotName.createSimple(AsyncResponse.class.getName()))); - - private static final DotName[] METHOD_ANNOTATIONS = { - GET, - HEAD, - DELETE, - OPTIONS, - PATCH, - POST, - PUT, - }; - - private static final DotName[] RESTEASY_PARAM_ANNOTATIONS = { - RESTEASY_QUERY_PARAM, - RESTEASY_FORM_PARAM, - RESTEASY_COOKIE_PARAM, - RESTEASY_PATH_PARAM, - RESTEASY_HEADER_PARAM, - RESTEASY_MATRIX_PARAM, - }; - - private static final ProviderDiscoverer[] PROVIDER_DISCOVERERS = { - new ProviderDiscoverer(GET, false, true), - new ProviderDiscoverer(HEAD, false, false), - new ProviderDiscoverer(DELETE, true, false), - new ProviderDiscoverer(OPTIONS, false, true), - new ProviderDiscoverer(PATCH, true, false), - new ProviderDiscoverer(POST, true, true), - new ProviderDiscoverer(PUT, true, false) - }; - private static final DotName SINGLETON_SCOPE = DotName.createSimple(Singleton.class.getName()); - - /** - * JAX-RS configuration. - */ - ResteasyConfig resteasyConfig; - - @ConfigRoot - static final class ResteasyConfig { - /** - * If this is true then JAX-RS will use only a single instance of a resource - * class to service all requests. - *

- * If this is false then it will create a new instance of the resource per - * request. - *

- * If the resource class has an explicit CDI scope annotation then the value of - * this annotation will always be used to control the lifecycle of the resource - * class. - *

- * IMPLEMENTATION NOTE: {@code javax.ws.rs.Path} turns into a CDI stereotype - * with singleton scope. As a result, if a user annotates a JAX-RS resource with - * a stereotype which has a different default scope the deployment fails with - * IllegalStateException. - */ - @ConfigItem(defaultValue = "true") - boolean singletonResources; - - /** - * Enable gzip support for JAX-RS services. - */ - @ConfigItem - boolean enableGzip; - - /** - * Set this to override the default path for JAX-RS resources if there are no - * annotated application classes. - */ - @ConfigItem(defaultValue = "/") - String path; - } - - private static final Logger log = Logger.getLogger("io.quarkus.resteasy"); - - @BuildStep - io.quarkus.resteasy.deployment.ResteasyJaxrsConfig exportConfig() { - return new io.quarkus.resteasy.deployment.ResteasyJaxrsConfig(resteasyConfig.path); - } - - @BuildStep - SubstrateConfigBuildItem config() { - return SubstrateConfigBuildItem.builder() - .addResourceBundle("messages") - .build(); - } - - @BuildStep - void scanForProviders(BuildProducer providers, CombinedIndexBuildItem indexBuildItem, - BuildProducer unremovableBeans) { - for (AnnotationInstance i : indexBuildItem.getIndex().getAnnotations(PROVIDER)) { - if (i.target().kind() == AnnotationTarget.Kind.CLASS) { - providers.produce(new ResteasyJaxrsProviderBuildItem(i.target().asClass().name().toString())); - } - } - // Providers should never be removed - unremovableBeans.produce(new UnremovableBeanBuildItem(new BeanClassAnnotationExclusion(PROVIDER))); - } - - @BuildStep - public void build( - BuildProducer feature, - BuildProducer reflectiveClass, - BuildProducer reflectiveHierarchy, - BuildProducer proxyDefinition, - BuildProducer resource, - BuildProducer runtimeClasses, - BuildProducer filterProducer, - BuildProducer servletProducer, - BuildProducer servletContextParams, - CombinedIndexBuildItem combinedIndexBuildItem) throws Exception { - feature.produce(new FeatureBuildItem(FeatureBuildItem.RESTEASY)); - - IndexView index = combinedIndexBuildItem.getIndex(); - - resource.produce(new SubstrateResourceBuildItem("META-INF/services/javax.ws.rs.client.ClientBuilder")); - - Collection app = index.getAnnotations(APPLICATION_PATH); - //@Context uses proxies for interface injection - for (AnnotationInstance annotation : index.getAnnotations(CONTEXT)) { - DotName typeName = null; - if (annotation.target().kind() == AnnotationTarget.Kind.METHOD) { - MethodInfo method = annotation.target().asMethod(); - if (method.parameters().size() == 1) { - typeName = method.parameters().get(0).name(); - } - } else if (annotation.target().kind() == AnnotationTarget.Kind.FIELD) { - typeName = annotation.target().asField().type().name(); - } else if (annotation.target().kind() == AnnotationTarget.Kind.METHOD_PARAMETER) { - int pos = annotation.target().asMethodParameter().position(); - typeName = annotation.target().asMethodParameter().method().parameters().get(pos).name(); - } - if (typeName != null) { - ClassInfo type = index.getClassByName(typeName); - if (type != null) { - if (Modifier.isInterface(type.flags())) { - proxyDefinition.produce(new SubstrateProxyDefinitionBuildItem(type.toString())); - } - } else { - //might be a framework class, which should be loadable - try { - Class typeClass = Class.forName(typeName.toString()); - if (typeClass.isInterface()) { - proxyDefinition.produce(new SubstrateProxyDefinitionBuildItem(typeName.toString())); - } - } catch (Exception e) { - //ignore - } - } - } - } - - for (ClassInfo implementation : index.getAllKnownImplementors(DYNAMIC_FEATURE)) { - reflectiveClass.produce(new ReflectiveClassBuildItem(false, false, implementation.name().toString())); - } - - //currently we only examine the first class that is annotated with @ApplicationPath so best - //fail if there the user code has multiple such annotations instead of surprising the user - //at runtime - if (app.size() > 1) { - StringBuilder sb = new StringBuilder(); - boolean first = true; - for (AnnotationInstance annotationInstance : app) { - if (first) { - first = false; - } else { - sb.append(","); - } - sb.append(annotationInstance.target().asClass().name().toString()); - } - throw new RuntimeException("Multiple classes ( " + sb.toString() - + ") have been annotated with @ApplicationPath which is currently not supported"); - } - String mappingPath; - String path = null; - String appClass = null; - if (!app.isEmpty()) { - AnnotationInstance appPath = app.iterator().next(); - path = appPath.value().asString(); - appClass = appPath.target().asClass().name().toString(); - } else { - path = resteasyConfig.path; - } - if (path.endsWith("/")) { - mappingPath = path + "*"; - } else { - mappingPath = path + "/*"; - } - - Collection paths = index.getAnnotations(PATH); - if (paths != null && !paths.isEmpty()) { - - //if JAX-RS is installed at the root location we use a filter, otherwise we use a Servlet and take over the whole mapped path - if (path.equals("/")) { - filterProducer - .produce(FilterBuildItem.builder(JAX_RS_FILTER_NAME, ResteasyFilter.class.getName()).setLoadOnStartup(1) - .addFilterServletNameMapping("default", DispatcherType.REQUEST).setAsyncSupported(true) - .build()); - reflectiveClass.produce(new ReflectiveClassBuildItem(false, false, ResteasyFilter.class.getName())); - } else { - servletProducer.produce(ServletBuildItem.builder(JAX_RS_SERVLET_NAME, HttpServlet30Dispatcher.class.getName()) - .setLoadOnStartup(1).addMapping(mappingPath).setAsyncSupported(true).build()); - reflectiveClass.produce(new ReflectiveClassBuildItem(false, false, HttpServlet30Dispatcher.class.getName())); - } - - Set resources = new HashSet<>(); - Set pathInterfaces = new HashSet<>(); - for (AnnotationInstance annotation : paths) { - if (annotation.target().kind() == AnnotationTarget.Kind.CLASS) { - ClassInfo clazz = annotation.target().asClass(); - if (!Modifier.isInterface(clazz.flags())) { - String className = clazz.name().toString(); - resources.add(className); - reflectiveClass.produce(new ReflectiveClassBuildItem(true, true, className)); - } else { - pathInterfaces.add(clazz.name()); - } - } - } - - // look for all implementations of interfaces annotated @Path - for (final DotName iface : pathInterfaces) { - final Collection implementors = index.getAllKnownImplementors(iface); - for (final ClassInfo implementor : implementors) { - String className = implementor.name().toString(); - reflectiveClass.produce(new ReflectiveClassBuildItem(true, true, className)); - resources.add(className); - } - } - - if (!resources.isEmpty()) { - servletContextParams.produce( - new ServletInitParamBuildItem(ResteasyContextParameters.RESTEASY_SCANNED_RESOURCES, - String.join(",", resources))); - } - servletContextParams.produce(new ServletInitParamBuildItem("resteasy.servlet.mapping.prefix", path)); - if (appClass != null) { - servletContextParams.produce(new ServletInitParamBuildItem(JAX_RS_APPLICATION_PARAMETER_NAME, appClass)); - } - } else { - // no @Application class and no detected @Path resources, bail out - return; - } - - OUTER: for (DotName annotationType : RESTEASY_PARAM_ANNOTATIONS) { - Collection instances = index.getAnnotations(annotationType); - for (AnnotationInstance instance : instances) { - MethodParameterInfo param = instance.target().asMethodParameter(); - if (param.name() == null) { - log.warnv( - "Detected RESTEasy annotation {0} on method parameter {1}.{2} with no name. Either specify its name," - + " or tell your compiler to enable debug info (-g) or parameter names (-parameters). This message is only" - + " logged for the first such parameter.", - instance.name(), - param.method().declaringClass(), param.method().name()); - break OUTER; - } - } - } - - registerReflectionForSerialization(reflectiveClass, reflectiveHierarchy, combinedIndexBuildItem); - } - - @Record(STATIC_INIT) - @BuildStep - void registerProviders(BuildProducer reflectiveClass, - BuildProducer servletContextParams, - CombinedIndexBuildItem combinedIndexBuildItem, - List contributedProviderBuildItems) throws Exception { - IndexView index = combinedIndexBuildItem.getIndex(); - - Set contributedProviders = new HashSet<>(); - for (ResteasyJaxrsProviderBuildItem contributedProviderBuildItem : contributedProviderBuildItems) { - contributedProviders.add(contributedProviderBuildItem.getName()); - } - - Set availableProviders = ServiceUtil.classNamesNamedIn(getClass().getClassLoader(), - "META-INF/services/" + Providers.class.getName()); - - MediaTypeMap categorizedReaders = new MediaTypeMap<>(); - MediaTypeMap categorizedWriters = new MediaTypeMap<>(); - MediaTypeMap categorizedContextResolvers = new MediaTypeMap<>(); - Set otherProviders = new HashSet<>(); - - categorizeProviders(availableProviders, categorizedReaders, categorizedWriters, categorizedContextResolvers, - otherProviders); - - Set providersToRegister = new HashSet<>(); - - // add the other providers detected - providersToRegister.addAll(otherProviders); - - // find the providers declared in our services - boolean useBuiltinProviders = collectDeclaredProviders(providersToRegister, categorizedReaders, categorizedWriters, - categorizedContextResolvers, index); - - // If GZIP support is enabled, enable it - if (resteasyConfig.enableGzip) { - providersToRegister.add(AcceptEncodingGZIPFilter.class.getName()); - providersToRegister.add(GZIPDecodingInterceptor.class.getName()); - providersToRegister.add(GZIPEncodingInterceptor.class.getName()); - } - - if (useBuiltinProviders) { - // if we find a wildcard media type, we just use the built-in providers - servletContextParams.produce(new ServletInitParamBuildItem("resteasy.use.builtin.providers", "true")); - if (!contributedProviders.isEmpty()) { - servletContextParams.produce(new ServletInitParamBuildItem("resteasy.providers", - contributedProviders.stream().collect(Collectors.joining(",")))); - } - - providersToRegister = new HashSet<>(contributedProviders); - providersToRegister.addAll(availableProviders); - } else { - providersToRegister.addAll(contributedProviders); - servletContextParams.produce(new ServletInitParamBuildItem("resteasy.use.builtin.providers", "false")); - servletContextParams.produce(new ServletInitParamBuildItem("resteasy.providers", - providersToRegister.stream().collect(Collectors.joining(",")))); - } - - // register the providers for reflection - for (String providerToRegister : providersToRegister) { - reflectiveClass.produce(new ReflectiveClassBuildItem(false, false, providerToRegister)); - } - } - - @Record(STATIC_INIT) - @BuildStep - void setupInjection(ResteasyTemplate template, - BuildProducer servletContextParams, - BeanContainerBuildItem beanContainerBuildItem, - List proxyUnwrappers) { - - List> unwrappers = new ArrayList<>(); - for (ProxyUnwrapperBuildItem i : proxyUnwrappers) { - unwrappers.add(i.getUnwrapper()); - } - template.setupIntegration(beanContainerBuildItem.getValue(), unwrappers); - - servletContextParams - .produce(new ServletInitParamBuildItem("resteasy.injector.factory", QuarkusInjectorFactory.class.getName())); - } - - @BuildStep - List beanDefiningAnnotations() { - return Collections.singletonList( - new BeanDefiningAnnotationBuildItem(PATH, resteasyConfig.singletonResources ? SINGLETON_SCOPE : null)); - } - - /** - * Install the JAXRS security provider - * - * @param providers - the JaxrsProviderBuildItem providers producer to use - */ - @BuildStep - void setupFilter(BuildProducer providers) { - providers.produce(new ResteasyJaxrsProviderBuildItem(RolesFilterRegistrar.class.getName())); - } - - private void registerReflectionForSerialization(BuildProducer reflectiveClass, - BuildProducer reflectiveHierarchy, - CombinedIndexBuildItem combinedIndexBuildItem) { - IndexView index = combinedIndexBuildItem.getIndex(); - - // required by Jackson - reflectiveClass.produce(new ReflectiveClassBuildItem(true, false, - "com.fasterxml.jackson.module.jaxb.JaxbAnnotationIntrospector", - "com.fasterxml.jackson.databind.ser.std.SqlDateSerializer")); - - // This is probably redundant with the automatic resolution we do just below but better be safe - for (AnnotationInstance annotation : index.getAnnotations(JSONB_ANNOTATION)) { - if (annotation.target().kind() == AnnotationTarget.Kind.CLASS) { - reflectiveClass - .produce(new ReflectiveClassBuildItem(true, true, annotation.target().asClass().name().toString())); - } - } - - // Declare reflection for all the types implicated in the Rest end points (return types and parameters). - // It might be needed for serialization. - for (DotName annotationType : METHOD_ANNOTATIONS) { - Collection instances = index.getAnnotations(annotationType); - for (AnnotationInstance instance : instances) { - MethodInfo method = instance.target().asMethod(); - if (isReflectionDeclarationRequiredFor(method.returnType())) { - reflectiveHierarchy.produce(new ReflectiveHierarchyBuildItem(method.returnType())); - } - for (short i = 0; i < method.parameters().size(); i++) { - Type parameterType = method.parameters().get(i); - if (isReflectionDeclarationRequiredFor(parameterType) && !hasAnnotation(method, i, CONTEXT)) { - reflectiveHierarchy.produce(new ReflectiveHierarchyBuildItem(parameterType)); - } - } - } - } - - // In the case of a constraint violation, these elements might be returned as entities and will be serialized - reflectiveClass.produce(new ReflectiveClassBuildItem(true, true, ViolationReport.class.getName())); - reflectiveClass.produce(new ReflectiveClassBuildItem(true, true, ResteasyConstraintViolation.class.getName())); - } - - private static void categorizeProviders(Set availableProviders, MediaTypeMap categorizedReaders, - MediaTypeMap categorizedWriters, MediaTypeMap categorizedContextResolvers, - Set otherProviders) { - for (String availableProvider : availableProviders) { - try { - Class providerClass = Class.forName(availableProvider); - if (MessageBodyReader.class.isAssignableFrom(providerClass) - || MessageBodyWriter.class.isAssignableFrom(providerClass)) { - if (MessageBodyReader.class.isAssignableFrom(providerClass)) { - Consumes consumes = providerClass.getAnnotation(Consumes.class); - if (consumes != null) { - for (String consumesMediaType : consumes.value()) { - categorizedReaders.add(MediaType.valueOf(consumesMediaType), providerClass.getName()); - } - } else { - categorizedReaders.add(MediaType.WILDCARD_TYPE, providerClass.getName()); - } - } - if (MessageBodyWriter.class.isAssignableFrom(providerClass)) { - Produces produces = providerClass.getAnnotation(Produces.class); - if (produces != null) { - for (String producesMediaType : produces.value()) { - categorizedWriters.add(MediaType.valueOf(producesMediaType), providerClass.getName()); - } - } else { - categorizedWriters.add(MediaType.WILDCARD_TYPE, providerClass.getName()); - } - } - } else if (ContextResolver.class.isAssignableFrom(providerClass)) { - Produces produces = providerClass.getAnnotation(Produces.class); - if (produces != null) { - for (String producesMediaType : produces.value()) { - categorizedContextResolvers.add(MediaType.valueOf(producesMediaType), - providerClass.getName()); - } - } else { - categorizedContextResolvers.add(MediaType.WILDCARD_TYPE, providerClass.getName()); - } - } else { - otherProviders.add(providerClass.getName()); - } - } catch (ClassNotFoundException e) { - // Ignore - } - } - } - - private static boolean collectDeclaredProviders(Set providersToRegister, - MediaTypeMap categorizedReaders, MediaTypeMap categorizedWriters, - MediaTypeMap categorizedContextResolvers, IndexView index) { - for (ProviderDiscoverer providerDiscoverer : PROVIDER_DISCOVERERS) { - Collection getMethods = index.getAnnotations(providerDiscoverer.getMethodAnnotation()); - for (AnnotationInstance getMethod : getMethods) { - MethodInfo methodTarget = getMethod.target().asMethod(); - if (collectDeclaredProvidersForMethodAndMediaTypeAnnotation(providersToRegister, categorizedReaders, - methodTarget, CONSUMES, providerDiscoverer.noConsumesDefaultsToAll())) { - return true; - } - if (collectDeclaredProvidersForMethodAndMediaTypeAnnotation(providersToRegister, categorizedWriters, - methodTarget, PRODUCES, providerDiscoverer.noProducesDefaultsToAll())) { - return true; - } - if (collectDeclaredProvidersForMethodAndMediaTypeAnnotation(providersToRegister, - categorizedContextResolvers, methodTarget, PRODUCES, - providerDiscoverer.noProducesDefaultsToAll())) { - return true; - } - } - } - - return false; - } - - private static boolean collectDeclaredProvidersForMethodAndMediaTypeAnnotation(Set providersToRegister, - MediaTypeMap categorizedProviders, MethodInfo methodTarget, DotName mediaTypeAnnotation, - boolean defaultsToAll) { - AnnotationInstance mediaTypeAnnotationInstance = methodTarget.annotation(mediaTypeAnnotation); - if (mediaTypeAnnotationInstance == null) { - // let's consider the class - Collection classAnnotations = methodTarget.declaringClass().classAnnotations(); - for (AnnotationInstance classAnnotation : classAnnotations) { - if (mediaTypeAnnotation.equals(classAnnotation.name())) { - if (collectDeclaredProvidersForMediaTypeAnnotationInstance(providersToRegister, categorizedProviders, - classAnnotation)) { - return true; - } - return false; - } - } - return defaultsToAll; - } - if (collectDeclaredProvidersForMediaTypeAnnotationInstance(providersToRegister, categorizedProviders, - mediaTypeAnnotationInstance)) { - return true; - } - - return false; - } - - private static boolean collectDeclaredProvidersForMediaTypeAnnotationInstance(Set providersToRegister, - MediaTypeMap categorizedProviders, AnnotationInstance mediaTypeAnnotationInstance) { - for (String media : mediaTypeAnnotationInstance.value().asStringArray()) { - MediaType mediaType = MediaType.valueOf(media); - if (MediaType.WILDCARD_TYPE.equals(mediaType)) { - // exit early if we have the wildcard type - return true; - } - providersToRegister.addAll(categorizedProviders.getPossible(mediaType)); - } - return false; - } - - private static boolean isReflectionDeclarationRequiredFor(Type type) { - DotName className = getClassName(type); - - return className != null && !TYPES_IGNORED_FOR_REFLECTION.contains(className); - } - - private static DotName getClassName(Type type) { - switch (type.kind()) { - case CLASS: - case PARAMETERIZED_TYPE: - return type.name(); - case ARRAY: - return getClassName(type.asArrayType().component()); - default: - return null; - } - } - - private static boolean hasAnnotation(MethodInfo method, short paramPosition, DotName annotation) { - for (AnnotationInstance annotationInstance : method.annotations()) { - AnnotationTarget target = annotationInstance.target(); - if (target != null && target.kind() == Kind.METHOD_PARAMETER - && target.asMethodParameter().position() == paramPosition - && annotationInstance.name().equals(annotation)) { - return true; - } - } - return false; - } - - private static class ProviderDiscoverer { - - private final DotName methodAnnotation; - - private final boolean noConsumesDefaultsToAll; - - private final boolean noProducesDefaultsToAll; - - private ProviderDiscoverer(DotName methodAnnotation, boolean noConsumesDefaultsToAll, - boolean noProducesDefaultsToAll) { - this.methodAnnotation = methodAnnotation; - this.noConsumesDefaultsToAll = noConsumesDefaultsToAll; - this.noProducesDefaultsToAll = noProducesDefaultsToAll; - } - - public DotName getMethodAnnotation() { - return methodAnnotation; - } - - public boolean noConsumesDefaultsToAll() { - return noConsumesDefaultsToAll; - } - - public boolean noProducesDefaultsToAll() { - return noProducesDefaultsToAll; - } - } -} diff --git a/extensions/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/ConstructorInjectionResource.java b/extensions/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/ConstructorInjectionResource.java new file mode 100644 index 0000000000000..1b78b0b158a0f --- /dev/null +++ b/extensions/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/ConstructorInjectionResource.java @@ -0,0 +1,19 @@ +package io.quarkus.resteasy.test; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; + +@Path("/ctor") +public class ConstructorInjectionResource { + + final Service service; + + public ConstructorInjectionResource(Service service) { + this.service = service; + } + + @GET + public String val() { + return service.execute(); + } +} diff --git a/extensions/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/ConstructorInjectionResourceTestCase.java b/extensions/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/ConstructorInjectionResourceTestCase.java new file mode 100644 index 0000000000000..65e2a713584ad --- /dev/null +++ b/extensions/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/ConstructorInjectionResourceTestCase.java @@ -0,0 +1,23 @@ +package io.quarkus.resteasy.test; + +import org.hamcrest.Matchers; +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 ConstructorInjectionResourceTestCase { + + @RegisterExtension + static QuarkusUnitTest runner = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(ConstructorInjectionResource.class, Service.class)); + + @Test + public void testConstructorInjectionResource() { + RestAssured.when().get("/ctor").then().body(Matchers.is("service")); + } +} diff --git a/extensions/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/RootResource.java b/extensions/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/RootResource.java index 508c6b1b80331..f1375d319dc14 100644 --- a/extensions/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/RootResource.java +++ b/extensions/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/RootResource.java @@ -10,5 +10,4 @@ public class RootResource { public String root() { return "Root Resource"; } - } diff --git a/extensions/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/Service.java b/extensions/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/Service.java new file mode 100644 index 0000000000000..b2ed70d0d1417 --- /dev/null +++ b/extensions/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/Service.java @@ -0,0 +1,11 @@ +package io.quarkus.resteasy.test; + +import javax.inject.Singleton; + +@Singleton +public class Service { + + String execute() { + return "service"; + } +} diff --git a/extensions/resteasy/runtime/pom.xml b/extensions/resteasy/runtime/pom.xml index 34cec06725538..f440f762f4a91 100644 --- a/extensions/resteasy/runtime/pom.xml +++ b/extensions/resteasy/runtime/pom.xml @@ -26,48 +26,25 @@ 4.0.0 - quarkus-resteasy-runtime + quarkus-resteasy Quarkus - RESTEasy - Runtime - - com.oracle.substratevm - svm - - - io.quarkus - quarkus-core-runtime - - - io.quarkus - quarkus-undertow-runtime - io.quarkus - quarkus-arc-runtime - - - io.quarkus - quarkus-resteasy-common-runtime - - - javax.validation - validation-api - - - com.sun.activation - jakarta.activation + quarkus-undertow io.quarkus - quarkus-jaxb-runtime + quarkus-resteasy-server-common - maven-dependency-plugin + io.quarkus + quarkus-bootstrap-maven-plugin maven-compiler-plugin diff --git a/extensions/scheduler/deployment/pom.xml b/extensions/scheduler/deployment/pom.xml index d5469deacc1a1..643b45232e10a 100644 --- a/extensions/scheduler/deployment/pom.xml +++ b/extensions/scheduler/deployment/pom.xml @@ -11,28 +11,28 @@ 4.0.0 - quarkus-scheduler + quarkus-scheduler-deployment Quarkus - Scheduler - Deployment io.quarkus - quarkus-core + quarkus-core-deployment io.quarkus - quarkus-arc + quarkus-arc-deployment io.quarkus - quarkus-scheduler-runtime + quarkus-scheduler - + io.quarkus quarkus-junit5-internal - + diff --git a/extensions/scheduler/deployment/src/main/java/io/quarkus/scheduler/deployment/ScheduledBusinessMethodItem.java b/extensions/scheduler/deployment/src/main/java/io/quarkus/scheduler/deployment/ScheduledBusinessMethodItem.java index d90f1a22f81e1..25bf30afd491f 100644 --- a/extensions/scheduler/deployment/src/main/java/io/quarkus/scheduler/deployment/ScheduledBusinessMethodItem.java +++ b/extensions/scheduler/deployment/src/main/java/io/quarkus/scheduler/deployment/ScheduledBusinessMethodItem.java @@ -17,11 +17,11 @@ import java.util.List; -import org.jboss.builder.item.MultiBuildItem; import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.MethodInfo; import io.quarkus.arc.processor.BeanInfo; +import io.quarkus.builder.item.MultiBuildItem; public final class ScheduledBusinessMethodItem extends MultiBuildItem { 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 ae49fac845fc2..219dcbd7dbcb8 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 @@ -64,6 +64,7 @@ import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.deployment.builditem.GeneratedClassBuildItem; import io.quarkus.deployment.builditem.substrate.ReflectiveClassBuildItem; +import io.quarkus.deployment.logging.LogCleanupFilterBuildItem; import io.quarkus.deployment.util.HashUtil; import io.quarkus.gizmo.ClassCreator; import io.quarkus.gizmo.ClassOutput; @@ -93,11 +94,8 @@ public class SchedulerProcessor { static final String INVOKER_SUFFIX = "_ScheduledInvoker"; @BuildStep - List beans() { - List beans = new ArrayList<>(); - beans.add(new AdditionalBeanBuildItem(SchedulerConfiguration.class)); - beans.add(new AdditionalBeanBuildItem(QuartzScheduler.class)); - return beans; + AdditionalBeanBuildItem beans() { + return new AdditionalBeanBuildItem(SchedulerConfiguration.class, QuartzScheduler.class); } @BuildStep @@ -106,12 +104,13 @@ AnnotationsTransformerBuildItem annotationTransformer() { @Override public boolean appliesTo(org.jboss.jandex.AnnotationTarget.Kind kind) { - return kind == org.jboss.jandex.AnnotationTarget.Kind.CLASS; + return kind == org.jboss.jandex.AnnotationTarget.Kind.CLASS + || kind == org.jboss.jandex.AnnotationTarget.Kind.METHOD; } @Override public void transform(TransformationContext context) { - if (context.getAnnotations().isEmpty()) { + if (context.isClass() && context.getAnnotations().isEmpty()) { // Class with no annotations but with @Scheduled method if (context.getTarget().asClass().annotations().containsKey(SCHEDULED_NAME) || context.getTarget().asClass().annotations().containsKey(SCHEDULES_NAME)) { @@ -119,6 +118,13 @@ public void transform(TransformationContext context) { context.getTarget()); context.transform().add(Singleton.class).done(); } + } else if (context.isMethod()) { + MethodInfo method = context.getTarget().asMethod(); + if ((method.hasAnnotation(SCHEDULED_NAME) || method.hasAnnotation(SCHEDULES_NAME)) + && !method.hasAnnotation(DotNames.ACTIVATE_REQUEST_CONTEXT)) { + // Activate request context during a scheduled method invocation + context.transform().add(DotNames.ACTIVATE_REQUEST_CONTEXT).done(); + } } } }); @@ -173,13 +179,13 @@ public void validate(ValidationContext validationContext) { if (params.size() > 1 || (params.size() == 1 && !params.get(0).equals(SCHEDULED_EXECUTION_TYPE))) { throw new IllegalStateException(String.format( - "Invalid scheduled business method parameters %s [method: %s, bean:%s", params, + "Invalid scheduled business method parameters %s [method: %s, bean: %s]", params, method, bean)); } if (!method.returnType().kind().equals(Type.Kind.VOID)) { throw new IllegalStateException( - String.format("Scheduled business method must return void [method: %s, bean:%s", - method.returnType(), method, bean)); + String.format("Scheduled business method must return void [method: %s, bean: %s]", + method, bean)); } // Validate cron() and every() expressions for (AnnotationInstance scheduled : schedules) { @@ -235,6 +241,28 @@ public void write(String name, byte[] data) { template.registerSchedules(scheduleConfigurations, beanContainer.getValue()); } + @BuildStep + public void logCleanup(BuildProducer logCleanupFilter) { + logCleanupFilter.produce(new LogCleanupFilterBuildItem("org.quartz.impl.StdSchedulerFactory", + "Quartz scheduler version:", + // no need to log if it's the default + "Using default implementation for", + "Quartz scheduler 'DefaultQuartzScheduler'")); + + logCleanupFilter.produce(new LogCleanupFilterBuildItem("org.quartz.core.QuartzScheduler", + "Quartz Scheduler v", + "JobFactory set to:", + "Scheduler meta-data:", + // no need to log if it's the default + "Scheduler DefaultQuartzScheduler")); + + logCleanupFilter.produce(new LogCleanupFilterBuildItem("org.quartz.simpl.RAMJobStore", + "RAMJobStore initialized.")); + + logCleanupFilter.produce(new LogCleanupFilterBuildItem("org.quartz.core.SchedulerSignalerImpl", + "Initialized Scheduler Signaller of type")); + } + private String generateInvoker(BeanInfo bean, MethodInfo method, ClassOutput classOutput) { String baseName; diff --git a/extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/RequestContextJobs.java b/extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/RequestContextJobs.java new file mode 100644 index 0000000000000..36da7d8fd4f48 --- /dev/null +++ b/extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/RequestContextJobs.java @@ -0,0 +1,55 @@ +/* + * Copyright 2018 Red Hat, Inc. + * + * Licensed 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.scheduler.test; + +import java.util.concurrent.CountDownLatch; + +import javax.annotation.PostConstruct; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; + +import io.quarkus.scheduler.Scheduled; + +public class RequestContextJobs { + + static final CountDownLatch LATCH = new CountDownLatch(1); + + @Inject + RequestFoo foo; + + @Scheduled(every = "1s") + void checkEverySecond() { + foo.getName(); + LATCH.countDown(); + } + + @RequestScoped + static class RequestFoo { + + private String name; + + @PostConstruct + void init() { + name = "oof"; + } + + public String getName() { + return name; + } + + } + +} diff --git a/extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/RequestScheduledMethodTest.java b/extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/RequestScheduledMethodTest.java new file mode 100644 index 0000000000000..4034cc1375165 --- /dev/null +++ b/extensions/scheduler/deployment/src/test/java/io/quarkus/scheduler/test/RequestScheduledMethodTest.java @@ -0,0 +1,40 @@ +/* + * Copyright 2018 Red Hat, Inc. + * + * Licensed 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.scheduler.test; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.concurrent.TimeUnit; + +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 RequestScheduledMethodTest { + + @RegisterExtension + static final QuarkusUnitTest test = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class).addClasses(RequestContextJobs.class)); + + @Test + public void testRequestContextScheduledJobs() throws InterruptedException { + assertTrue(RequestContextJobs.LATCH.await(4, TimeUnit.SECONDS)); + } + +} diff --git a/extensions/scheduler/runtime/pom.xml b/extensions/scheduler/runtime/pom.xml index 3b13cf61ce412..1b55907c431c0 100644 --- a/extensions/scheduler/runtime/pom.xml +++ b/extensions/scheduler/runtime/pom.xml @@ -10,13 +10,13 @@ 4.0.0 - quarkus-scheduler-runtime + quarkus-scheduler Quarkus - Scheduler - Runtime io.quarkus - quarkus-arc-runtime + quarkus-arc org.slf4j @@ -51,13 +51,14 @@ true --> - + - maven-dependency-plugin + io.quarkus + quarkus-bootstrap-maven-plugin maven-compiler-plugin diff --git a/extensions/smallrye-fault-tolerance/deployment/pom.xml b/extensions/smallrye-fault-tolerance/deployment/pom.xml index ff4bd3a1ec7f9..6fc75cf49791f 100644 --- a/extensions/smallrye-fault-tolerance/deployment/pom.xml +++ b/extensions/smallrye-fault-tolerance/deployment/pom.xml @@ -14,59 +14,61 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - - - quarkus-smallrye-fault-tolerance-parent - io.quarkus - 999-SNAPSHOT - ../ - - 4.0.0 - - quarkus-smallrye-fault-tolerance - Quarkus - SmallRye Fault tolerance - Deployment + 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-smallrye-fault-tolerance-parent + io.quarkus + 999-SNAPSHOT + ../ + + 4.0.0 - - - io.quarkus - quarkus-core - - - io.quarkus - quarkus-arc - - - io.quarkus - quarkus-smallrye-metrics - - - io.quarkus - quarkus-smallrye-fault-tolerance-runtime - - - com.oracle.substratevm - svm - - + quarkus-smallrye-fault-tolerance-deployment + Quarkus - SmallRye Fault tolerance - Deployment - - - - maven-compiler-plugin - - - - io.quarkus - quarkus-extension-processor - ${project.version} - - - - - - + + + io.quarkus + quarkus-core-deployment + + + io.quarkus + quarkus-arc-deployment + + + io.quarkus + quarkus-smallrye-metrics-deployment + + + io.quarkus + quarkus-smallrye-fault-tolerance + + + com.oracle.substratevm + svm + + + io.quarkus + quarkus-junit5-internal + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + 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 87ed374fd23b6..7c05fd72c5f3a 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 @@ -40,12 +40,14 @@ import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem; import io.quarkus.arc.processor.AnnotationsTransformer; +import io.quarkus.arc.processor.BuiltinScope; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.CombinedIndexBuildItem; import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.deployment.builditem.substrate.ReflectiveClassBuildItem; import io.quarkus.deployment.builditem.substrate.SubstrateSystemPropertyBuildItem; +import io.quarkus.deployment.logging.LogCleanupFilterBuildItem; import io.quarkus.smallrye.faulttolerance.runtime.QuarkusFallbackHandlerProvider; import io.quarkus.smallrye.faulttolerance.runtime.QuarkusFaultToleranceOperationProvider; import io.smallrye.faulttolerance.DefaultCommandListenersProvider; @@ -53,7 +55,7 @@ import io.smallrye.faulttolerance.HystrixCommandBinding; import io.smallrye.faulttolerance.HystrixCommandInterceptor; import io.smallrye.faulttolerance.HystrixInitializer; -import io.smallrye.faulttolerance.MetricsCollectorFactory; +import io.smallrye.faulttolerance.metrics.MetricsCollectorFactory; public class SmallRyeFaultToleranceProcessor { @@ -91,25 +93,35 @@ public void build(BuildProducer annotationsTran nativeImageSystemProperty.produce(new SubstrateSystemPropertyBuildItem("rx.unsafe-disable", "true")); // Add reflective acccess to fallback handlers - Collection fallbackHandlers = index - .getAllKnownImplementors(DotName.createSimple(FallbackHandler.class.getName())); - for (ClassInfo fallbackHandler : fallbackHandlers) { - reflectiveClass.produce(new ReflectiveClassBuildItem(true, false, fallbackHandler.name().toString())); + Set fallbackHandlers = new HashSet<>(); + for (ClassInfo implementor : index + .getAllKnownImplementors(DotName.createSimple(FallbackHandler.class.getName()))) { + fallbackHandlers.add(implementor.name().toString()); } + if (!fallbackHandlers.isEmpty()) { + AdditionalBeanBuildItem.Builder fallbackHandlersBeans = AdditionalBeanBuildItem.builder() + .setDefaultScope(BuiltinScope.DEPENDENT.getName()); + for (String fallbackHandler : fallbackHandlers) { + reflectiveClass.produce(new ReflectiveClassBuildItem(true, false, fallbackHandler)); + fallbackHandlersBeans.addBeanClass(fallbackHandler); + } + additionalBean.produce(fallbackHandlersBeans.build()); + } + reflectiveClass.produce(new ReflectiveClassBuildItem(false, true, HystrixCircuitBreaker.Factory.class.getName())); for (DotName annotation : FT_ANNOTATIONS) { reflectiveClass.produce(new ReflectiveClassBuildItem(true, false, annotation.toString())); } // Add HystrixCommandBinding to app classes - Set ftClasses = new HashSet<>(); + Set ftClasses = new HashSet<>(); for (DotName annotation : FT_ANNOTATIONS) { Collection annotationInstances = index.getAnnotations(annotation); for (AnnotationInstance instance : annotationInstances) { if (instance.target().kind() == Kind.CLASS) { - ftClasses.add(instance.target().asClass().toString()); + ftClasses.add(instance.target().asClass().name()); } else if (instance.target().kind() == Kind.METHOD) { - ftClasses.add(instance.target().asMethod().declaringClass().toString()); + ftClasses.add(instance.target().asMethod().declaringClass().name()); } } } @@ -122,7 +134,7 @@ public boolean appliesTo(Kind kind) { @Override public void transform(TransformationContext context) { - if (ftClasses.contains(context.getTarget().asClass().name().toString())) { + if (ftClasses.contains(context.getTarget().asClass().name())) { context.transform().add(HystrixCommandBinding.class).done(); } } @@ -136,4 +148,17 @@ public void transform(TransformationContext context) { MetricsCollectorFactory.class)); } + @BuildStep + public void logCleanup(BuildProducer logCleanupFilter) { + logCleanupFilter.produce(new LogCleanupFilterBuildItem("io.smallrye.faulttolerance.HystrixInitializer", + "### Init Hystrix ###", + // no need to log the strategy if it is the default + "Hystrix concurrency strategy used: DefaultHystrixConcurrencyStrategy")); + logCleanupFilter.produce(new LogCleanupFilterBuildItem("io.smallrye.faulttolerance.DefaultHystrixConcurrencyStrategy", + "### Privilleged Thread Factory used ###")); + + logCleanupFilter.produce(new LogCleanupFilterBuildItem("com.netflix.config.sources.URLConfigurationSource", + "No URLs will be polled as dynamic configuration sources.", + "To enable URLs as dynamic configuration sources")); + } } diff --git a/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/fallback/FallbackBean.java b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/fallback/FallbackBean.java new file mode 100644 index 0000000000000..d19a966499d92 --- /dev/null +++ b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/fallback/FallbackBean.java @@ -0,0 +1,24 @@ +package io.quarkus.smallrye.faulttolerance.test.fallback; + +import javax.enterprise.context.ApplicationScoped; + +import org.eclipse.microprofile.faulttolerance.ExecutionContext; +import org.eclipse.microprofile.faulttolerance.Fallback; +import org.eclipse.microprofile.faulttolerance.FallbackHandler; + +@ApplicationScoped +public class FallbackBean { + + public static class RecoverFallback implements FallbackHandler { + @Override + public String handle(ExecutionContext context) { + return RecoverFallback.class.getName(); + } + } + + @Fallback(RecoverFallback.class) + public String ping() { + throw new RuntimeException(); + } + +} 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 new file mode 100644 index 0000000000000..7bf0e9c083608 --- /dev/null +++ b/extensions/smallrye-fault-tolerance/deployment/src/test/java/io/quarkus/smallrye/faulttolerance/test/fallback/FallbackTest.java @@ -0,0 +1,46 @@ +/* + * Copyright 2018 Red Hat, Inc. + * + * Licensed 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.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/runtime/pom.xml b/extensions/smallrye-fault-tolerance/runtime/pom.xml index 76aee731d0e45..126cf34a7bd77 100644 --- a/extensions/smallrye-fault-tolerance/runtime/pom.xml +++ b/extensions/smallrye-fault-tolerance/runtime/pom.xml @@ -14,62 +14,65 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - - - quarkus-smallrye-fault-tolerance-parent - io.quarkus - 999-SNAPSHOT - ../ - - 4.0.0 - - quarkus-smallrye-fault-tolerance-runtime - Quarkus - SmallRye Fault tolerance - Runtime + 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-smallrye-fault-tolerance-parent + io.quarkus + 999-SNAPSHOT + ../ + + 4.0.0 - - - io.quarkus - quarkus-core-runtime - - - io.smallrye - smallrye-fault-tolerance - - - - - org.eclipse.microprofile.metrics - microprofile-metrics-api - - - - - com.oracle.substratevm - svm - - + quarkus-smallrye-fault-tolerance + Quarkus - SmallRye Fault tolerance - Runtime - - - - maven-dependency-plugin - - - maven-compiler-plugin - - - - io.quarkus - quarkus-extension-processor - ${project.version} - - - - - - + + + io.quarkus + quarkus-core + + + io.smallrye + smallrye-fault-tolerance + + + + + org.eclipse.microprofile.metrics + microprofile-metrics-api + + + + + io.quarkus + quarkus-smallrye-metrics + + + com.oracle.substratevm + svm + + + + + + io.quarkus + quarkus-bootstrap-maven-plugin + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + diff --git a/extensions/smallrye-fault-tolerance/runtime/src/main/java/io/quarkus/smallrye/faulttolerance/runtime/QuarkusFallbackHandlerProvider.java b/extensions/smallrye-fault-tolerance/runtime/src/main/java/io/quarkus/smallrye/faulttolerance/runtime/QuarkusFallbackHandlerProvider.java index 2a5ddfa02619a..646dac2d42869 100644 --- a/extensions/smallrye-fault-tolerance/runtime/src/main/java/io/quarkus/smallrye/faulttolerance/runtime/QuarkusFallbackHandlerProvider.java +++ b/extensions/smallrye-fault-tolerance/runtime/src/main/java/io/quarkus/smallrye/faulttolerance/runtime/QuarkusFallbackHandlerProvider.java @@ -37,7 +37,7 @@ public class QuarkusFallbackHandlerProvider implements FallbackHandlerProvider { @Inject @Any - Instance instance; + Instance> instance; @Override public FallbackHandler get(FaultToleranceOperation operation) { @@ -46,7 +46,7 @@ public FallbackHandler get(FaultToleranceOperation operation) { @SuppressWarnings("unchecked") @Override public T handle(ExecutionContext context) { - Class clazz = operation.getFallback().get(FallbackConfig.VALUE); + Class> clazz = operation.getFallback().get(FallbackConfig.VALUE); FallbackHandler fallbackHandlerInstance = (FallbackHandler) instance.select(clazz).get(); try { return fallbackHandlerInstance.handle(context); diff --git a/extensions/smallrye-health/deployment/pom.xml b/extensions/smallrye-health/deployment/pom.xml index e2849e0757e9f..71100f407e78b 100644 --- a/extensions/smallrye-health/deployment/pom.xml +++ b/extensions/smallrye-health/deployment/pom.xml @@ -26,25 +26,29 @@ 4.0.0 - quarkus-smallrye-health + quarkus-smallrye-health-deployment Quarkus - SmallRye Health - Deployment io.quarkus - quarkus-core + quarkus-core-deployment io.quarkus - quarkus-arc + quarkus-arc-deployment io.quarkus - quarkus-undertow + quarkus-undertow-deployment io.quarkus - quarkus-smallrye-health-runtime + quarkus-jsonp-deployment + + + io.quarkus + quarkus-smallrye-health diff --git a/extensions/smallrye-health/runtime/pom.xml b/extensions/smallrye-health/runtime/pom.xml index f65313afa56e2..bdd8448c8a8c1 100644 --- a/extensions/smallrye-health/runtime/pom.xml +++ b/extensions/smallrye-health/runtime/pom.xml @@ -26,7 +26,7 @@ 4.0.0 - quarkus-smallrye-health-runtime + quarkus-smallrye-health Quarkus - SmallRye Health - Runtime @@ -37,15 +37,15 @@ io.quarkus - quarkus-core-runtime + quarkus-core io.quarkus - quarkus-undertow-runtime + quarkus-undertow - org.glassfish - javax.json + io.quarkus + quarkus-jsonp org.jboss.spec.javax.servlet @@ -56,7 +56,8 @@ - maven-dependency-plugin + io.quarkus + quarkus-bootstrap-maven-plugin maven-compiler-plugin diff --git a/extensions/smallrye-jwt/deployment/pom.xml b/extensions/smallrye-jwt/deployment/pom.xml index c20e22e4dfa1b..94f5944ec2839 100644 --- a/extensions/smallrye-jwt/deployment/pom.xml +++ b/extensions/smallrye-jwt/deployment/pom.xml @@ -26,7 +26,7 @@ 4.0.0 - quarkus-smallrye-jwt + quarkus-smallrye-jwt-deployment Quarkus - SmallRye JWT - Deployment @@ -36,23 +36,27 @@ io.quarkus - quarkus-core + quarkus-core-deployment io.quarkus - quarkus-arc + quarkus-arc-deployment io.quarkus - quarkus-undertow + quarkus-undertow-deployment io.quarkus - quarkus-elytron-security + quarkus-jsonp-deployment io.quarkus - quarkus-smallrye-jwt-runtime + quarkus-elytron-security-deployment + + + io.quarkus + quarkus-smallrye-jwt @@ -63,7 +67,7 @@ io.quarkus - quarkus-resteasy-jsonb-runtime + quarkus-resteasy-jsonb io.rest-assured 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 aff8b7ce8a992..44f550f1a2506 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 @@ -23,6 +23,7 @@ import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.deployment.BeanContainerBuildItem; +import io.quarkus.deployment.QuarkusConfig; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.annotations.ExecutionTime; @@ -30,18 +31,15 @@ import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.deployment.builditem.ObjectSubstitutionBuildItem; import io.quarkus.deployment.builditem.substrate.ReflectiveClassBuildItem; +import io.quarkus.deployment.builditem.substrate.SubstrateResourceBuildItem; import io.quarkus.elytron.security.deployment.AuthConfigBuildItem; import io.quarkus.elytron.security.deployment.IdentityManagerBuildItem; +import io.quarkus.elytron.security.deployment.JCAProviderBuildItem; import io.quarkus.elytron.security.deployment.SecurityDomainBuildItem; import io.quarkus.elytron.security.deployment.SecurityRealmBuildItem; import io.quarkus.elytron.security.runtime.AuthConfig; import io.quarkus.runtime.RuntimeValue; -import io.quarkus.smallrye.jwt.runtime.ClaimValueProducer; -import io.quarkus.smallrye.jwt.runtime.CommonJwtProducer; import io.quarkus.smallrye.jwt.runtime.JWTAuthContextInfoGroup; -import io.quarkus.smallrye.jwt.runtime.JsonValueProducer; -import io.quarkus.smallrye.jwt.runtime.PrincipalProducer; -import io.quarkus.smallrye.jwt.runtime.RawClaimTypeProducer; import io.quarkus.smallrye.jwt.runtime.SmallRyeJwtTemplate; import io.quarkus.smallrye.jwt.runtime.auth.ClaimAttributes; import io.quarkus.smallrye.jwt.runtime.auth.ElytronJwtCallerPrincipal; @@ -50,6 +48,11 @@ import io.quarkus.smallrye.jwt.runtime.auth.PublicKeyProxy; import io.quarkus.smallrye.jwt.runtime.auth.PublicKeySubstitution; import io.quarkus.undertow.deployment.ServletExtensionBuildItem; +import io.smallrye.jwt.auth.cdi.ClaimValueProducer; +import io.smallrye.jwt.auth.cdi.CommonJwtProducer; +import io.smallrye.jwt.auth.cdi.JsonValueProducer; +import io.smallrye.jwt.auth.cdi.PrincipalProducer; +import io.smallrye.jwt.auth.cdi.RawClaimTypeProducer; import io.smallrye.jwt.config.JWTAuthContextInfoProvider; import io.undertow.security.idm.IdentityManager; import io.undertow.servlet.ServletExtension; @@ -69,26 +72,47 @@ class SmallRyeJwtProcessor { */ @BuildStep void registerAdditionalBeans(BuildProducer additionalBeans) { - additionalBeans.produce(new AdditionalBeanBuildItem(JWTAuthContextInfoProvider.class)); - additionalBeans.produce(new AdditionalBeanBuildItem(false, MpJwtValidator.class)); - additionalBeans.produce(new AdditionalBeanBuildItem(false, JWTAuthMethodExtension.class)); - additionalBeans.produce(new AdditionalBeanBuildItem(CommonJwtProducer.class)); - additionalBeans.produce(new AdditionalBeanBuildItem(RawClaimTypeProducer.class)); - additionalBeans.produce(new AdditionalBeanBuildItem(PrincipalProducer.class)); - additionalBeans.produce(new AdditionalBeanBuildItem(ClaimValueProducer.class)); - additionalBeans.produce(new AdditionalBeanBuildItem(JsonValueProducer.class)); + AdditionalBeanBuildItem.Builder unremovable = AdditionalBeanBuildItem.builder().setUnremovable(); + unremovable.addBeanClass(MpJwtValidator.class); + unremovable.addBeanClass(JWTAuthMethodExtension.class); + additionalBeans.produce(unremovable.build()); + AdditionalBeanBuildItem.Builder removable = AdditionalBeanBuildItem.builder(); + removable.addBeanClass(JWTAuthContextInfoProvider.class); + removable.addBeanClass(CommonJwtProducer.class); + removable.addBeanClass(RawClaimTypeProducer.class); + removable.addBeanClass(PrincipalProducer.class); + removable.addBeanClass(ClaimValueProducer.class); + removable.addBeanClass(JsonValueProducer.class); + additionalBeans.produce(removable.build()); } /** * Register this extension as a MP-JWT feature * - * @return + * @return FeatureBuildItem */ @BuildStep FeatureBuildItem feature() { return new FeatureBuildItem(FeatureBuildItem.SMALLRYE_JWT); } + /** + * If the configuration specified a deployment local key resource, register it with substrate + * + * @return SubstrateResourceBuildItem + */ + @BuildStep + SubstrateResourceBuildItem registerSubstrateResources() { + String publicKeyLocation = QuarkusConfig.getString("mp.jwt.verify.publickey.location", null, true); + if (publicKeyLocation != null) { + if (publicKeyLocation.indexOf(':') < 0 || publicKeyLocation.startsWith("classpath:")) { + log.infof("Adding %s to native image", publicKeyLocation); + return new SubstrateResourceBuildItem(publicKeyLocation); + } + } + return null; + } + /** * Configure a TokenSecurityRealm if enabled * @@ -159,4 +183,15 @@ ServletExtensionBuildItem registerJwtAuthExtension(SmallRyeJwtTemplate template, ServletExtensionBuildItem sebi = new ServletExtensionBuildItem(authExt); return sebi; } + + /** + * Register the SHA256withRSA signature provider + * + * @return JCAProviderBuildItem for SHA256withRSA signature provider + */ + @BuildStep + @Record(ExecutionTime.STATIC_INIT) + JCAProviderBuildItem registerRSASigProvider() { + return new JCAProviderBuildItem(config.rsaSigProvider); + } } diff --git a/extensions/smallrye-jwt/deployment/src/test/java/io/quarkus/jwt/test/DefaultGroupsEndpoint.java b/extensions/smallrye-jwt/deployment/src/test/java/io/quarkus/jwt/test/DefaultGroupsEndpoint.java new file mode 100644 index 0000000000000..b07bd6ba8b175 --- /dev/null +++ b/extensions/smallrye-jwt/deployment/src/test/java/io/quarkus/jwt/test/DefaultGroupsEndpoint.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2016-2017 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * Licensed 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.jwt.test; + +import javax.annotation.security.DenyAll; +import javax.annotation.security.RolesAllowed; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; + +import org.eclipse.microprofile.jwt.JsonWebToken; + +@Path("/endp") +@DenyAll +@RequestScoped +public class DefaultGroupsEndpoint { + + @Inject + JsonWebToken jwtPrincipal; + + @GET + @Path("/echo") + @RolesAllowed("User") + public String echoGroups() { + return jwtPrincipal.getGroups().stream().reduce("", String::concat); + } +} diff --git a/extensions/smallrye-jwt/deployment/src/test/java/io/quarkus/jwt/test/DefaultGroupsUnitTest.java b/extensions/smallrye-jwt/deployment/src/test/java/io/quarkus/jwt/test/DefaultGroupsUnitTest.java new file mode 100644 index 0000000000000..4621475751c2b --- /dev/null +++ b/extensions/smallrye-jwt/deployment/src/test/java/io/quarkus/jwt/test/DefaultGroupsUnitTest.java @@ -0,0 +1,52 @@ +package io.quarkus.jwt.test; + +import java.net.HttpURLConnection; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +public class DefaultGroupsUnitTest { + private static Class[] testClasses = { + DefaultGroupsEndpoint.class + }; + /** + * The test generated JWT token string + */ + private String token; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(testClasses) + .addAsResource("applicationDefaultGroups.properties", "application.properties")); + + @BeforeEach + public void generateToken() throws Exception { + token = TokenUtils.generateTokenString("/TokenNoGroups.json"); + } + + /** + * Validate a request with MP-JWT without a 'groups' claim is successful + * due to the default value being provided in the configuration + * + * @throws Exception + */ + @Test + public void echoGroups() throws Exception { + io.restassured.response.Response response = RestAssured.given().auth() + .oauth2(token) + .get("/endp/echo").andReturn(); + + Assertions.assertEquals(HttpURLConnection.HTTP_OK, response.getStatusCode()); + String replyString = response.body().asString(); + // The missing 'groups' claim's default value, 'User' is expected + Assertions.assertEquals("User", replyString); + } +} diff --git a/extensions/smallrye-jwt/deployment/src/test/java/io/quarkus/jwt/test/DefaultScopedEndpoint.java b/extensions/smallrye-jwt/deployment/src/test/java/io/quarkus/jwt/test/DefaultScopedEndpoint.java new file mode 100644 index 0000000000000..ce7ff7db0fbac --- /dev/null +++ b/extensions/smallrye-jwt/deployment/src/test/java/io/quarkus/jwt/test/DefaultScopedEndpoint.java @@ -0,0 +1,60 @@ +package io.quarkus.jwt.test; + +import java.util.Optional; + +import javax.annotation.security.RolesAllowed; +import javax.inject.Inject; +import javax.json.Json; +import javax.json.JsonObject; +import javax.json.JsonString; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.SecurityContext; + +import org.eclipse.microprofile.jwt.Claim; +import org.eclipse.microprofile.jwt.Claims; + +/** + * An endpoint that uses no explict scoping + */ +@Path("/endp-defaultscoped") +public class DefaultScopedEndpoint { + @Inject + @Claim(standard = Claims.preferred_username) + Optional currentUsername; + @Context + private SecurityContext context; + + /** + * Validate that the passed in username parameter matches the injected preferred_username claim + * + * @param username - expected username + * @return test result response + */ + @GET + @Path("/validateUsername") + @Produces(MediaType.APPLICATION_JSON) + @RolesAllowed("Tester") + public JsonObject validateUsername(@QueryParam("username") String username) { + boolean pass = false; + String msg; + if (!currentUsername.isPresent()) { + msg = "Injected preferred_username value is null, FAIL"; + } else if (currentUsername.get().getString().equals(username)) { + msg = "\nInjected Principal#getName matches, PASS"; + pass = true; + } else { + msg = String.format("Injected preferred_username %s != %s, FAIL", currentUsername.get().getString(), username); + } + + JsonObject result = Json.createObjectBuilder() + .add("pass", pass) + .add("msg", msg) + .build(); + return result; + } +} diff --git a/extensions/smallrye-jwt/deployment/src/test/java/io/quarkus/jwt/test/JwtCookieUnitTest.java b/extensions/smallrye-jwt/deployment/src/test/java/io/quarkus/jwt/test/JwtCookieUnitTest.java new file mode 100644 index 0000000000000..862938e5cc6f8 --- /dev/null +++ b/extensions/smallrye-jwt/deployment/src/test/java/io/quarkus/jwt/test/JwtCookieUnitTest.java @@ -0,0 +1,51 @@ +package io.quarkus.jwt.test; + +import java.net.HttpURLConnection; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +public class JwtCookieUnitTest { + private static Class[] testClasses = { + DefaultGroupsEndpoint.class + }; + /** + * The test generated JWT token string + */ + private String token; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(testClasses) + .addAsResource("applicationJwtCookie.properties", "application.properties")); + + @BeforeEach + public void generateToken() throws Exception { + token = TokenUtils.generateTokenString("/TokenNoGroups.json"); + } + + /** + * Validate a request with MP-JWT token in a Cookie header is successful + * + * @throws Exception + */ + @Test + public void echoGroups() throws Exception { + io.restassured.response.Response response = RestAssured.given() + .header("Cookie", "a=" + token) + .get("/endp/echo").andReturn(); + + Assertions.assertEquals(HttpURLConnection.HTTP_OK, response.getStatusCode()); + String replyString = response.body().asString(); + // The missing 'groups' claim's default value, 'User' is expected + Assertions.assertEquals("User", replyString); + } +} diff --git a/extensions/smallrye-jwt/deployment/src/test/java/io/quarkus/jwt/test/PrimitiveInjectionUnitTest.java b/extensions/smallrye-jwt/deployment/src/test/java/io/quarkus/jwt/test/PrimitiveInjectionUnitTest.java index afcfb14f74d00..b196a361ee041 100644 --- a/extensions/smallrye-jwt/deployment/src/test/java/io/quarkus/jwt/test/PrimitiveInjectionUnitTest.java +++ b/extensions/smallrye-jwt/deployment/src/test/java/io/quarkus/jwt/test/PrimitiveInjectionUnitTest.java @@ -114,9 +114,7 @@ public void verifyInjectedJTI() throws Exception { String replyString = response.body().asString(); JsonReader jsonReader = Json.createReader(new StringReader(replyString)); JsonObject reply = jsonReader.readObject(); - // TODO add proper assertion - //System.out.println(reply.toString()); - + Assertions.assertTrue(reply.getBoolean("pass"), reply.getString("msg")); } /** @@ -137,8 +135,7 @@ public void verifyInjectedUPN() throws Exception { String replyString = response.body().asString(); JsonReader jsonReader = Json.createReader(new StringReader(replyString)); JsonObject reply = jsonReader.readObject(); - // TODO add proper assertion - //System.out.println(reply.toString()); + Assertions.assertTrue(reply.getBoolean("pass"), reply.getString("msg")); } /** @@ -159,8 +156,7 @@ public void verifyInjectedAudience() throws Exception { String replyString = response.body().asString(); JsonReader jsonReader = Json.createReader(new StringReader(replyString)); JsonObject reply = jsonReader.readObject(); - // TODO add proper assertion - //System.out.println(reply.toString()); + Assertions.assertTrue(reply.getBoolean("pass"), reply.getString("msg")); } /** @@ -182,8 +178,7 @@ public void verifyInjectedGroups() throws Exception { String replyString = response.body().asString(); JsonReader jsonReader = Json.createReader(new StringReader(replyString)); JsonObject reply = jsonReader.readObject(); - // TODO add proper assertion - //System.out.println(reply.toString()); + Assertions.assertTrue(reply.getBoolean("pass"), reply.getString("msg")); } /** @@ -204,8 +199,7 @@ public void verifyInjectedIssuedAt() throws Exception { String replyString = response.body().asString(); JsonReader jsonReader = Json.createReader(new StringReader(replyString)); JsonObject reply = jsonReader.readObject(); - // TODO add proper assertion - //System.out.println(reply.toString()); + Assertions.assertTrue(reply.getBoolean("pass"), reply.getString("msg")); } /** @@ -226,8 +220,7 @@ public void verifyInjectedExpiration() throws Exception { String replyString = response.body().asString(); JsonReader jsonReader = Json.createReader(new StringReader(replyString)); JsonObject reply = jsonReader.readObject(); - // TODO add proper assertion - //System.out.println(reply.toString()); + Assertions.assertTrue(reply.getBoolean("pass"), reply.getString("msg")); } /** @@ -248,8 +241,7 @@ public void verifyInjectedCustomString() throws Exception { String replyString = response.body().asString(); JsonReader jsonReader = Json.createReader(new StringReader(replyString)); JsonObject reply = jsonReader.readObject(); - // TODO add proper assertion - //System.out.println(reply.toString()); + Assertions.assertTrue(reply.getBoolean("pass"), reply.getString("msg")); } /** @@ -270,7 +262,6 @@ public void verifyInjectedCustomDouble() throws Exception { String replyString = response.body().asString(); JsonReader jsonReader = Json.createReader(new StringReader(replyString)); JsonObject reply = jsonReader.readObject(); - // TODO add proper assertion - //System.out.println(reply.toString()); + Assertions.assertTrue(reply.getBoolean("pass"), reply.getString("msg")); } } diff --git a/extensions/smallrye-jwt/deployment/src/test/java/io/quarkus/jwt/test/PrincipalInjectionEndpoint.java b/extensions/smallrye-jwt/deployment/src/test/java/io/quarkus/jwt/test/PrincipalInjectionEndpoint.java index dfd74b755ac73..1524d26fcf707 100644 --- a/extensions/smallrye-jwt/deployment/src/test/java/io/quarkus/jwt/test/PrincipalInjectionEndpoint.java +++ b/extensions/smallrye-jwt/deployment/src/test/java/io/quarkus/jwt/test/PrincipalInjectionEndpoint.java @@ -1,19 +1,24 @@ package io.quarkus.jwt.test; import java.security.Principal; +import java.util.Optional; import javax.annotation.security.RolesAllowed; import javax.enterprise.context.RequestScoped; import javax.inject.Inject; import javax.json.Json; import javax.json.JsonObject; +import javax.json.JsonString; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.SecurityContext; +import org.eclipse.microprofile.jwt.Claim; +import org.eclipse.microprofile.jwt.Claims; import org.eclipse.microprofile.jwt.JsonWebToken; /** @@ -27,8 +32,13 @@ @RequestScoped @RolesAllowed("Tester") public class PrincipalInjectionEndpoint { + private static final JsonString ANOYNMOUS = Json.createValue("anonymous"); + @Inject Principal principal; + @Inject + @Claim(standard = Claims.preferred_username) + Optional currentUsername; @Context private SecurityContext context; @@ -68,4 +78,26 @@ public JsonObject verifyInjectedPrincipal() { return result; } + @GET + @Path("/validateUsername") + @Produces(MediaType.APPLICATION_JSON) + @RolesAllowed("user") + public JsonObject validateUsername(@QueryParam("username") String username) { + boolean pass = false; + String msg; + if (!currentUsername.isPresent()) { + msg = "Injected preferred_username value is null, FAIL"; + } else if (currentUsername.get().getString().equals(username)) { + msg = "\nInjected Principal#getName matches, PASS"; + pass = true; + } else { + msg = String.format("Injected preferred_username %s != %s, FAIL", currentUsername.get().getString(), username); + } + + JsonObject result = Json.createObjectBuilder() + .add("pass", pass) + .add("msg", msg) + .build(); + return result; + } } diff --git a/extensions/smallrye-jwt/deployment/src/test/java/io/quarkus/jwt/test/RequestScopedEndpoint.java b/extensions/smallrye-jwt/deployment/src/test/java/io/quarkus/jwt/test/RequestScopedEndpoint.java new file mode 100644 index 0000000000000..f7aec75be6812 --- /dev/null +++ b/extensions/smallrye-jwt/deployment/src/test/java/io/quarkus/jwt/test/RequestScopedEndpoint.java @@ -0,0 +1,62 @@ +package io.quarkus.jwt.test; + +import java.util.Optional; + +import javax.annotation.security.RolesAllowed; +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.json.Json; +import javax.json.JsonObject; +import javax.json.JsonString; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.SecurityContext; + +import org.eclipse.microprofile.jwt.Claim; +import org.eclipse.microprofile.jwt.Claims; + +/** + * An endpoint with explicit {@linkplain RequestScoped} scoping + */ +@Path("/endp-requestscoped") +@RequestScoped +public class RequestScopedEndpoint { + @Inject + @Claim(standard = Claims.preferred_username) + Optional currentUsername; + @Context + private SecurityContext context; + + /** + * Validate that the passed in username parameter matches the injected preferred_username claim + * + * @param username - expected username + * @return test result response + */ + @GET + @Path("/validateUsername") + @Produces(MediaType.APPLICATION_JSON) + @RolesAllowed("Tester") + public JsonObject validateUsername(@QueryParam("username") String username) { + boolean pass = false; + String msg; + if (!currentUsername.isPresent()) { + msg = "Injected preferred_username value is null, FAIL"; + } else if (currentUsername.get().getString().equals(username)) { + msg = "\nInjected Principal#getName matches, PASS"; + pass = true; + } else { + msg = String.format("Injected preferred_username %s != %s, FAIL", currentUsername.get().getString(), username); + } + + JsonObject result = Json.createObjectBuilder() + .add("pass", pass) + .add("msg", msg) + .build(); + return result; + } +} diff --git a/extensions/smallrye-jwt/deployment/src/test/java/io/quarkus/jwt/test/RequiredClaimsUnitTest.java b/extensions/smallrye-jwt/deployment/src/test/java/io/quarkus/jwt/test/RequiredClaimsUnitTest.java index e5a2adfe137e8..29011d102a673 100644 --- a/extensions/smallrye-jwt/deployment/src/test/java/io/quarkus/jwt/test/RequiredClaimsUnitTest.java +++ b/extensions/smallrye-jwt/deployment/src/test/java/io/quarkus/jwt/test/RequiredClaimsUnitTest.java @@ -113,9 +113,7 @@ public void verifyJTI() throws Exception { String replyString = response.body().asString(); JsonReader jsonReader = Json.createReader(new StringReader(replyString)); JsonObject reply = jsonReader.readObject(); - // TODO add proper assertion - //System.out.println(reply.toString()); - + Assertions.assertTrue(reply.getBoolean("pass"), reply.getString("msg")); } /** @@ -137,8 +135,7 @@ public void verifyUPN() throws Exception { String replyString = response.body().asString(); JsonReader jsonReader = Json.createReader(new StringReader(replyString)); JsonObject reply = jsonReader.readObject(); - // TODO add proper assertion - //System.out.println(reply.toString()); + Assertions.assertTrue(reply.getBoolean("pass"), reply.getString("msg")); } /** @@ -160,8 +157,7 @@ public void verifyAudience() throws Exception { String replyString = response.body().asString(); JsonReader jsonReader = Json.createReader(new StringReader(replyString)); JsonObject reply = jsonReader.readObject(); - // TODO add proper assertion - //System.out.println(reply.toString()); + Assertions.assertTrue(reply.getBoolean("pass"), reply.getString("msg")); } /** @@ -183,8 +179,7 @@ public void verifyAudience2() throws Exception { String replyString = response.body().asString(); JsonReader jsonReader = Json.createReader(new StringReader(replyString)); JsonObject reply = jsonReader.readObject(); - // TODO add proper assertion - //System.out.println(reply.toString()); + Assertions.assertTrue(reply.getBoolean("pass"), reply.getString("msg")); } /** @@ -206,8 +201,7 @@ public void verifyIssuedAt() throws Exception { String replyString = response.body().asString(); JsonReader jsonReader = Json.createReader(new StringReader(replyString)); JsonObject reply = jsonReader.readObject(); - // TODO add proper assertion - //System.out.println(reply.toString()); + Assertions.assertTrue(reply.getBoolean("pass"), reply.getString("msg")); } /** @@ -229,7 +223,6 @@ public void verifyExpiration() throws Exception { String replyString = response.body().asString(); JsonReader jsonReader = Json.createReader(new StringReader(replyString)); JsonObject reply = jsonReader.readObject(); - // TODO add proper assertion - //System.out.println(reply.toString()); + Assertions.assertTrue(reply.getBoolean("pass"), reply.getString("msg")); } } diff --git a/extensions/smallrye-jwt/deployment/src/test/java/io/quarkus/jwt/test/RolesAllowedUnitTest.java b/extensions/smallrye-jwt/deployment/src/test/java/io/quarkus/jwt/test/RolesAllowedUnitTest.java index 2c8942100cf90..cf3670ce5f0f4 100644 --- a/extensions/smallrye-jwt/deployment/src/test/java/io/quarkus/jwt/test/RolesAllowedUnitTest.java +++ b/extensions/smallrye-jwt/deployment/src/test/java/io/quarkus/jwt/test/RolesAllowedUnitTest.java @@ -86,8 +86,7 @@ public void callEchoBASIC() throws Exception { Assertions.assertEquals(HttpURLConnection.HTTP_UNAUTHORIZED, response.getStatusCode()); String replyString = response.body().asString(); - // TODO add proper assertion - //System.out.println(replyString); + Assertions.assertEquals("Not authorized", replyString); } /** @@ -124,8 +123,7 @@ public void callEcho2() throws Exception { Assertions.assertEquals(HttpURLConnection.HTTP_FORBIDDEN, response.getStatusCode()); String replyString = response.body().asString(); - // TODO add proper assertion - //System.out.println(replyString); + Assertions.assertEquals("Access forbidden: role not allowed", replyString); } /** @@ -135,6 +133,7 @@ public void callEcho2() throws Exception { */ @Test() public void checkIsUserInRole() throws Exception { + io.restassured.response.Response response = RestAssured.given().auth() .oauth2(token) .when() @@ -142,8 +141,7 @@ public void checkIsUserInRole() throws Exception { Assertions.assertEquals(HttpURLConnection.HTTP_OK, response.getStatusCode()); String replyString = response.body().asString(); - // TODO add proper assertion - //System.out.println(replyString); + Assertions.assertEquals("jdoe@example.com", replyString); } /** @@ -161,8 +159,8 @@ public void checkIsUserInRoleToken2() throws Exception { Assertions.assertEquals(HttpURLConnection.HTTP_FORBIDDEN, response.getStatusCode()); String replyString = response.body().asString(); - // TODO add proper assertion - //System.out.println(replyString); + + Assertions.assertEquals("", replyString); } /** @@ -172,17 +170,17 @@ public void checkIsUserInRoleToken2() throws Exception { */ @Test() public void echoNeedsToken2Role() throws Exception { + String input = "hello"; String token2 = TokenUtils.generateTokenString("/Token2.json"); io.restassured.response.Response response = RestAssured.given().auth() .oauth2(token2) .when() - .queryParam("input", "hello") + .queryParam("input", input) .get("/endp/echoNeedsToken2Role").andReturn(); Assertions.assertEquals(HttpURLConnection.HTTP_OK, response.getStatusCode()); String replyString = response.body().asString(); - // TODO add proper assertion - //System.out.println(replyString); + Assertions.assertEquals(input + ", user=jdoe2@example.com", replyString); } /** @@ -201,8 +199,7 @@ public void echoWithToken2() throws Exception { Assertions.assertEquals(HttpURLConnection.HTTP_FORBIDDEN, response.getStatusCode()); String replyString = response.body().asString(); - // TODO add proper assertion - //System.out.println(replyString); + Assertions.assertEquals("Access forbidden: role not allowed", replyString); } /** @@ -238,8 +235,7 @@ public void testNeedsGroup1Mapping() throws Exception { Assertions.assertEquals(HttpURLConnection.HTTP_OK, response.getStatusCode()); String replyString = response.body().asString(); - // TODO add proper assertion - //System.out.println(replyString); + Assertions.assertEquals("jdoe@example.com", replyString); } /** diff --git a/extensions/smallrye-jwt/deployment/src/test/java/io/quarkus/jwt/test/ScopingUnitTest.java b/extensions/smallrye-jwt/deployment/src/test/java/io/quarkus/jwt/test/ScopingUnitTest.java new file mode 100644 index 0000000000000..170e9b4109a4e --- /dev/null +++ b/extensions/smallrye-jwt/deployment/src/test/java/io/quarkus/jwt/test/ScopingUnitTest.java @@ -0,0 +1,91 @@ +package io.quarkus.jwt.test; + +import java.io.StringReader; +import java.net.HttpURLConnection; +import java.util.Base64; +import java.util.HashMap; + +import javax.json.Json; +import javax.json.JsonObject; +import javax.json.JsonReader; + +import org.eclipse.microprofile.jwt.Claims; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; +import io.restassured.response.Response; + +public class ScopingUnitTest { + private static Class[] testClasses = { + DefaultScopedEndpoint.class, + RequestScopedEndpoint.class + }; + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(testClasses) + .addAsResource("application.properties")); + + @Test + public void verifyUsernameClaim() throws Exception { + String token = TokenUtils.generateTokenString("/Token1.json"); + Response response = RestAssured.given().auth() + .oauth2(token) + .when() + .queryParam("username", "jdoe") + .get("/endp-defaultscoped/validateUsername").andReturn(); + + Assertions.assertEquals(HttpURLConnection.HTTP_OK, response.getStatusCode()); + String replyString = response.body().asString(); + JsonReader jsonReader = Json.createReader(new StringReader(replyString)); + JsonObject reply = jsonReader.readObject(); + Assertions.assertTrue(reply.getBoolean("pass"), reply.getString("msg")); + + String token2 = TokenUtils.generateTokenString("/Token2.json"); + Response response2 = RestAssured.given().auth() + .oauth2(token2) + .when() + // We expect the injected preferred_username claim to still be jdoe due to default scope = @ApplicationScoped + .queryParam("username", "jdoe") + .get("/endp-defaultscoped/validateUsername").andReturn(); + + Assertions.assertEquals(HttpURLConnection.HTTP_OK, response2.getStatusCode()); + String replyString2 = response2.body().asString(); + JsonReader jsonReader2 = Json.createReader(new StringReader(replyString2)); + JsonObject reply2 = jsonReader2.readObject(); + Assertions.assertTrue(reply2.getBoolean("pass"), reply2.getString("msg")); + + Response response3 = RestAssured.given().auth() + .oauth2(token) + .when() + // We expect + .queryParam("username", "jdoe") + .get("/endp-requestscoped/validateUsername").andReturn(); + + Assertions.assertEquals(HttpURLConnection.HTTP_OK, response3.getStatusCode()); + String replyString3 = response3.body().asString(); + JsonReader jsonReader3 = Json.createReader(new StringReader(replyString3)); + JsonObject reply3 = jsonReader3.readObject(); + Assertions.assertTrue(reply3.getBoolean("pass"), reply3.getString("msg")); + + Response response4 = RestAssured.given().auth() + .oauth2(token2) + .when() + // Now we expect the injected claim to match the current caller + .queryParam("username", "jdoe2") + .get("/endp-requestscoped/validateUsername").andReturn(); + + Assertions.assertEquals(HttpURLConnection.HTTP_OK, response4.getStatusCode()); + String replyString4 = response4.body().asString(); + JsonReader jsonReader4 = Json.createReader(new StringReader(replyString4)); + JsonObject reply4 = jsonReader4.readObject(); + Assertions.assertTrue(reply4.getBoolean("pass"), reply4.getString("msg")); + } +} diff --git a/extensions/smallrye-jwt/deployment/src/test/java/io/quarkus/jwt/test/TokenRealmUnitTest.java b/extensions/smallrye-jwt/deployment/src/test/java/io/quarkus/jwt/test/TokenRealmUnitTest.java index 2c40af2f4af03..0beb842c3ec39 100644 --- a/extensions/smallrye-jwt/deployment/src/test/java/io/quarkus/jwt/test/TokenRealmUnitTest.java +++ b/extensions/smallrye-jwt/deployment/src/test/java/io/quarkus/jwt/test/TokenRealmUnitTest.java @@ -5,6 +5,7 @@ import java.security.KeyPair; import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; import java.security.Permission; import java.security.Principal; import java.security.PrivateKey; @@ -14,6 +15,7 @@ import java.util.Iterator; import java.util.Set; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.wildfly.security.auth.principal.NamePrincipal; import org.wildfly.security.auth.realm.token.TokenSecurityRealm; @@ -40,7 +42,7 @@ public class TokenRealmUnitTest { @Test public void testTokenRealm() throws Exception { - KeyPair keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair(); + KeyPair keyPair = generateKeyPair(); PublicKey pk1 = keyPair.getPublic(); PrivateKey pk1Priv = keyPair.getPrivate(); JWTAuthContextInfo contextInfo = new JWTAuthContextInfo(); @@ -59,14 +61,13 @@ public void testTokenRealm() throws Exception { RealmIdentity identity = tokenRealm.getRealmIdentity(tokenEvidence); assertNotNull(identity); assertTrue(identity.exists()); - AuthorizationIdentity authz = identity.getAuthorizationIdentity(); - // TODO add proper assertion - //System.out.println(authz.getAttributes().keySet()); + AuthorizationIdentity authorizationIdentity = identity.getAuthorizationIdentity(); + Assertions.assertNotNull(authorizationIdentity); } @Test public void testSecurityDomain() throws Exception { - KeyPair keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair(); + KeyPair keyPair = generateKeyPair(); PublicKey pk1 = keyPair.getPublic(); PrivateKey pk1Priv = keyPair.getPrivate(); JWTAuthContextInfo contextInfo = new JWTAuthContextInfo(); @@ -116,8 +117,7 @@ public boolean implies(Permission permission) { String jwt = TokenUtils.generateTokenString("/Token1.json", pk1Priv, "testTokenRealm"); BearerTokenEvidence tokenEvidence = new BearerTokenEvidence(jwt); SecurityIdentity securityIdentity = securityDomain.authenticate(tokenEvidence); - // TODO add proper assertion - //System.out.println(securityIdentity.getAttributes().keySet()); + Assertions.assertNotNull(securityIdentity); } private Principal mpJwtLogic(Attributes claims) { @@ -130,4 +130,10 @@ private Principal mpJwtLogic(Attributes claims) { } return new NamePrincipal(pn); } + + private KeyPair generateKeyPair() throws NoSuchAlgorithmException { + KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA"); + generator.initialize(2048); // because that's the minimal accepted size + return generator.generateKeyPair(); + } } diff --git a/extensions/smallrye-jwt/deployment/src/test/resources/Token2.json b/extensions/smallrye-jwt/deployment/src/test/resources/Token2.json index b8aee84d3d7f5..326ac22349009 100644 --- a/extensions/smallrye-jwt/deployment/src/test/resources/Token2.json +++ b/extensions/smallrye-jwt/deployment/src/test/resources/Token2.json @@ -3,7 +3,7 @@ "jti": "a-123.2", "sub": "24400320#2", "upn": "jdoe2@example.com", - "preferred_username": "jdoe", + "preferred_username": "jdoe2", "aud": "s6BhdRkqt3.2", "exp": 1311281970, "iat": 1311280970, diff --git a/extensions/smallrye-jwt/deployment/src/test/resources/TokenNoGroups.json b/extensions/smallrye-jwt/deployment/src/test/resources/TokenNoGroups.json new file mode 100644 index 0000000000000..4e23506295093 --- /dev/null +++ b/extensions/smallrye-jwt/deployment/src/test/resources/TokenNoGroups.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/smallrye-jwt/deployment/src/test/resources/applicationDefaultGroups.properties b/extensions/smallrye-jwt/deployment/src/test/resources/applicationDefaultGroups.properties new file mode 100644 index 0000000000000..df31bac8e57c7 --- /dev/null +++ b/extensions/smallrye-jwt/deployment/src/test/resources/applicationDefaultGroups.properties @@ -0,0 +1,5 @@ +mp.jwt.verify.publickey.location=/publicKey.pem +mp.jwt.verify.issuer=https://server.example.com +smallrye.jwt.claims.groups=User +quarkus.smallrye-jwt.auth-mechanism=MP-JWT +quarkus.smallrye-jwt.enabled=true \ No newline at end of file diff --git a/extensions/smallrye-jwt/deployment/src/test/resources/applicationJwtCookie.properties b/extensions/smallrye-jwt/deployment/src/test/resources/applicationJwtCookie.properties new file mode 100644 index 0000000000000..8c963aa9adeee --- /dev/null +++ b/extensions/smallrye-jwt/deployment/src/test/resources/applicationJwtCookie.properties @@ -0,0 +1,7 @@ +mp.jwt.verify.publickey.location=/publicKey.pem +mp.jwt.verify.issuer=https://server.example.com +smallrye.jwt.claims.groups=User +smallrye.jwt.token.header=Cookie +smallrye.jwt.token.cookie=a +quarkus.smallrye-jwt.auth-mechanism=MP-JWT +quarkus.smallrye-jwt.enabled=true \ No newline at end of file diff --git a/extensions/smallrye-jwt/runtime/pom.xml b/extensions/smallrye-jwt/runtime/pom.xml index 8687d17526544..6b35ea26ca9f8 100644 --- a/extensions/smallrye-jwt/runtime/pom.xml +++ b/extensions/smallrye-jwt/runtime/pom.xml @@ -26,7 +26,7 @@ 4.0.0 - quarkus-smallrye-jwt-runtime + quarkus-smallrye-jwt Quarkus - SmallRye JWT - Runtime @@ -40,19 +40,19 @@ io.quarkus - quarkus-core-runtime + quarkus-core io.quarkus - quarkus-elytron-security-runtime + quarkus-elytron-security io.quarkus - quarkus-undertow-runtime + quarkus-undertow - org.glassfish - javax.json + io.quarkus + quarkus-jsonp org.jboss.spec.javax.servlet @@ -67,7 +67,8 @@ - maven-dependency-plugin + io.quarkus + quarkus-bootstrap-maven-plugin maven-compiler-plugin diff --git a/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/ClaimValueProducer.java b/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/ClaimValueProducer.java deleted file mode 100644 index 4816711a7343b..0000000000000 --- a/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/ClaimValueProducer.java +++ /dev/null @@ -1,52 +0,0 @@ -package io.quarkus.smallrye.jwt.runtime; - -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.Optional; - -import javax.enterprise.inject.Produces; -import javax.enterprise.inject.spi.InjectionPoint; -import javax.inject.Inject; - -import org.eclipse.microprofile.jwt.Claim; -import org.eclipse.microprofile.jwt.ClaimValue; - -/** - * A producer for the ClaimValue wrapper injection sites. - * - * @param the raw claim type - */ -public class ClaimValueProducer { - - @Inject - CommonJwtProducer util; - - @Produces - @Claim("") - @SuppressWarnings("unchecked") - ClaimValue produce(InjectionPoint ip) { - ClaimValue> cv = util.generalClaimValueProducer(ip); - ClaimValue returnValue = (ClaimValue) cv; - Optional value = cv.getValue(); - // Pull out the ClaimValue T type, - Type matchType = ip.getType(); - Type actualType = Object.class; - boolean isOptional = false; - if (matchType instanceof ParameterizedType) { - actualType = ((ParameterizedType) matchType).getActualTypeArguments()[0]; - isOptional = matchType.getTypeName().equals(Optional.class.getTypeName()); - if (isOptional) { - actualType = ((ParameterizedType) matchType).getActualTypeArguments()[0]; - } - } - - if (!actualType.getTypeName().startsWith(Optional.class.getTypeName())) { - T nestedValue = value.orElse(null); - ClaimValueWrapper wrapper = new ClaimValueWrapper<>(cv.getName()); - wrapper.setValue(nestedValue); - returnValue = wrapper; - } - return returnValue; - } - -} diff --git a/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/ClaimValueWrapper.java b/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/ClaimValueWrapper.java deleted file mode 100644 index 569be1055da36..0000000000000 --- a/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/ClaimValueWrapper.java +++ /dev/null @@ -1,38 +0,0 @@ -package io.quarkus.smallrye.jwt.runtime; - -import org.eclipse.microprofile.jwt.ClaimValue; - -/** - * An implementation of the ClaimValue interface - * - * @param the claim value type - */ -public class ClaimValueWrapper implements ClaimValue { - private String name; - - private T value; - - public ClaimValueWrapper(String name) { - this.name = name; - } - - @Override - public String getName() { - return name; - } - - @Override - public T getValue() { - return value; - } - - public void setValue(T value) { - this.value = value; - } - - @Override - public String toString() { - return String.format("ClaimValueWrapper[@%s], name=%s, value[%s]=%s", Integer.toHexString(hashCode()), - name, value.getClass(), value); - } -} diff --git a/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/CommonJwtProducer.java b/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/CommonJwtProducer.java deleted file mode 100644 index b84f2e68817c2..0000000000000 --- a/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/CommonJwtProducer.java +++ /dev/null @@ -1,158 +0,0 @@ -package io.quarkus.smallrye.jwt.runtime; - -import java.lang.annotation.Annotation; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import javax.enterprise.context.RequestScoped; -import javax.enterprise.inject.spi.InjectionPoint; -import javax.inject.Inject; -import javax.json.Json; -import javax.json.JsonArray; -import javax.json.JsonArrayBuilder; -import javax.json.JsonObject; -import javax.json.JsonObjectBuilder; -import javax.json.JsonValue; - -import org.eclipse.microprofile.jwt.Claim; -import org.eclipse.microprofile.jwt.ClaimValue; -import org.eclipse.microprofile.jwt.Claims; -import org.eclipse.microprofile.jwt.JsonWebToken; -import org.jboss.logging.Logger; - -@RequestScoped -public class CommonJwtProducer { - private static Logger log = Logger.getLogger(CommonJwtProducer.class); - private static final String TMP = "tmp"; - - @Inject - JsonWebToken currentToken; - - /** - * A utility method for accessing a claim from the current JsonWebToken as a ClaimValue> object. - * - * @param ip - injection point of the claim - * @param expected actual type of the claim - * @return the claim value wrapper object - */ - public ClaimValue> generalClaimValueProducer(InjectionPoint ip) { - String name = getName(ip); - ClaimValueWrapper> wrapper = new ClaimValueWrapper<>(name); - T value = getValue(name, false); - Optional optValue = Optional.ofNullable(value); - wrapper.setValue(optValue); - return wrapper; - } - - /** - * Return the indicated claim value as a JsonValue - * - * @param ip - injection point of the claim - * @return a JsonValue wrapper - */ - public JsonValue generalJsonValueProducer(InjectionPoint ip) { - String name = getName(ip); - Object value = getValue(name, false); - JsonValue jsonValue = wrapValue(value); - return jsonValue; - } - - public T getValue(String name, boolean isOptional) { - if (currentToken == null) { - log.debugf("getValue(%s), null JsonWebToken", name); - return null; - } - - Optional claimValue = currentToken.claim(name); - if (!isOptional && !claimValue.isPresent()) { - log.debugf("Failed to find Claim for: %s", name); - } - log.debugf("getValue(%s), isOptional=%s, claimValue=%s", name, isOptional, claimValue); - return claimValue.orElse(null); - } - - public String getName(InjectionPoint ip) { - String name = null; - for (Annotation ann : ip.getQualifiers()) { - if (ann instanceof Claim) { - Claim claim = (Claim) ann; - name = claim.standard() == Claims.UNKNOWN ? claim.value() : claim.standard().name(); - } - } - return name; - } - - @SuppressWarnings("unchecked") - private static JsonObject replaceMap(Map map) { - JsonObjectBuilder builder = Json.createObjectBuilder(); - for (Map.Entry entry : map.entrySet()) { - Object entryValue = entry.getValue(); - if (entryValue instanceof Map) { - JsonObject entryJsonObject = replaceMap((Map) entryValue); - builder.add(entry.getKey(), entryJsonObject); - } else if (entryValue instanceof List) { - JsonArray array = (JsonArray) wrapValue(entryValue); - builder.add(entry.getKey(), array); - } else if (entryValue instanceof Long || entryValue instanceof Integer) { - long lvalue = ((Number) entryValue).longValue(); - builder.add(entry.getKey(), lvalue); - } else if (entryValue instanceof Double || entryValue instanceof Float) { - double dvalue = ((Number) entryValue).doubleValue(); - builder.add(entry.getKey(), dvalue); - } else if (entryValue instanceof Boolean) { - boolean flag = ((Boolean) entryValue).booleanValue(); - builder.add(entry.getKey(), flag); - } else if (entryValue instanceof String) { - builder.add(entry.getKey(), entryValue.toString()); - } - } - return builder.build(); - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - private static JsonValue wrapValue(Object value) { - JsonValue jsonValue = null; - if (value instanceof JsonValue) { - // This may already be a JsonValue - jsonValue = (JsonValue) value; - } else if (value instanceof String) { - jsonValue = Json.createObjectBuilder() - .add(TMP, value.toString()) - .build() - .getJsonString(TMP); - } else if (value instanceof Number) { - Number number = (Number) value; - if ((number instanceof Long) || (number instanceof Integer)) { - jsonValue = Json.createObjectBuilder() - .add(TMP, number.longValue()) - .build() - .getJsonNumber(TMP); - } else { - jsonValue = Json.createObjectBuilder() - .add(TMP, number.doubleValue()) - .build() - .getJsonNumber(TMP); - } - } else if (value instanceof Boolean) { - Boolean flag = (Boolean) value; - jsonValue = flag ? JsonValue.TRUE : JsonValue.FALSE; - } else if (value instanceof Collection) { - JsonArrayBuilder arrayBuilder = Json.createArrayBuilder(); - Collection list = (Collection) value; - for (Object element : list) { - if (element instanceof String) { - arrayBuilder.add(element.toString()); - } else { - JsonValue jvalue = wrapValue(element); - arrayBuilder.add(jvalue); - } - } - jsonValue = arrayBuilder.build(); - } else if (value instanceof Map) { - jsonValue = replaceMap((Map) value); - } - return jsonValue; - } -} diff --git a/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/JWTAuthContextInfoGroup.java b/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/JWTAuthContextInfoGroup.java index 3d482036c01c7..40ebdb31a28ea 100644 --- a/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/JWTAuthContextInfoGroup.java +++ b/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/JWTAuthContextInfoGroup.java @@ -27,4 +27,10 @@ public class JWTAuthContextInfoGroup { */ @ConfigItem(defaultValue = "true") public boolean enabled = true; + + /** + * The name of the {@linkplain java.security.Provider} that supports SHA256withRSA signatures + */ + @ConfigItem(defaultValue = "SunRsaSign") + public String rsaSigProvider; } diff --git a/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/JsonValueProducer.java b/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/JsonValueProducer.java deleted file mode 100644 index 44e6af6d35296..0000000000000 --- a/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/JsonValueProducer.java +++ /dev/null @@ -1,88 +0,0 @@ -package io.quarkus.smallrye.jwt.runtime; - -import java.util.Optional; - -import javax.enterprise.inject.Produces; -import javax.enterprise.inject.spi.InjectionPoint; -import javax.inject.Inject; -import javax.json.JsonArray; -import javax.json.JsonNumber; -import javax.json.JsonObject; -import javax.json.JsonString; -import javax.json.JsonValue; - -import org.eclipse.microprofile.jwt.Claim; -import org.jboss.logging.Logger; - -/** - * A producer for JsonValue injection types - */ -public class JsonValueProducer { - private static Logger log = Logger.getLogger(JsonValueProducer.class); - - @Inject - CommonJwtProducer util; - - @Produces - @Claim("") - public JsonString getJsonString(InjectionPoint ip) { - return getValue(ip); - } - - @Produces - @Claim("") - public Optional getOptionalJsonString(InjectionPoint ip) { - return getOptionalValue(ip); - } - - @Produces - @Claim("") - public JsonNumber getJsonNumber(InjectionPoint ip) { - return getValue(ip); - } - - @Produces - @Claim("") - public Optional getOptionalJsonNumber(InjectionPoint ip) { - return getOptionalValue(ip); - } - - @Produces - @Claim("") - public JsonArray getJsonArray(InjectionPoint ip) { - return getValue(ip); - } - - @Produces - @Claim("") - public Optional getOptionalJsonArray(InjectionPoint ip) { - return getOptionalValue(ip); - } - - @Produces - @Claim("") - public JsonObject getJsonObject(InjectionPoint ip) { - return getValue(ip); - } - - @Produces - @Claim("") - public Optional getOptionalJsonObject(InjectionPoint ip) { - return getOptionalValue(ip); - } - - @SuppressWarnings("unchecked") - public T getValue(InjectionPoint ip) { - log.debugf("JsonValueProducer(%s).produce", ip); - T jsonValue = (T) util.generalJsonValueProducer(ip); - return jsonValue; - } - - @SuppressWarnings("unchecked") - public Optional getOptionalValue(InjectionPoint ip) { - log.debugf("JsonValueProducer(%s).produce", ip); - T jsonValue = (T) util.generalJsonValueProducer(ip); - return Optional.ofNullable(jsonValue); - } - -} diff --git a/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/PrincipalProducer.java b/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/PrincipalProducer.java deleted file mode 100644 index 16d33a561cc23..0000000000000 --- a/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/PrincipalProducer.java +++ /dev/null @@ -1,46 +0,0 @@ -package io.quarkus.smallrye.jwt.runtime; - -import javax.annotation.Priority; -import javax.enterprise.context.RequestScoped; -import javax.enterprise.inject.Alternative; -import javax.enterprise.inject.Produces; - -import org.eclipse.microprofile.jwt.JsonWebToken; - -import io.quarkus.smallrye.jwt.runtime.auth.JWTAccount; - -/** - * Override the default CDI Principal bean to allow the injection of a Principal to be both a - * {@linkplain JsonWebToken} and a {@linkplain java.security.Principal}. - */ -@Priority(1) -@Alternative -@RequestScoped -public class PrincipalProducer { - private JWTAccount account; - - public PrincipalProducer() { - } - - public JWTAccount getAccount() { - return account; - } - - public void setAccount(JWTAccount account) { - this.account = account; - } - - /** - * The producer method for the current JsonWebToken - * - * @return - */ - @Produces - JsonWebToken currentJWTPrincipalOrNull() { - JsonWebToken token = null; - if (account != null) { - token = (JsonWebToken) account.getPrincipal(); - } - return token; - } -} diff --git a/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/RawClaimTypeProducer.java b/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/RawClaimTypeProducer.java deleted file mode 100644 index 194181220833a..0000000000000 --- a/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/RawClaimTypeProducer.java +++ /dev/null @@ -1,104 +0,0 @@ -package io.quarkus.smallrye.jwt.runtime; - -import java.lang.annotation.Annotation; -import java.util.Optional; -import java.util.Set; - -import javax.enterprise.inject.Produces; -import javax.enterprise.inject.spi.InjectionPoint; -import javax.inject.Inject; -import javax.json.JsonNumber; -import javax.json.JsonString; - -import org.eclipse.microprofile.jwt.Claim; -import org.eclipse.microprofile.jwt.Claims; -import org.eclipse.microprofile.jwt.JsonWebToken; -import org.jboss.logging.Logger; - -/** - * Producer for unwrapped native and primitive Java types of the claims - */ -public class RawClaimTypeProducer { - private static Logger log = Logger.getLogger(RawClaimTypeProducer.class); - - @Inject - JsonWebToken currentToken; - - @Produces - @Claim("") - Set getClaimAsSet(InjectionPoint ip) { - log.debugf("getValue(%s)", ip); - String name = getName(ip); - Optional> value = currentToken.claim(name); - Set returnValue = value.orElse(null); - return returnValue; - } - - @Produces - @Claim("") - String getClaimAsString(InjectionPoint ip) { - log.debugf("getValue(%s)", ip); - String name = getName(ip); - Optional optValue = currentToken.claim(name); - String returnValue = null; - if (optValue.isPresent()) { - Object value = optValue.get(); - if (value instanceof JsonString) { - JsonString jsonValue = (JsonString) value; - returnValue = jsonValue.getString(); - } else { - returnValue = value.toString(); - } - } - return returnValue; - } - - @Produces - @Claim("") - Long getClaimAsLong(InjectionPoint ip) { - log.debugf("getValue(%s)", ip); - String name = getName(ip); - Optional optValue = currentToken.claim(name); - Long returnValue = null; - if (optValue.isPresent()) { - Object value = optValue.get(); - if (value instanceof JsonNumber) { - JsonNumber jsonValue = (JsonNumber) value; - returnValue = jsonValue.longValue(); - } else { - returnValue = Long.parseLong(value.toString()); - } - } - return returnValue; - } - - @Produces - @Claim("") - Double getClaimAsDouble(InjectionPoint ip) { - log.debugf("getValue(%s)", ip); - String name = getName(ip); - Optional optValue = currentToken.claim(name); - Double returnValue = null; - if (optValue.isPresent()) { - Object value = optValue.get(); - if (value instanceof JsonNumber) { - JsonNumber jsonValue = (JsonNumber) value; - returnValue = jsonValue.doubleValue(); - } else { - returnValue = Double.parseDouble(value.toString()); - } - } - return returnValue; - } - - String getName(InjectionPoint ip) { - String name = null; - for (Annotation ann : ip.getQualifiers()) { - if (ann instanceof Claim) { - Claim claim = (Claim) ann; - name = claim.standard() == Claims.UNKNOWN ? claim.value() : claim.standard().name(); - } - } - return name; - } -} diff --git a/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/SmallRyeJwtTemplate.java b/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/SmallRyeJwtTemplate.java index e94a862b38a45..ce33408f723b9 100644 --- a/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/SmallRyeJwtTemplate.java +++ b/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/SmallRyeJwtTemplate.java @@ -1,11 +1,8 @@ package io.quarkus.smallrye.jwt.runtime; -import java.security.Principal; - import org.wildfly.security.auth.realm.token.TokenSecurityRealm; import org.wildfly.security.auth.server.SecurityDomain; import org.wildfly.security.auth.server.SecurityRealm; -import org.wildfly.security.authz.Attributes; import io.quarkus.arc.runtime.BeanContainer; import io.quarkus.runtime.RuntimeValue; @@ -54,28 +51,9 @@ public ServletExtension createAuthExtension(String authMechanism, BeanContainer public RuntimeValue createTokenRealm(BeanContainer container) { MpJwtValidator jwtValidator = container.instance(MpJwtValidator.class); TokenSecurityRealm tokenRealm = TokenSecurityRealm.builder() - .claimToPrincipal(this::mpJwtLogic) + .claimToPrincipal(claims -> new ElytronJwtCallerPrincipal(claims)) .validator(jwtValidator) .build(); return new RuntimeValue<>(tokenRealm); } - - /** - * MP-JWT logic for determining the name to use for the principal - * - * @param claims - token claims - * @return JWTCallerPrincipal implementation - */ - private Principal mpJwtLogic(Attributes claims) { - String pn = claims.getFirst("upn"); - if (pn == null) { - pn = claims.getFirst("preferred_name"); - } - if (pn == null) { - pn = claims.getFirst("sub"); - } - - ElytronJwtCallerPrincipal jwtCallerPrincipal = new ElytronJwtCallerPrincipal(pn, claims); - return jwtCallerPrincipal; - } } diff --git a/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/auth/ElytronJwtCallerPrincipal.java b/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/auth/ElytronJwtCallerPrincipal.java index a80c8b2a59ae9..5116636f3349b 100644 --- a/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/auth/ElytronJwtCallerPrincipal.java +++ b/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/auth/ElytronJwtCallerPrincipal.java @@ -1,340 +1,60 @@ package io.quarkus.smallrye.jwt.runtime.auth; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.json.Json; -import javax.json.JsonArray; -import javax.json.JsonArrayBuilder; -import javax.json.JsonNumber; -import javax.json.JsonObject; -import javax.json.JsonObjectBuilder; import javax.json.JsonStructure; -import javax.json.JsonValue; -import javax.security.auth.Subject; import org.eclipse.microprofile.jwt.Claims; -import org.jboss.logging.Logger; import org.jose4j.jwt.JwtClaims; -import org.jose4j.jwt.MalformedClaimException; import org.wildfly.security.authz.Attributes; -import io.smallrye.jwt.auth.principal.JWTCallerPrincipal; +import io.smallrye.jwt.auth.principal.DefaultJWTCallerPrincipal; /** * An implementation of JWTCallerPrincipal that builds on the Elytron attributes */ -public class ElytronJwtCallerPrincipal extends JWTCallerPrincipal { - private static final String TMP = "tmp"; - private static Logger logger = Logger.getLogger(ElytronJwtCallerPrincipal.class); - +public class ElytronJwtCallerPrincipal extends DefaultJWTCallerPrincipal { private Attributes claims; - private JwtClaims claimsSet; + private String customPrincipalName; - public ElytronJwtCallerPrincipal(final String name, final Attributes claims) { - super(name); + public ElytronJwtCallerPrincipal(final String customPrincipalName, final Attributes claims) { + super(getClaimsSet(claims)); this.claims = claims; - if (!(claims instanceof ClaimAttributes)) { - throw new IllegalStateException( - "ElytronJwtCallerPrincipal requires Attributes to be a: " + ClaimAttributes.class.getName()); - } - this.claimsSet = ((ClaimAttributes) claims).getClaimsSet(); - fixJoseTypes(); - } - - public ElytronJwtCallerPrincipal(final String name, final JwtClaims claimsSet) { - super(name); - this.claimsSet = claimsSet; - fixJoseTypes(); - } - - public Attributes getClaims() { - return claims; + this.customPrincipalName = customPrincipalName; } - @Override - public Set getAudience() { - Set audSet = null; - try { - if (claimsSet.hasClaim(Claims.aud.name())) { - List audList = claimsSet.getStringListClaimValue("aud"); - audSet = new HashSet<>(audList); - } - } catch (MalformedClaimException e) { - try { - // Not sent as an array, try a single value - String aud = claimsSet.getStringClaimValue("aud"); - audSet = new HashSet<>(); - audSet.add(aud); - } catch (MalformedClaimException e1) { - } - } - return audSet; + public ElytronJwtCallerPrincipal(final String customPrincipalName, final JwtClaims claimsSet) { + this(customPrincipalName, new ClaimAttributes(claimsSet)); } - @Override - public Set getGroups() { - HashSet groups = new HashSet<>(); - try { - List globalGroups = claimsSet.getStringListClaimValue("groups"); - if (globalGroups != null) { - groups.addAll(globalGroups); - } - } catch (MalformedClaimException e) { - e.printStackTrace(); - } - return groups; + public ElytronJwtCallerPrincipal(Attributes claims) { + this(null, claims); } - @Override - public Set getClaimNames() { - return new HashSet<>(claimsSet.getClaimNames()); + public Attributes getClaims() { + return claims; } - @Override - public T getClaim(String claimName) { - Claims claimType = Claims.UNKNOWN; - Object claim = null; - try { - claimType = Claims.valueOf(claimName); - } catch (IllegalArgumentException e) { - } - // Handle the jose4j NumericDate types and - switch (claimType) { - case exp: - case iat: - case auth_time: - case nbf: - case updated_at: - try { - claim = claimsSet.getClaimValue(claimType.name(), Long.class); - if (claim == null) { - claim = new Long(0); - } - } catch (MalformedClaimException e) { - } - break; - case groups: - claim = getGroups(); - break; - case aud: - claim = getAudience(); - break; - case UNKNOWN: - // This has to be a Json type - claim = claimsSet.getClaimValue(claimName); - if (!(claim instanceof JsonStructure)) { - claim = wrapValue(claim); - } - break; - default: - claim = claimsSet.getClaimValue(claimType.name()); + private static JwtClaims getClaimsSet(Attributes claims) { + if (!(claims instanceof ClaimAttributes)) { + throw new IllegalStateException( + "ElytronJwtCallerPrincipal requires Attributes to be a: " + ClaimAttributes.class.getName()); } - return (T) claim; + return ((ClaimAttributes) claims).getClaimsSet(); } @Override - public boolean implies(Subject subject) { - return false; + public String getName() { + return customPrincipalName != null ? customPrincipalName : super.getName(); } - public String toString() { - return toString(false); - } - - /** - * TODO: showAll is ignored and currently assumed true - * - * @param showAll - should all claims associated with the JWT be displayed or should only those defined in the - * JsonWebToken interface be displayed. - * @return JWTCallerPrincipal string view - */ @Override - public String toString(boolean showAll) { - String toString = "DefaultJWTCallerPrincipal{" + - "id='" + getTokenID() + '\'' + - ", name='" + getName() + '\'' + - ", expiration=" + getExpirationTime() + - ", notBefore=" + getClaim(Claims.nbf.name()) + - ", issuedAt=" + getIssuedAtTime() + - ", issuer='" + getIssuer() + '\'' + - ", audience=" + getAudience() + - ", subject='" + getSubject() + '\'' + - ", issuedFor='" + getClaim("azp") + '\'' + - ", authTime=" + getClaim("auth_time") + - ", givenName='" + getClaim("given_name") + '\'' + - ", familyName='" + getClaim("family_name") + '\'' + - ", middleName='" + getClaim("middle_name") + '\'' + - ", nickName='" + getClaim("nickname") + '\'' + - ", preferredUsername='" + getClaim("preferred_username") + '\'' + - ", email='" + getClaim("email") + '\'' + - ", emailVerified=" + getClaim(Claims.email_verified.name()) + - ", allowedOrigins=" + getClaim("allowedOrigins") + - ", updatedAt=" + getClaim("updated_at") + - ", acr='" + getClaim("acr") + '\''; - StringBuilder tmp = new StringBuilder(toString); - tmp.append(", groups=["); - for (String group : getGroups()) { - tmp.append(group); - tmp.append(','); - } - tmp.setLength(tmp.length() - 1); - tmp.append("]}"); - return tmp.toString(); - } - - /** - * Convert the types jose4j uses for address, sub_jwk, and jwk - */ - private void fixJoseTypes() { - if (claimsSet.hasClaim(Claims.address.name())) { - replaceMap(Claims.address.name()); - } - if (claimsSet.hasClaim(Claims.jwk.name())) { - replaceMap(Claims.jwk.name()); - } - if (claimsSet.hasClaim(Claims.sub_jwk.name())) { - replaceMap(Claims.sub_jwk.name()); - } - // Handle custom claims - Set customClaimNames = filterCustomClaimNames(claimsSet.getClaimNames()); - for (String name : customClaimNames) { - Object claimValue = claimsSet.getClaimValue(name); - Class claimType = claimValue.getClass(); - if (claimValue instanceof List) { - replaceList(name); - } else if (claimValue instanceof Map) { - replaceMap(name); - } else if (claimValue instanceof Number) { - replaceNumber(name); - } - } - } - - /** - * Determine the custom claims in the set - * - * @param claimNames - the current set of claim names in this token - * @return the possibly empty set of names for non-Claims claims - */ - private Set filterCustomClaimNames(Collection claimNames) { - HashSet customNames = new HashSet<>(claimNames); - for (Claims claim : Claims.values()) { - customNames.remove(claim.name()); - } - return customNames; - } - - /** - * Replace the jose4j Map with a JsonObject - * - * @param name - claim name - */ - private void replaceMap(String name) { - try { - Map map = claimsSet.getClaimValue(name, Map.class); - JsonObject jsonObject = replaceMap(map); - claimsSet.setClaim(name, jsonObject); - } catch (MalformedClaimException e) { - logger.warn("replaceMap failure for: " + name, e); - } - } - - private JsonObject replaceMap(Map map) { - JsonObjectBuilder builder = Json.createObjectBuilder(); - for (Map.Entry entry : map.entrySet()) { - Object entryValue = entry.getValue(); - if (entryValue instanceof Map) { - JsonObject entryJsonObject = replaceMap((Map) entryValue); - builder.add(entry.getKey(), entryJsonObject); - } else if (entryValue instanceof List) { - JsonArray array = (JsonArray) wrapValue(entryValue); - builder.add(entry.getKey(), array); - } else if (entryValue instanceof Long || entryValue instanceof Integer) { - long lvalue = ((Number) entryValue).longValue(); - builder.add(entry.getKey(), lvalue); - } else if (entryValue instanceof Double || entryValue instanceof Float) { - double dvalue = ((Number) entryValue).doubleValue(); - builder.add(entry.getKey(), dvalue); - } else if (entryValue instanceof Boolean) { - boolean flag = ((Boolean) entryValue).booleanValue(); - builder.add(entry.getKey(), flag); - } else if (entryValue instanceof String) { - builder.add(entry.getKey(), entryValue.toString()); - } - } - return builder.build(); - } - - JsonValue wrapValue(Object value) { - JsonValue jsonValue = null; - if (value instanceof JsonValue) { - // This may already be a JsonValue - jsonValue = (JsonValue) value; - } else if (value instanceof String) { - jsonValue = Json.createObjectBuilder() - .add(TMP, value.toString()) - .build() - .getJsonString(TMP); - } else if (value instanceof Number) { - Number number = (Number) value; - if ((number instanceof Long) || (number instanceof Integer)) { - jsonValue = Json.createObjectBuilder() - .add(TMP, number.longValue()) - .build() - .getJsonNumber(TMP); - } else { - jsonValue = Json.createObjectBuilder() - .add(TMP, number.doubleValue()) - .build() - .getJsonNumber(TMP); - } - } else if (value instanceof Boolean) { - Boolean flag = (Boolean) value; - jsonValue = flag ? JsonValue.TRUE : JsonValue.FALSE; - } else if (value instanceof Collection) { - JsonArrayBuilder arrayBuilder = Json.createArrayBuilder(); - Collection list = (Collection) value; - for (Object element : list) { - if (element instanceof String) { - arrayBuilder.add(element.toString()); - } else { - JsonValue jvalue = wrapValue(element); - arrayBuilder.add(jvalue); - } - } - jsonValue = arrayBuilder.build(); - } else if (value instanceof Map) { - jsonValue = replaceMap((Map) value); - } - return jsonValue; - } + protected Object getClaimValue(String claimName) { + Object value = super.getClaimValue(claimName); - /** - * Replace the jose4j List with a JsonArray - * - * @param name - claim name - */ - private void replaceList(String name) { - try { - List list = claimsSet.getClaimValue(name, List.class); - JsonArray array = (JsonArray) wrapValue(list); - claimsSet.setClaim(name, array); - } catch (MalformedClaimException e) { - logger.warn("replaceList failure for: " + name, e); + Claims claimType = getClaimType(claimName); + if (claimType == Claims.UNKNOWN && !(value instanceof JsonStructure)) { + value = wrapClaimValue(value); } - } - private void replaceNumber(String name) { - try { - Number number = claimsSet.getClaimValue(name, Number.class); - JsonNumber jsonNumber = (JsonNumber) wrapValue(number); - claimsSet.setClaim(name, jsonNumber); - } catch (MalformedClaimException e) { - logger.warn("replaceNumber failure for: " + name, e); - } + return value; } } diff --git a/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/auth/JWTAccount.java b/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/auth/JWTAccount.java deleted file mode 100644 index 5acabaad8b8c7..0000000000000 --- a/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/auth/JWTAccount.java +++ /dev/null @@ -1,33 +0,0 @@ -package io.quarkus.smallrye.jwt.runtime.auth; - -import java.security.Principal; -import java.util.Set; - -import org.eclipse.microprofile.jwt.JsonWebToken; - -import io.undertow.security.idm.Account; - -/** - * Representation of the caller account using the JWTCallerPrincipal as an Undertow Account object. - */ -public class JWTAccount implements Account { - private JsonWebToken principal; - - private Account delegate; - - public JWTAccount(JsonWebToken principal, Account delegate) { - this.principal = principal; - this.delegate = delegate; - } - - @Override - public Principal getPrincipal() { - return principal; - } - - @Override - public Set getRoles() { - return delegate.getRoles(); - } - -} diff --git a/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/auth/JWTAuthMechanism.java b/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/auth/JWTAuthMechanism.java index 828af8e48dd79..143959b2e14b7 100644 --- a/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/auth/JWTAuthMechanism.java +++ b/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/auth/JWTAuthMechanism.java @@ -1,16 +1,12 @@ package io.quarkus.smallrye.jwt.runtime.auth; import static io.undertow.util.Headers.AUTHORIZATION; +import static io.undertow.util.Headers.COOKIE; import static io.undertow.util.Headers.WWW_AUTHENTICATE; import static io.undertow.util.StatusCodes.UNAUTHORIZED; -import java.util.List; import java.util.Locale; -import javax.inject.Inject; - -import org.eclipse.microprofile.jwt.JsonWebToken; - import io.smallrye.jwt.auth.principal.JWTAuthContextInfo; import io.undertow.UndertowLogger; import io.undertow.security.api.AuthenticationMechanism; @@ -18,14 +14,14 @@ import io.undertow.security.idm.Account; import io.undertow.security.idm.IdentityManager; import io.undertow.server.HttpServerExchange; +import io.undertow.server.handlers.Cookie; /** * An AuthenticationMechanism that validates a caller based on a MicroProfile JWT bearer token */ public class JWTAuthMechanism implements AuthenticationMechanism { - @Inject - private JWTAuthContextInfo authContextInfo; + private JWTAuthContextInfo authContextInfo; private IdentityManager identityManager; public JWTAuthMechanism(JWTAuthContextInfo authContextInfo, IdentityManager identityManager) { @@ -33,10 +29,6 @@ public JWTAuthMechanism(JWTAuthContextInfo authContextInfo, IdentityManager iden this.identityManager = identityManager; } - public JWTAuthMechanism(IdentityManager identityManager) { - this.identityManager = identityManager; - } - /** * Extract the Authorization header and validate the bearer token if it exists. If it does, and is validated, this * builds the org.jboss.security.SecurityContext authenticated Subject that drives the container APIs as well as @@ -48,49 +40,27 @@ public JWTAuthMechanism(IdentityManager identityManager) { */ @Override public AuthenticationMechanismOutcome authenticate(HttpServerExchange exchange, SecurityContext securityContext) { - List authHeaders = exchange.getRequestHeaders().get(AUTHORIZATION); - if (authHeaders != null) { - String bearerToken = null; - for (String current : authHeaders) { - if (current.toLowerCase(Locale.ENGLISH).startsWith("bearer ")) { - bearerToken = current.substring(7); - if (UndertowLogger.SECURITY_LOGGER.isTraceEnabled()) { - UndertowLogger.SECURITY_LOGGER.tracef("Bearer token: %s", bearerToken); - } - try { - //identityManager = securityContext.getIdentityManager(); - JWTCredential credential = new JWTCredential(bearerToken, authContextInfo); - if (UndertowLogger.SECURITY_LOGGER.isTraceEnabled()) { - UndertowLogger.SECURITY_LOGGER.tracef("Bearer token: %s", bearerToken); - } - // Install the JWT principal as the caller - Account account = identityManager.verify(credential.getName(), credential); - if (account != null) { - JsonWebToken jwtPrincipal = (JsonWebToken) account.getPrincipal(); - //MPJWTProducer.setJWTPrincipal(jwtPrincipal); - JWTAccount jwtAccount = new JWTAccount(jwtPrincipal, account); - securityContext.authenticationComplete(jwtAccount, "MP-JWT", false); - /* - * // Workaround authenticated JsonWebToken not being installed as user principal - * // https://issues.jboss.org/browse/WFLY-9212 - * org.jboss.security.SecurityContext jbSC = SecurityContextAssociation.getSecurityContext(); - * Subject subject = jbSC.getUtil().getSubject(); - * jbSC.getUtil().createSubjectInfo(jwtPrincipal, bearerToken, subject); - * RoleGroup roles = extract(subject); - * jbSC.getUtil().setRoles(roles); - */ - UndertowLogger.SECURITY_LOGGER.debugf("Authenticated caller(%s) for path(%s) with roles: %s", - credential.getName(), exchange.getRequestPath(), account.getRoles()); - return AuthenticationMechanismOutcome.AUTHENTICATED; - } else { - UndertowLogger.SECURITY_LOGGER.info("Failed to authenticate JWT bearer token"); - return AuthenticationMechanismOutcome.NOT_AUTHENTICATED; - } - } catch (Exception e) { - UndertowLogger.SECURITY_LOGGER.infof(e, "Failed to validate JWT bearer token"); - return AuthenticationMechanismOutcome.NOT_AUTHENTICATED; - } + String jwtToken = getJwtToken(exchange); + if (jwtToken != null) { + try { + JWTCredential credential = new JWTCredential(jwtToken, authContextInfo); + if (UndertowLogger.SECURITY_LOGGER.isTraceEnabled()) { + UndertowLogger.SECURITY_LOGGER.tracef("Bearer token: %s", jwtToken); + } + // Install the JWT principal as the caller + Account account = identityManager.verify(credential.getName(), credential); + if (account != null) { + securityContext.authenticationComplete(account, "MP-JWT", false); + UndertowLogger.SECURITY_LOGGER.debugf("Authenticated caller(%s) for path(%s) with roles: %s", + credential.getName(), exchange.getRequestPath(), account.getRoles()); + return AuthenticationMechanismOutcome.AUTHENTICATED; + } else { + UndertowLogger.SECURITY_LOGGER.info("Failed to authenticate JWT bearer token"); + return AuthenticationMechanismOutcome.NOT_AUTHENTICATED; } + } catch (Exception e) { + UndertowLogger.SECURITY_LOGGER.infof(e, "Failed to validate JWT bearer token"); + return AuthenticationMechanismOutcome.NOT_AUTHENTICATED; } } @@ -98,6 +68,25 @@ public AuthenticationMechanismOutcome authenticate(HttpServerExchange exchange, return AuthenticationMechanismOutcome.NOT_ATTEMPTED; } + private String getJwtToken(HttpServerExchange exchange) { + String bearerToken = null; + if (AUTHORIZATION.toString().equals(authContextInfo.getTokenHeader())) { + String authScheme = exchange.getRequestHeaders().getFirst(authContextInfo.getTokenHeader()); + if (authScheme != null && authScheme.toLowerCase(Locale.ENGLISH).startsWith("bearer ")) { + bearerToken = authScheme.substring(7); + } + } else if (COOKIE.toString().equals(authContextInfo.getTokenHeader()) + && authContextInfo.getTokenCookie() != null) { + Cookie cookie = exchange.getRequestCookies().get(authContextInfo.getTokenCookie()); + if (cookie != null) { + bearerToken = cookie.getValue(); + } + } else { + bearerToken = exchange.getRequestHeaders().getFirst(authContextInfo.getTokenHeader()); + } + return bearerToken; + } + @Override public ChallengeResult sendChallenge(HttpServerExchange exchange, SecurityContext securityContext) { exchange.getResponseHeaders().add(WWW_AUTHENTICATE, "Bearer {token}"); @@ -105,19 +94,4 @@ public ChallengeResult sendChallenge(HttpServerExchange exchange, SecurityContex return new ChallengeResult(true, UNAUTHORIZED); } - /** - * Extract the Roles group and return it as a RoleGroup - * - * @param subject authenticated subject - * @return RoleGroup from "Roles" - * protected RoleGroup extract(Subject subject) { - * Optional match = subject.getPrincipals() - * .stream() - * .filter(g -> g.getName().equals(SecurityConstants.ROLES_IDENTIFIER)) - * .findFirst(); - * Group rolesGroup = (Group) match.get(); - * RoleGroup roles = new SimpleRoleGroup(rolesGroup); - * return roles; - * } - */ } diff --git a/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/auth/JWTAuthMechanismFactory.java b/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/auth/JWTAuthMechanismFactory.java index 163df401065fa..bff42ce443a3a 100644 --- a/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/auth/JWTAuthMechanismFactory.java +++ b/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/auth/JWTAuthMechanismFactory.java @@ -2,6 +2,8 @@ import java.util.Map; +import javax.inject.Inject; + import io.smallrye.jwt.auth.principal.JWTAuthContextInfo; import io.undertow.security.api.AuthenticationMechanism; import io.undertow.security.api.AuthenticationMechanismFactory; @@ -12,8 +14,11 @@ * An AuthenticationMechanismFactory for the MicroProfile JWT RBAC */ public class JWTAuthMechanismFactory implements AuthenticationMechanismFactory { + @Inject + private JWTAuthContextInfo authContextInfo; - public JWTAuthMechanismFactory() { + public JWTAuthMechanismFactory(JWTAuthContextInfo authContextInfo) { + this.authContextInfo = authContextInfo; } /** @@ -32,7 +37,7 @@ public JWTAuthMechanismFactory() { @Override public AuthenticationMechanism create(String mechanismName, IdentityManager identityManager, FormParserFactory formParserFactory, final Map properties) { - return new JWTAuthMechanism(identityManager); + return new JWTAuthMechanism(authContextInfo, identityManager); } } diff --git a/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/auth/JWTAuthMethodExtension.java b/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/auth/JWTAuthMethodExtension.java index 1cac8219f4522..2c3a03f5fa4da 100644 --- a/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/auth/JWTAuthMethodExtension.java +++ b/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/auth/JWTAuthMethodExtension.java @@ -1,7 +1,9 @@ package io.quarkus.smallrye.jwt.runtime.auth; +import javax.inject.Inject; import javax.servlet.ServletContext; +import io.smallrye.jwt.auth.principal.JWTAuthContextInfo; import io.undertow.servlet.ServletExtension; import io.undertow.servlet.api.DeploymentInfo; @@ -10,6 +12,8 @@ * Additionally, registers an Undertow handler that cleans up MP JWT principal */ public class JWTAuthMethodExtension implements ServletExtension { + @Inject + JWTAuthContextInfo info; private String authMechanism; public String getAuthMechanism() { @@ -28,7 +32,7 @@ public void setAuthMechanism(String authMechanism) { */ @Override public void handleDeployment(DeploymentInfo deploymentInfo, ServletContext servletContext) { - deploymentInfo.addAuthenticationMechanism(authMechanism, new JWTAuthMechanismFactory()); + deploymentInfo.addAuthenticationMechanism(authMechanism, new JWTAuthMechanismFactory(info)); deploymentInfo.addInnerHandlerChainWrapper(MpJwtPrincipalHandler::new); } } diff --git a/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/auth/JwtIdentityManager.java b/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/auth/JwtIdentityManager.java index b9754f6989497..9a2a41008b782 100644 --- a/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/auth/JwtIdentityManager.java +++ b/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/auth/JwtIdentityManager.java @@ -7,9 +7,6 @@ import org.wildfly.security.evidence.BearerTokenEvidence; import io.quarkus.elytron.security.runtime.ElytronAccount; -import io.smallrye.jwt.auth.principal.JWTCallerPrincipal; -import io.smallrye.jwt.auth.principal.JWTCallerPrincipalFactory; -import io.smallrye.jwt.auth.principal.ParseException; import io.undertow.security.idm.Account; import io.undertow.security.idm.Credential; import io.undertow.security.idm.IdentityManager; @@ -55,17 +52,4 @@ public Account verify(String id, Credential credential) { public Account verify(Credential credential) { return null; } - - /** - * Validate the bearer token passed in with the authorization header - * - * @param jwtCredential - the input bearer token - * @return return the validated JWTCallerPrincipal - * @throws ParseException - thrown on token parse or validation failure - */ - protected JWTCallerPrincipal validate(JWTCredential jwtCredential) throws ParseException { - JWTCallerPrincipalFactory factory = JWTCallerPrincipalFactory.instance(); - JWTCallerPrincipal callerPrincipal = factory.parse(jwtCredential.getBearerToken(), jwtCredential.getAuthContextInfo()); - return callerPrincipal; - } } diff --git a/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/auth/MpJwtPrincipalHandler.java b/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/auth/MpJwtPrincipalHandler.java index 8d82e7561efd2..3def533f88ab3 100644 --- a/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/auth/MpJwtPrincipalHandler.java +++ b/extensions/smallrye-jwt/runtime/src/main/java/io/quarkus/smallrye/jwt/runtime/auth/MpJwtPrincipalHandler.java @@ -2,7 +2,9 @@ import javax.enterprise.inject.spi.CDI; -import io.quarkus.smallrye.jwt.runtime.PrincipalProducer; +import org.eclipse.microprofile.jwt.JsonWebToken; + +import io.smallrye.jwt.auth.cdi.PrincipalProducer; import io.undertow.security.idm.Account; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; @@ -31,10 +33,10 @@ public MpJwtPrincipalHandler(HttpHandler next) { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { Account account = exchange.getSecurityContext().getAuthenticatedAccount(); - if (account instanceof JWTAccount) { - JWTAccount jwtAccount = (JWTAccount) account; + if (account != null && account.getPrincipal() instanceof JsonWebToken) { + JsonWebToken token = (JsonWebToken) account.getPrincipal(); PrincipalProducer myInstance = CDI.current().select(PrincipalProducer.class).get(); - myInstance.setAccount(jwtAccount); + myInstance.setJsonWebToken(token); } next.handleRequest(exchange); } 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 1723974100a20..29e67059693e0 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 @@ -1,44 +1,27 @@ package io.quarkus.smallrye.jwt.runtime.auth; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - import javax.enterprise.context.ApplicationScoped; -import javax.enterprise.context.Dependent; import javax.inject.Inject; -import javax.json.JsonArray; -import javax.json.JsonObject; -import org.eclipse.microprofile.jwt.Claims; -import org.jboss.logging.Logger; -import org.jose4j.jwa.AlgorithmConstraints; -import org.jose4j.jwk.JsonWebKey; -import org.jose4j.jws.AlgorithmIdentifiers; import org.jose4j.jwt.JwtClaims; -import org.jose4j.jwt.NumericDate; -import org.jose4j.jwt.consumer.InvalidJwtException; -import org.jose4j.jwt.consumer.JwtConsumer; -import org.jose4j.jwt.consumer.JwtConsumerBuilder; import org.jose4j.jwt.consumer.JwtContext; -import org.jose4j.keys.resolvers.JwksVerificationKeyResolver; import org.wildfly.security.auth.realm.token.TokenValidator; import org.wildfly.security.auth.server.RealmUnavailableException; import org.wildfly.security.authz.Attributes; import org.wildfly.security.evidence.BearerTokenEvidence; +import io.smallrye.jwt.auth.principal.DefaultJWTTokenParser; import io.smallrye.jwt.auth.principal.JWTAuthContextInfo; -import io.smallrye.jwt.auth.principal.KeyLocationResolver; +import io.smallrye.jwt.auth.principal.ParseException; /** * Validates a bearer token according to the MP-JWT rules */ @ApplicationScoped public class MpJwtValidator implements TokenValidator { - private static final String ROLE_MAPPINGS = "roleMappings"; - private static Logger log = Logger.getLogger(MpJwtValidator.class); @Inject JWTAuthContextInfo authContextInfo; + private DefaultJWTTokenParser parser = new DefaultJWTTokenParser(); public MpJwtValidator() { } @@ -49,71 +32,16 @@ public MpJwtValidator(JWTAuthContextInfo authContextInfo) { @Override public Attributes validate(BearerTokenEvidence evidence) throws RealmUnavailableException { - String token = evidence.getToken(); - JwtClaims claimsSet = null; - try { - JwtConsumerBuilder builder = new JwtConsumerBuilder() - .setRequireExpirationTime() - .setRequireSubject() - .setSkipDefaultAudienceValidation() - .setJwsAlgorithmConstraints( - new AlgorithmConstraints(AlgorithmConstraints.ConstraintType.WHITELIST, - AlgorithmIdentifiers.RSA_USING_SHA256)); - - if (authContextInfo.isRequireIssuer()) { - builder.setExpectedIssuer(true, authContextInfo.getIssuedBy()); - } else { - builder.setExpectedIssuer(false, null); - } - if (authContextInfo.getSignerKey() != null) { - builder.setVerificationKey(authContextInfo.getSignerKey()); - } else if (authContextInfo.isFollowMpJwt11Rules()) { - builder.setVerificationKeyResolver(new KeyLocationResolver(authContextInfo.getJwksUri())); - } else { - final List jsonWebKeys = authContextInfo.loadJsonWebKeys(); - builder.setVerificationKeyResolver(new JwksVerificationKeyResolver(jsonWebKeys)); - } - - if (authContextInfo.getExpGracePeriodSecs() > 0) { - builder.setAllowedClockSkewInSeconds(authContextInfo.getExpGracePeriodSecs()); - } else { - builder.setEvaluationTime(NumericDate.fromSeconds(0)); - } - - JwtConsumer jwtConsumer = builder.build(); - JwtContext jwtContext = jwtConsumer.process(token); - String type = jwtContext.getJoseObjects().get(0).getHeader("typ"); - // Validate the JWT and process it to the Claims - jwtConsumer.processContext(jwtContext); - claimsSet = jwtContext.getJwtClaims(); - - // Process the rolesMapping claim - if (claimsSet.hasClaim(ROLE_MAPPINGS)) { - try { - Map rolesMapping = claimsSet.getClaimValue(ROLE_MAPPINGS, Map.class); - List groups = claimsSet.getStringListClaimValue(Claims.groups.name()); - List allGroups = new ArrayList<>(groups); - for (String key : rolesMapping.keySet()) { - // If the key group is in groups list, add the mapped role - if (groups.contains(key)) { - String toRole = rolesMapping.get(key); - allGroups.add(toRole); - } - } - // Replace the groups with the original groups + mapped roles - claimsSet.setStringListClaim("groups", allGroups); - log.infof("Updated groups to: %s", allGroups); - } catch (Exception e) { - log.warnf(e, "Failed to access rolesMapping claim"); - } - } - claimsSet.setClaim(Claims.raw_token.name(), token); + JwtClaims claimsSet = validateClaimsSet(evidence.getToken()); + return new ClaimAttributes(claimsSet); + } - } catch (InvalidJwtException e) { + private JwtClaims validateClaimsSet(String token) throws RealmUnavailableException { + try { + JwtContext jwtContext = parser.parse(token, authContextInfo); + return jwtContext.getJwtClaims(); + } catch (ParseException e) { throw new RealmUnavailableException("Failed to verify token", e); } - - ClaimAttributes claimAttributes = new ClaimAttributes(claimsSet); - return claimAttributes; } } diff --git a/extensions/smallrye-metrics/deployment/pom.xml b/extensions/smallrye-metrics/deployment/pom.xml index e37a3a7019001..0341755d1388a 100644 --- a/extensions/smallrye-metrics/deployment/pom.xml +++ b/extensions/smallrye-metrics/deployment/pom.xml @@ -26,25 +26,29 @@ 4.0.0 - quarkus-smallrye-metrics + quarkus-smallrye-metrics-deployment Quarkus - SmallRye Metrics - Deployment io.quarkus - quarkus-core + quarkus-core-deployment io.quarkus - quarkus-undertow + quarkus-undertow-deployment io.quarkus - quarkus-arc + quarkus-arc-deployment io.quarkus - quarkus-smallrye-metrics-runtime + quarkus-jsonp-deployment + + + io.quarkus + quarkus-smallrye-metrics 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 98572601e6549..4ba4ac6e8cf80 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 @@ -25,6 +25,7 @@ import org.eclipse.microprofile.metrics.annotation.Counted; import org.eclipse.microprofile.metrics.annotation.Gauge; import org.eclipse.microprofile.metrics.annotation.Metered; +import org.eclipse.microprofile.metrics.annotation.Metric; import org.eclipse.microprofile.metrics.annotation.Timed; import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationValue; @@ -32,6 +33,7 @@ import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem; +import io.quarkus.arc.deployment.AutoInjectAnnotationBuildItem; import io.quarkus.arc.deployment.BeanArchiveIndexBuildItem; import io.quarkus.arc.deployment.BeanContainerBuildItem; import io.quarkus.deployment.annotations.BuildProducer; @@ -118,6 +120,11 @@ void annotationTransformers(BuildProducer trans })); } + @BuildStep + AutoInjectAnnotationBuildItem autoInjectMetric() { + return new AutoInjectAnnotationBuildItem(DotName.createSimple(Metric.class.getName())); + } + @BuildStep @Record(STATIC_INIT) public void build(BeanContainerBuildItem beanContainerBuildItem, diff --git a/extensions/smallrye-metrics/runtime/pom.xml b/extensions/smallrye-metrics/runtime/pom.xml index d24443f815ec5..8d4b77d15903b 100644 --- a/extensions/smallrye-metrics/runtime/pom.xml +++ b/extensions/smallrye-metrics/runtime/pom.xml @@ -26,7 +26,7 @@ 4.0.0 - quarkus-smallrye-metrics-runtime + quarkus-smallrye-metrics Quarkus - SmallRye Metrics - Runtime @@ -37,19 +37,19 @@ io.quarkus - quarkus-core-runtime + quarkus-core io.quarkus - quarkus-undertow-runtime + quarkus-undertow io.quarkus - quarkus-arc-runtime + quarkus-arc - org.glassfish - javax.json + io.quarkus + quarkus-jsonp org.jboss.spec.javax.servlet @@ -60,7 +60,8 @@ - maven-dependency-plugin + io.quarkus + quarkus-bootstrap-maven-plugin maven-compiler-plugin diff --git a/extensions/smallrye-openapi-common/deployment/pom.xml b/extensions/smallrye-openapi-common/deployment/pom.xml new file mode 100644 index 0000000000000..7bb85410d8038 --- /dev/null +++ b/extensions/smallrye-openapi-common/deployment/pom.xml @@ -0,0 +1,56 @@ + + + + + + quarkus-smallrye-openapi-common-parent + io.quarkus + 999-SNAPSHOT + ../ + + 4.0.0 + + quarkus-smallrye-openapi-common-deployment + Quarkus - SmallRye OpenAPI - Common - Deployment + + + + io.quarkus + quarkus-core-deployment + + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + + diff --git a/extensions/smallrye-openapi-common/deployment/src/main/java/io/quarkus/smallrye/openapi/common/deployment/SmallRyeOpenApiConfig.java b/extensions/smallrye-openapi-common/deployment/src/main/java/io/quarkus/smallrye/openapi/common/deployment/SmallRyeOpenApiConfig.java new file mode 100644 index 0000000000000..92f0d15bd48e2 --- /dev/null +++ b/extensions/smallrye-openapi-common/deployment/src/main/java/io/quarkus/smallrye/openapi/common/deployment/SmallRyeOpenApiConfig.java @@ -0,0 +1,28 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Licensed 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.smallrye.openapi.common.deployment; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigRoot; + +@ConfigRoot(name = "smallrye-openapi") +public final class SmallRyeOpenApiConfig { + /** + * The path at which to register the OpenAPI Servlet. + */ + @ConfigItem(defaultValue = "/openapi") + public String path; +} diff --git a/extensions/smallrye-openapi-common/pom.xml b/extensions/smallrye-openapi-common/pom.xml new file mode 100644 index 0000000000000..08abdb74f946f --- /dev/null +++ b/extensions/smallrye-openapi-common/pom.xml @@ -0,0 +1,36 @@ + + + + + + quarkus-build-parent + io.quarkus + 999-SNAPSHOT + ../../build-parent/pom.xml + + 4.0.0 + + quarkus-smallrye-openapi-common-parent + Quarkus - SmallRye OpenAPI - Common + pom + + deployment + + + diff --git a/extensions/smallrye-openapi/deployment/pom.xml b/extensions/smallrye-openapi/deployment/pom.xml index 9de6acb068078..82dfc93f69a64 100644 --- a/extensions/smallrye-openapi/deployment/pom.xml +++ b/extensions/smallrye-openapi/deployment/pom.xml @@ -26,29 +26,37 @@ 4.0.0 - quarkus-smallrye-openapi + quarkus-smallrye-openapi-deployment Quarkus - SmallRye OpenAPI - Deployment io.quarkus - quarkus-core + quarkus-core-deployment io.quarkus - quarkus-undertow + quarkus-undertow-deployment io.quarkus - quarkus-resteasy + quarkus-resteasy-deployment io.quarkus - quarkus-arc + quarkus-arc-deployment io.quarkus - quarkus-smallrye-openapi-runtime + quarkus-smallrye-openapi-common-deployment + + + io.quarkus + quarkus-swagger-ui-deployment + + + io.quarkus + quarkus-smallrye-openapi diff --git a/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/RESTEasyExtension.java b/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/RESTEasyExtension.java index bb51dda1054ca..dd02b52ffa68e 100644 --- a/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/RESTEasyExtension.java +++ b/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/RESTEasyExtension.java @@ -16,7 +16,7 @@ import org.jboss.jandex.Type; import io.quarkus.deployment.util.ServiceUtil; -import io.quarkus.resteasy.deployment.ResteasyJaxrsConfig; +import io.quarkus.resteasy.deployment.ResteasyJaxrsConfigBuildItem; import io.smallrye.openapi.api.OpenApiConstants; import io.smallrye.openapi.runtime.scanner.DefaultAnnotationScannerExtension; import io.smallrye.openapi.runtime.scanner.OpenApiAnnotationScanner; @@ -38,7 +38,7 @@ public class RESTEasyExtension extends DefaultAnnotationScannerExtension { private List asyncTypes = new ArrayList<>(); private String defaultPath; - public RESTEasyExtension(ResteasyJaxrsConfig jaxrsConfig, IndexView index) { + public RESTEasyExtension(ResteasyJaxrsConfigBuildItem jaxrsConfig, IndexView index) { this.defaultPath = jaxrsConfig.defaultPath; // the index is not enough to scan for providers because it does not contain // dependencies, so we have to rely on scanning the declared providers via services diff --git a/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java b/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java index a654f69019c58..4019a2bcad0c8 100644 --- a/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java +++ b/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java @@ -23,15 +23,25 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; import org.eclipse.microprofile.config.Config; import org.eclipse.microprofile.config.ConfigProvider; +import org.eclipse.microprofile.openapi.annotations.media.Schema; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponses; import org.eclipse.microprofile.openapi.models.OpenAPI; +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationTarget; +import org.jboss.jandex.AnnotationValue; +import org.jboss.jandex.DotName; import org.jboss.jandex.IndexView; +import org.jboss.jandex.Type; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; import io.quarkus.arc.deployment.BeanContainerListenerBuildItem; @@ -42,9 +52,11 @@ import io.quarkus.deployment.builditem.CombinedIndexBuildItem; import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.deployment.builditem.HotDeploymentConfigFileBuildItem; -import io.quarkus.resteasy.deployment.ResteasyJaxrsConfig; -import io.quarkus.runtime.annotations.ConfigItem; -import io.quarkus.runtime.annotations.ConfigRoot; +import io.quarkus.deployment.builditem.substrate.ReflectiveClassBuildItem; +import io.quarkus.deployment.builditem.substrate.ReflectiveHierarchyBuildItem; +import io.quarkus.deployment.logging.LogCleanupFilterBuildItem; +import io.quarkus.resteasy.deployment.ResteasyJaxrsConfigBuildItem; +import io.quarkus.smallrye.openapi.common.deployment.SmallRyeOpenApiConfig; import io.quarkus.smallrye.openapi.runtime.OpenApiDocumentProducer; import io.quarkus.smallrye.openapi.runtime.OpenApiServlet; import io.quarkus.smallrye.openapi.runtime.SmallRyeOpenApiTemplate; @@ -67,16 +79,19 @@ public class SmallRyeOpenApiProcessor { private static final String META_INF_OPENAPI_JSON = "META-INF/openapi.json"; private static final String WEB_INF_CLASSES_META_INF_OPENAPI_JSON = "WEB-INF/classes/META-INF/openapi.json"; - SmallRyeOpenApiConfig openapi; + private static final DotName OPENAPI_SCHEMA = DotName.createSimple(Schema.class.getName()); + private static final DotName OPENAPI_RESPONSE = DotName.createSimple(APIResponse.class.getName()); + private static final DotName OPENAPI_RESPONSES = DotName.createSimple(APIResponses.class.getName()); - @ConfigRoot(name = "smallrye-openapi") - static final class SmallRyeOpenApiConfig { - /** - * The path at which to register the OpenAPI Servlet. - */ - @ConfigItem(defaultValue = "/openapi") - String path; - } + private static final String OPENAPI_RESPONSE_CONTENT = "content"; + private static final String OPENAPI_RESPONSE_SCHEMA = "schema"; + private static final String OPENAPI_SCHEMA_NOT = "not"; + private static final String OPENAPI_SCHEMA_ONE_OF = "oneOf"; + private static final String OPENAPI_SCHEMA_ANY_OF = "anyOf"; + private static final String OPENAPI_SCHEMA_ALL_OF = "allOf"; + private static final String OPENAPI_SCHEMA_IMPLEMENTATION = "implementation"; + + SmallRyeOpenApiConfig openapi; List configFiles() { return Stream.of(META_INF_OPENAPI_YAML, WEB_INF_CLASSES_META_INF_OPENAPI_YAML, @@ -92,9 +107,90 @@ ServletBuildItem servlet() { } @BuildStep - List beans() { - return Arrays.asList(new AdditionalBeanBuildItem(OpenApiServlet.class), - new AdditionalBeanBuildItem(OpenApiDocumentProducer.class)); + AdditionalBeanBuildItem beans() { + return new AdditionalBeanBuildItem(OpenApiServlet.class, OpenApiDocumentProducer.class); + } + + @BuildStep + public void registerOpenApiSchemaClassesForReflection(BuildProducer reflectiveClass, + BuildProducer reflectiveHierarchy, CombinedIndexBuildItem combinedIndexBuildItem) { + IndexView index = combinedIndexBuildItem.getIndex(); + + // Generate reflection declaration from MP OpenAPI Schema definition + // They are needed for serialization. + Collection schemaAnnotationInstances = index.getAnnotations(OPENAPI_SCHEMA); + for (AnnotationInstance schemaAnnotationInstance : schemaAnnotationInstances) { + AnnotationTarget typeTarget = schemaAnnotationInstance.target(); + if (typeTarget.kind() != AnnotationTarget.Kind.CLASS) { + continue; + } + reflectiveHierarchy + .produce(new ReflectiveHierarchyBuildItem(Type.create(typeTarget.asClass().name(), Type.Kind.CLASS))); + } + + // Generate reflection declaration from MP OpenAPI APIResponse schema definition + // They are needed for serialization + Collection apiResponseAnnotationInstances = index.getAnnotations(OPENAPI_RESPONSE); + registerReflectionForApiResponseSchemaSerialization(reflectiveClass, reflectiveHierarchy, + apiResponseAnnotationInstances); + + // Generate reflection declaration from MP OpenAPI APIResponses schema definition + // They are needed for serialization + Collection apiResponsesAnnotationInstances = index.getAnnotations(OPENAPI_RESPONSES); + for (AnnotationInstance apiResponsesAnnotationInstance : apiResponsesAnnotationInstances) { + AnnotationValue apiResponsesAnnotationValue = apiResponsesAnnotationInstance.value(); + if (apiResponsesAnnotationValue == null) { + continue; + } + registerReflectionForApiResponseSchemaSerialization(reflectiveClass, reflectiveHierarchy, + Arrays.asList(apiResponsesAnnotationValue.asNestedArray())); + } + } + + private void registerReflectionForApiResponseSchemaSerialization(BuildProducer reflectiveClass, + BuildProducer reflectiveHierarchy, + Collection apiResponseAnnotationInstances) { + for (AnnotationInstance apiResponseAnnotationInstance : apiResponseAnnotationInstances) { + AnnotationValue contentAnnotationValue = apiResponseAnnotationInstance.value(OPENAPI_RESPONSE_CONTENT); + if (contentAnnotationValue == null) { + continue; + } + + AnnotationInstance[] contents = contentAnnotationValue.asNestedArray(); + for (AnnotationInstance content : contents) { + AnnotationInstance schema = content.value(OPENAPI_RESPONSE_SCHEMA).asNested(); + AnnotationValue schemaImplementationClass = schema.value(OPENAPI_SCHEMA_IMPLEMENTATION); + if (schemaImplementationClass != null) { + reflectiveHierarchy.produce(new ReflectiveHierarchyBuildItem(schemaImplementationClass.asClass())); + } + + AnnotationValue schemaNotClass = schema.value(OPENAPI_SCHEMA_NOT); + if (schemaNotClass != null) { + reflectiveClass.produce(new ReflectiveClassBuildItem(true, true, schemaNotClass.asString())); + } + + AnnotationValue schemaOneOfClasses = schema.value(OPENAPI_SCHEMA_ONE_OF); + if (schemaOneOfClasses != null) { + for (Type schemaOneOfClass : schemaOneOfClasses.asClassArray()) { + reflectiveHierarchy.produce(new ReflectiveHierarchyBuildItem(schemaOneOfClass)); + } + } + + AnnotationValue schemaAnyOfClasses = schema.value(OPENAPI_SCHEMA_ANY_OF); + if (schemaAnyOfClasses != null) { + for (Type schemaAnyOfClass : schemaAnyOfClasses.asClassArray()) { + reflectiveHierarchy.produce(new ReflectiveHierarchyBuildItem(schemaAnyOfClass)); + } + } + + AnnotationValue schemaAllOfClasses = schema.value(OPENAPI_SCHEMA_ALL_OF); + if (schemaAllOfClasses != null) { + for (Type schemaAllOfClass : schemaAllOfClasses.asClassArray()) { + reflectiveHierarchy.produce(new ReflectiveHierarchyBuildItem(schemaAllOfClass)); + } + } + } + } } @BuildStep @@ -102,11 +198,22 @@ List beans() { public BeanContainerListenerBuildItem build(SmallRyeOpenApiTemplate template, ApplicationArchivesBuildItem archivesBuildItem, CombinedIndexBuildItem combinedIndexBuildItem, BuildProducer feature, - ResteasyJaxrsConfig jaxrsConfig) throws Exception { + Optional resteasyJaxrsConfig) throws Exception { feature.produce(new FeatureBuildItem(FeatureBuildItem.SMALLRYE_OPENAPI)); - OpenAPI sm = generateStaticModel(archivesBuildItem); - OpenAPI am = generateAnnotationModel(combinedIndexBuildItem.getIndex(), jaxrsConfig); - return new BeanContainerListenerBuildItem(template.setupModel(sm, am)); + OpenAPI staticModel = generateStaticModel(archivesBuildItem); + OpenAPI annotationModel; + if (resteasyJaxrsConfig.isPresent()) { + annotationModel = generateAnnotationModel(combinedIndexBuildItem.getIndex(), resteasyJaxrsConfig.get()); + } else { + annotationModel = null; + } + return new BeanContainerListenerBuildItem(template.setupModel(staticModel, annotationModel)); + } + + @BuildStep + LogCleanupFilterBuildItem logCleanup() { + return new LogCleanupFilterBuildItem("io.smallrye.openapi.api.OpenApiDocument", + "OpenAPI document initialized:"); } private OpenAPI generateStaticModel(ApplicationArchivesBuildItem archivesBuildItem) throws IOException { @@ -120,7 +227,7 @@ private OpenAPI generateStaticModel(ApplicationArchivesBuildItem archivesBuildIt return null; } - private OpenAPI generateAnnotationModel(IndexView indexView, ResteasyJaxrsConfig jaxrsConfig) { + private OpenAPI generateAnnotationModel(IndexView indexView, ResteasyJaxrsConfigBuildItem jaxrsConfig) { Config config = ConfigProvider.getConfig(); OpenApiConfig openApiConfig = new OpenApiConfigImpl(config); return new OpenApiAnnotationScanner(openApiConfig, indexView, diff --git a/extensions/smallrye-openapi/runtime/pom.xml b/extensions/smallrye-openapi/runtime/pom.xml index 061741ca407ea..12aa10d3c287d 100644 --- a/extensions/smallrye-openapi/runtime/pom.xml +++ b/extensions/smallrye-openapi/runtime/pom.xml @@ -26,7 +26,7 @@ 4.0.0 - quarkus-smallrye-openapi-runtime + quarkus-smallrye-openapi Quarkus - SmallRye OpenAPI - Runtime @@ -46,15 +46,19 @@ io.quarkus - quarkus-core-runtime + quarkus-core io.quarkus - quarkus-undertow-runtime + quarkus-undertow io.quarkus - quarkus-arc-runtime + quarkus-arc + + + io.quarkus + quarkus-swagger-ui com.fasterxml.jackson.core @@ -65,7 +69,8 @@ - maven-dependency-plugin + io.quarkus + quarkus-bootstrap-maven-plugin maven-compiler-plugin diff --git a/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/OpenApiServlet.java b/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/OpenApiServlet.java index 8624ce3c2f85f..383d560877c40 100644 --- a/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/OpenApiServlet.java +++ b/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/OpenApiServlet.java @@ -68,8 +68,9 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws Se String oai = getCachedOaiString(format); addCorsResponseHeaders(resp); - resp.addHeader("Content-Type", format.getMimeType()); - resp.getOutputStream().print(oai); + resp.setHeader("Content-Type", format.getMimeType()); + resp.setCharacterEncoding("UTF-8"); + resp.getWriter().print(oai); } void setOpenApiDocument(OpenApiDocument document) { @@ -89,10 +90,10 @@ private String getModel(OpenApiSerializer.Format format) { } private static void addCorsResponseHeaders(HttpServletResponse response) { - response.addHeader("Access-Control-Allow-Origin", "*"); - response.addHeader("Access-Control-Allow-Credentials", "true"); - response.addHeader("Access-Control-Allow-Methods", ALLOWED_METHODS); - response.addHeader("Access-Control-Allow-Headers", "Content-Type, Authorization"); - response.addHeader("Access-Control-Max-Age", "86400"); + response.setHeader("Access-Control-Allow-Origin", "*"); + response.setHeader("Access-Control-Allow-Credentials", "true"); + response.setHeader("Access-Control-Allow-Methods", ALLOWED_METHODS); + response.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization"); + response.setHeader("Access-Control-Max-Age", "86400"); } } diff --git a/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/SmallRyeOpenApiTemplate.java b/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/SmallRyeOpenApiTemplate.java index beb9ebfae5a9b..860345c610373 100644 --- a/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/SmallRyeOpenApiTemplate.java +++ b/extensions/smallrye-openapi/runtime/src/main/java/io/quarkus/smallrye/openapi/runtime/SmallRyeOpenApiTemplate.java @@ -23,7 +23,6 @@ import io.quarkus.arc.runtime.BeanContainer; import io.quarkus.arc.runtime.BeanContainerListener; -import io.quarkus.runtime.Quarkus; import io.quarkus.runtime.annotations.Template; import io.smallrye.openapi.api.OpenApiConfig; import io.smallrye.openapi.api.OpenApiConfigImpl; @@ -43,10 +42,13 @@ public void created(BeanContainer container) { Config config = ConfigProvider.getConfig(); OpenApiConfig openApiConfig = new OpenApiConfigImpl(config); - OpenAPI readerModel = OpenApiProcessor.modelFromReader(openApiConfig, Quarkus.class.getClassLoader()); + OpenAPI readerModel = OpenApiProcessor.modelFromReader(openApiConfig, + Thread.currentThread().getContextClassLoader()); OpenApiDocument document = createDocument(openApiConfig); - document.modelFromAnnotations(annotationModel); + if (annotationModel != null) { + document.modelFromAnnotations(annotationModel); + } document.modelFromReader(readerModel); document.modelFromStaticFile(staticModel); document.filter(filter(openApiConfig)); @@ -64,6 +66,7 @@ private OpenApiDocument createDocument(OpenApiConfig openApiConfig) { } private OASFilter filter(OpenApiConfig openApiConfig) { - return OpenApiProcessor.getFilter(openApiConfig, Quarkus.class.getClassLoader()); + return OpenApiProcessor.getFilter(openApiConfig, + Thread.currentThread().getContextClassLoader()); } } diff --git a/extensions/smallrye-opentracing/deployment/pom.xml b/extensions/smallrye-opentracing/deployment/pom.xml index 17739bb010e2f..8beafd45a3e8a 100644 --- a/extensions/smallrye-opentracing/deployment/pom.xml +++ b/extensions/smallrye-opentracing/deployment/pom.xml @@ -26,41 +26,74 @@ 4.0.0 - quarkus-smallrye-opentracing + quarkus-smallrye-opentracing-deployment Quarkus - SmallRye OpenTracing - Deployment io.quarkus - quarkus-core + quarkus-core-deployment io.quarkus - quarkus-resteasy + quarkus-resteasy-deployment io.quarkus - quarkus-undertow + quarkus-undertow-deployment io.quarkus - quarkus-arc + quarkus-arc-deployment io.quarkus - quarkus-smallrye-opentracing-runtime + quarkus-smallrye-opentracing io.quarkus - quarkus-jaeger + quarkus-jaeger-deployment - io.smallrye - smallrye-opentracing + io.quarkus + quarkus-jsonp-deployment + + + + io.quarkus + quarkus-smallrye-rest-client-deployment + test + + + io.quarkus + quarkus-smallrye-fault-tolerance-deployment + test + + + io.quarkus + quarkus-junit5-internal + test + + + io.rest-assured + rest-assured + test + + + io.opentracing + opentracing-mock + test + + + io.opentracing + opentracing-util + test-jar + test - javax.enterprise - cdi-api + org.awaitility + awaitility + test diff --git a/extensions/smallrye-opentracing/deployment/src/main/java/io/quarkus/smallrye/opentracing/deployment/SmallRyeOpenTracingProcessor.java b/extensions/smallrye-opentracing/deployment/src/main/java/io/quarkus/smallrye/opentracing/deployment/SmallRyeOpenTracingProcessor.java index a75496dc4c076..ff5964f1a0151 100644 --- a/extensions/smallrye-opentracing/deployment/src/main/java/io/quarkus/smallrye/opentracing/deployment/SmallRyeOpenTracingProcessor.java +++ b/extensions/smallrye-opentracing/deployment/src/main/java/io/quarkus/smallrye/opentracing/deployment/SmallRyeOpenTracingProcessor.java @@ -17,8 +17,6 @@ package io.quarkus.smallrye.opentracing.deployment; import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.List; import javax.enterprise.inject.spi.ObserverMethod; import javax.servlet.DispatcherType; @@ -30,7 +28,7 @@ import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.deployment.builditem.substrate.ReflectiveMethodBuildItem; -import io.quarkus.resteasy.deployment.ResteasyJaxrsProviderBuildItem; +import io.quarkus.resteasy.common.deployment.ResteasyJaxrsProviderBuildItem; import io.quarkus.smallrye.opentracing.runtime.QuarkusSmallRyeTracingDynamicFeature; import io.quarkus.smallrye.opentracing.runtime.TracerProducer; import io.quarkus.undertow.deployment.FilterBuildItem; @@ -38,8 +36,8 @@ public class SmallRyeOpenTracingProcessor { @BuildStep - List registerBeans() { - return Arrays.asList(new AdditionalBeanBuildItem(OpenTracingInterceptor.class, TracerProducer.class)); + AdditionalBeanBuildItem registerBeans() { + return new AdditionalBeanBuildItem(OpenTracingInterceptor.class, TracerProducer.class); } @BuildStep diff --git a/extensions/smallrye-opentracing/deployment/src/test/java/io/quarkus/smallrye/opentracing/deployment/RestService.java b/extensions/smallrye-opentracing/deployment/src/test/java/io/quarkus/smallrye/opentracing/deployment/RestService.java new file mode 100644 index 0000000000000..b1a340af3f4f3 --- /dev/null +++ b/extensions/smallrye-opentracing/deployment/src/test/java/io/quarkus/smallrye/opentracing/deployment/RestService.java @@ -0,0 +1,28 @@ +package io.quarkus.smallrye.opentracing.deployment; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.core.Response; + +import org.eclipse.microprofile.opentracing.Traced; + +@Path("/") +public interface RestService { + + @GET + @Path("/hello") + Response hello(); + + @GET + @Path("/cdi") + Response cdi(); + + @GET + @Path("/restClient") + Response restClient(); + + @GET + @Traced(false) + @Path("/faultTolerance") + Response faultTolerance(); +} diff --git a/extensions/smallrye-opentracing/deployment/src/test/java/io/quarkus/smallrye/opentracing/deployment/Service.java b/extensions/smallrye-opentracing/deployment/src/test/java/io/quarkus/smallrye/opentracing/deployment/Service.java new file mode 100644 index 0000000000000..23d266e5512e8 --- /dev/null +++ b/extensions/smallrye-opentracing/deployment/src/test/java/io/quarkus/smallrye/opentracing/deployment/Service.java @@ -0,0 +1,36 @@ +package io.quarkus.smallrye.opentracing.deployment; + +import java.time.temporal.ChronoUnit; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; + +import org.eclipse.microprofile.faulttolerance.Fallback; +import org.eclipse.microprofile.faulttolerance.Retry; +import org.eclipse.microprofile.faulttolerance.Timeout; +import org.eclipse.microprofile.opentracing.Traced; + +import io.opentracing.Tracer; + +@Traced +@ApplicationScoped +public class Service { + + @Inject + private Tracer tracer; + + public void foo() { + } + + @Fallback(fallbackMethod = "fallback") + @Timeout(value = 20L, unit = ChronoUnit.MILLIS) + @Retry(delay = 10L, maxRetries = 2) + public String faultTolerance() { + tracer.buildSpan("ft").start().finish(); + throw new RuntimeException(); + } + + public String fallback() { + return "fallback"; + } +} diff --git a/extensions/smallrye-opentracing/deployment/src/test/java/io/quarkus/smallrye/opentracing/deployment/TestResource.java b/extensions/smallrye-opentracing/deployment/src/test/java/io/quarkus/smallrye/opentracing/deployment/TestResource.java new file mode 100644 index 0000000000000..eb17dc303940e --- /dev/null +++ b/extensions/smallrye-opentracing/deployment/src/test/java/io/quarkus/smallrye/opentracing/deployment/TestResource.java @@ -0,0 +1,45 @@ +package io.quarkus.smallrye.opentracing.deployment; + +import javax.inject.Inject; +import javax.ws.rs.Path; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; + +import org.eclipse.microprofile.rest.client.RestClientBuilder; + +@Path("/") +public class TestResource implements RestService { + + @Inject + private Service service; + + @Context + private UriInfo uri; + + @Override + public Response hello() { + return Response.ok().build(); + } + + @Override + public Response cdi() { + service.foo(); + return Response.ok().build(); + } + + @Override + public Response restClient() { + RestService client = RestClientBuilder.newBuilder() + .baseUri(uri.getBaseUri()) + .build(RestService.class); + client.hello(); + return Response.ok().build(); + } + + @Override + public Response faultTolerance() { + String ret = service.faultTolerance(); + return Response.ok(ret).build(); + } +} diff --git a/extensions/smallrye-opentracing/deployment/src/test/java/io/quarkus/smallrye/opentracing/deployment/TracerRegistrar.java b/extensions/smallrye-opentracing/deployment/src/test/java/io/quarkus/smallrye/opentracing/deployment/TracerRegistrar.java new file mode 100644 index 0000000000000..3e86ea8b8d216 --- /dev/null +++ b/extensions/smallrye-opentracing/deployment/src/test/java/io/quarkus/smallrye/opentracing/deployment/TracerRegistrar.java @@ -0,0 +1,16 @@ +package io.quarkus.smallrye.opentracing.deployment; + +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import javax.servlet.annotation.WebListener; + +import io.opentracing.util.GlobalTracer; + +@WebListener +public class TracerRegistrar implements ServletContextListener { + + @Override + public void contextInitialized(ServletContextEvent sce) { + GlobalTracer.register(TracingTest.mockTracer); + } +} diff --git a/extensions/smallrye-opentracing/deployment/src/test/java/io/quarkus/smallrye/opentracing/deployment/TracingTest.java b/extensions/smallrye-opentracing/deployment/src/test/java/io/quarkus/smallrye/opentracing/deployment/TracingTest.java new file mode 100644 index 0000000000000..079d89d3d945c --- /dev/null +++ b/extensions/smallrye-opentracing/deployment/src/test/java/io/quarkus/smallrye/opentracing/deployment/TracingTest.java @@ -0,0 +1,126 @@ +package io.quarkus.smallrye.opentracing.deployment; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.awaitility.Awaitility; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.EmptyAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +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; + +import io.opentracing.mock.MockSpan; +import io.opentracing.mock.MockTracer; +import io.opentracing.util.GlobalTracer; +import io.opentracing.util.GlobalTracerTestUtil; +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; +import io.restassured.parsing.Parser; +import io.restassured.response.Response; + +public class TracingTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClass(TestResource.class) + .addClass(TracerRegistrar.class) + .addClass(Service.class) + .addClass(RestService.class) + .addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml")); + + static MockTracer mockTracer = new MockTracer(); + + @AfterEach + public void after() { + mockTracer.reset(); + } + + @AfterAll + public static void afterAll() { + GlobalTracerTestUtil.resetGlobalTracer(); + } + + @Test + public void testSingleServerRequest() { + try { + RestAssured.defaultParser = Parser.TEXT; + RestAssured.when().get("/hello") + .then() + .statusCode(200); + Assertions.assertEquals(1, mockTracer.finishedSpans().size()); + Assertions.assertEquals("GET:io.quarkus.smallrye.opentracing.deployment.TestResource.hello", + mockTracer.finishedSpans().get(0).operationName()); + } finally { + RestAssured.reset(); + } + } + + @Test + public void testCDI() { + try { + RestAssured.defaultParser = Parser.TEXT; + RestAssured.when().get("/cdi") + .then() + .statusCode(200); + Assertions.assertEquals(2, mockTracer.finishedSpans().size()); + Assertions.assertEquals("io.quarkus.smallrye.opentracing.deployment.Service.foo", + mockTracer.finishedSpans().get(0).operationName()); + Assertions.assertEquals("GET:io.quarkus.smallrye.opentracing.deployment.TestResource.cdi", + mockTracer.finishedSpans().get(1).operationName()); + } finally { + RestAssured.reset(); + } + } + + @Test + public void testMPRestClient() { + try { + RestAssured.defaultParser = Parser.TEXT; + RestAssured.when().get("/restClient") + .then() + .statusCode(200); + Assertions.assertEquals(3, mockTracer.finishedSpans().size()); + Assertions.assertEquals("GET:io.quarkus.smallrye.opentracing.deployment.TestResource.hello", + mockTracer.finishedSpans().get(0).operationName()); + Assertions.assertEquals("GET", mockTracer.finishedSpans().get(1).operationName()); + Assertions.assertEquals("GET:io.quarkus.smallrye.opentracing.deployment.TestResource.restClient", + mockTracer.finishedSpans().get(2).operationName()); + } finally { + RestAssured.reset(); + } + } + + @Test + @Disabled("https://github.com/quarkusio/quarkus/issues/2187") + public void testContextPropagationInFaultTolerance() { + try { + RestAssured.defaultParser = Parser.TEXT; + Response response = RestAssured.when().get("/faultTolerance"); + response.then().statusCode(200); + Assertions.assertEquals("fallback", response.body().asString()); + Awaitility.await().atMost(5, TimeUnit.SECONDS) + .until(() -> mockTracer.finishedSpans().size() == 5); + List spans = mockTracer.finishedSpans(); + + Assertions.assertEquals(5, spans.size()); + for (MockSpan mockSpan : spans) { + Assertions.assertEquals(spans.get(0).context().traceId(), mockSpan.context().traceId()); + } + Assertions.assertEquals("ft", mockTracer.finishedSpans().get(0).operationName()); + Assertions.assertEquals("ft", mockTracer.finishedSpans().get(1).operationName()); + Assertions.assertEquals("ft", mockTracer.finishedSpans().get(2).operationName()); + Assertions.assertEquals("io.quarkus.smallrye.opentracing.deployment.Service.fallback", + mockTracer.finishedSpans().get(3).operationName()); + Assertions.assertEquals("io.quarkus.smallrye.opentracing.deployment.Service.faultTolerance", + mockTracer.finishedSpans().get(4).operationName()); + } finally { + RestAssured.reset(); + } + } +} diff --git a/extensions/smallrye-opentracing/runtime/pom.xml b/extensions/smallrye-opentracing/runtime/pom.xml index cf9506c25f42d..a7061ab8ab3c4 100644 --- a/extensions/smallrye-opentracing/runtime/pom.xml +++ b/extensions/smallrye-opentracing/runtime/pom.xml @@ -26,11 +26,10 @@ 4.0.0 - quarkus-smallrye-opentracing-runtime + quarkus-smallrye-opentracing Quarkus - SmallRye OpenTracing - Runtime - org.eclipse.microprofile.config microprofile-config-api @@ -45,27 +44,23 @@ io.quarkus - quarkus-core-runtime + quarkus-core io.quarkus - quarkus-undertow-runtime - - - org.glassfish - javax.json + quarkus-jaeger - org.jboss.spec.javax.servlet - jboss-servlet-api_4.0_spec + io.quarkus + quarkus-undertow - io.jaegertracing - jaeger-core + io.quarkus + quarkus-jsonp - io.jaegertracing - jaeger-thrift + org.jboss.spec.javax.servlet + jboss-servlet-api_4.0_spec com.oracle.substratevm @@ -81,7 +76,8 @@ - maven-dependency-plugin + io.quarkus + quarkus-bootstrap-maven-plugin maven-compiler-plugin diff --git a/extensions/smallrye-opentracing/runtime/src/main/java/io/quarkus/smallrye/opentracing/runtime/TracerProducer.java b/extensions/smallrye-opentracing/runtime/src/main/java/io/quarkus/smallrye/opentracing/runtime/TracerProducer.java index c1f77bcc2dfdd..d6fb988c63256 100644 --- a/extensions/smallrye-opentracing/runtime/src/main/java/io/quarkus/smallrye/opentracing/runtime/TracerProducer.java +++ b/extensions/smallrye-opentracing/runtime/src/main/java/io/quarkus/smallrye/opentracing/runtime/TracerProducer.java @@ -16,9 +16,9 @@ package io.quarkus.smallrye.opentracing.runtime; +import javax.enterprise.context.ApplicationScoped; import javax.enterprise.context.Dependent; import javax.enterprise.inject.Produces; -import javax.enterprise.inject.spi.InjectionPoint; import io.opentracing.Tracer; import io.opentracing.util.GlobalTracer; @@ -29,10 +29,8 @@ @Dependent public class TracerProducer { @Produces - @Dependent - Tracer tracer(InjectionPoint ip) { - Tracer tracer = GlobalTracer.get(); - System.err.println("producing tracer: " + tracer + " for " + ip); - return tracer; + @ApplicationScoped + Tracer tracer() { + return GlobalTracer.get(); } } diff --git a/extensions/smallrye-reactive-messaging-kafka/deployment/pom.xml b/extensions/smallrye-reactive-messaging-kafka/deployment/pom.xml index 0460927c956a9..b0198fa759bf1 100644 --- a/extensions/smallrye-reactive-messaging-kafka/deployment/pom.xml +++ b/extensions/smallrye-reactive-messaging-kafka/deployment/pom.xml @@ -10,29 +10,29 @@ 999-SNAPSHOT - quarkus-smallrye-reactive-messaging-kafka + quarkus-smallrye-reactive-messaging-kafka-deployment Quarkus - SmallRye Reactive Messaging - Kafka - Deployment io.quarkus - quarkus-core + quarkus-core-deployment io.quarkus - quarkus-smallrye-reactive-messaging + quarkus-smallrye-reactive-messaging-deployment io.quarkus - quarkus-kafka-client + quarkus-kafka-client-deployment io.quarkus - quarkus-smallrye-reactive-messaging-kafka-runtime + quarkus-smallrye-reactive-messaging-kafka io.quarkus - quarkus-vertx + quarkus-vertx-deployment diff --git a/extensions/smallrye-reactive-messaging-kafka/runtime/pom.xml b/extensions/smallrye-reactive-messaging-kafka/runtime/pom.xml index 81fad82ee3250..1f5b083eef836 100644 --- a/extensions/smallrye-reactive-messaging-kafka/runtime/pom.xml +++ b/extensions/smallrye-reactive-messaging-kafka/runtime/pom.xml @@ -10,13 +10,13 @@ 999-SNAPSHOT - quarkus-smallrye-reactive-messaging-kafka-runtime + quarkus-smallrye-reactive-messaging-kafka Quarkus - SmallRye Reactive Messaging - Kafka - Runtime io.quarkus - quarkus-kafka-client-runtime + quarkus-kafka-client io.smallrye.reactive @@ -34,7 +34,11 @@ io.quarkus - quarkus-smallrye-reactive-streams-operators-runtime + quarkus-smallrye-reactive-streams-operators + + + io.quarkus + quarkus-smallrye-reactive-messaging io.smallrye.reactive @@ -56,7 +60,8 @@ - maven-dependency-plugin + io.quarkus + quarkus-bootstrap-maven-plugin maven-compiler-plugin diff --git a/extensions/smallrye-reactive-messaging/deployment/pom.xml b/extensions/smallrye-reactive-messaging/deployment/pom.xml index 97f5f8a2b70f9..89aa3f6d34ed8 100644 --- a/extensions/smallrye-reactive-messaging/deployment/pom.xml +++ b/extensions/smallrye-reactive-messaging/deployment/pom.xml @@ -11,29 +11,32 @@ 4.0.0 - quarkus-smallrye-reactive-messaging + quarkus-smallrye-reactive-messaging-deployment Quarkus - SmallRye Reactive Messaging - Deployment io.quarkus - quarkus-core + quarkus-core-deployment io.quarkus - quarkus-arc + quarkus-arc-deployment io.quarkus - quarkus-smallrye-reactive-messaging-runtime + quarkus-smallrye-reactive-messaging - - + + io.quarkus + quarkus-smallrye-reactive-streams-operators + + io.quarkus quarkus-junit5-internal - + diff --git a/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/EmitterBuildItem.java b/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/EmitterBuildItem.java new file mode 100644 index 0000000000000..a4ae5c49b5f02 --- /dev/null +++ b/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/EmitterBuildItem.java @@ -0,0 +1,35 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Licensed 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.smallrye.reactivemessaging.deployment; + +import io.quarkus.builder.item.MultiBuildItem; + +public final class EmitterBuildItem extends MultiBuildItem { + + /** + * The name of the stream the emitter is connected to. + */ + private final String name; + + public EmitterBuildItem(String name) { + this.name = name; + } + + public String getName() { + return name; + } + +} diff --git a/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/MediatorBuildItem.java b/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/MediatorBuildItem.java index 0c5c6cd65db24..b83ed42e0723c 100644 --- a/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/MediatorBuildItem.java +++ b/extensions/smallrye-reactive-messaging/deployment/src/main/java/io/quarkus/smallrye/reactivemessaging/deployment/MediatorBuildItem.java @@ -15,10 +15,10 @@ */ package io.quarkus.smallrye.reactivemessaging.deployment; -import org.jboss.builder.item.MultiBuildItem; import org.jboss.jandex.MethodInfo; import io.quarkus.arc.processor.BeanInfo; +import io.quarkus.builder.item.MultiBuildItem; public final class MediatorBuildItem extends MultiBuildItem { 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 e014fea1fb13f..329d61c2f51d6 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 @@ -17,14 +17,15 @@ import static io.quarkus.deployment.annotations.ExecutionTime.STATIC_INIT; -import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import org.eclipse.microprofile.reactive.messaging.Incoming; import org.eclipse.microprofile.reactive.messaging.Outgoing; +import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.DotName; import org.jboss.jandex.MethodInfo; import org.jboss.logging.Logger; @@ -37,6 +38,7 @@ import io.quarkus.arc.processor.AnnotationStore; import io.quarkus.arc.processor.BeanDeploymentValidator; import io.quarkus.arc.processor.BeanInfo; +import io.quarkus.arc.processor.InjectionPointInfo; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.annotations.Record; @@ -44,6 +46,8 @@ import io.quarkus.deployment.builditem.substrate.ReflectiveClassBuildItem; import io.quarkus.smallrye.reactivemessaging.runtime.SmallRyeReactiveMessagingLifecycle; import io.quarkus.smallrye.reactivemessaging.runtime.SmallRyeReactiveMessagingTemplate; +import io.smallrye.reactive.messaging.annotations.Emitter; +import io.smallrye.reactive.messaging.annotations.Stream; /** * @@ -51,20 +55,21 @@ */ public class SmallRyeReactiveMessagingProcessor { - private static final Logger LOGGER = Logger.getLogger("io.quarkus.scheduler.deployment.processor"); + private static final Logger LOGGER = Logger.getLogger("io.quarkus.smallrye-reactive-messaging.deployment.processor"); static final DotName NAME_INCOMING = DotName.createSimple(Incoming.class.getName()); static final DotName NAME_OUTGOING = DotName.createSimple(Outgoing.class.getName()); + static final DotName NAME_STREAM = DotName.createSimple(Stream.class.getName()); + static final DotName NAME_EMITTER = DotName.createSimple(Emitter.class.getName()); @BuildStep - List beans() { - List beans = new ArrayList<>(); - beans.add(new AdditionalBeanBuildItem(SmallRyeReactiveMessagingLifecycle.class)); - return beans; + AdditionalBeanBuildItem beans() { + return new AdditionalBeanBuildItem(SmallRyeReactiveMessagingLifecycle.class); } @BuildStep BeanDeploymentValidatorBuildItem beanDeploymentValidator(BuildProducer mediatorMethods, + BuildProducer emitters, BuildProducer feature) { feature.produce(new FeatureBuildItem(FeatureBuildItem.SMALLRYE_REACTIVE_MESSAGING)); @@ -80,10 +85,7 @@ public void validate(ValidationContext validationContext) { for (BeanInfo bean : validationContext.get(Key.BEANS)) { if (bean.isClassBean()) { // TODO: add support for inherited business methods - for (MethodInfo method : bean.getTarget() - .get() - .asClass() - .methods()) { + for (MethodInfo method : bean.getTarget().get().asClass().methods()) { if (annotationStore.hasAnnotation(method, NAME_INCOMING) || annotationStore.hasAnnotation(method, NAME_OUTGOING)) { // TODO: validate method params and return type? @@ -93,6 +95,19 @@ public void validate(ValidationContext validationContext) { } } } + + for (InjectionPointInfo injectionPoint : validationContext.get(Key.INJECTION_POINTS)) { + if (injectionPoint.getRequiredType().name().equals(NAME_EMITTER)) { + AnnotationInstance stream = injectionPoint.getRequiredQualifier(NAME_STREAM); + if (stream != null) { + // Stream.value() is mandatory + String name = stream.value().asString(); + LOGGER.debugf("Emitter injection point '%s' detected, stream name: '%s'", + injectionPoint.getTargetInfo(), name); + emitters.produce(new EmitterBuildItem(name)); + } + } + } } }); } @@ -107,6 +122,7 @@ public List removalExclusions() { @Record(STATIC_INIT) public void build(SmallRyeReactiveMessagingTemplate template, BeanContainerBuildItem beanContainer, List mediatorMethods, + List emitterFields, BuildProducer reflectiveClass) { /* * IMPLEMENTATION NOTE/FUTURE IMPROVEMENTS: It would be possible to replace the reflection completely and use Jandex and @@ -126,7 +142,8 @@ public void build(SmallRyeReactiveMessagingTemplate template, BeanContainerBuild .getIdentifier()); } } - template.registerMediators(beanClassToBeanId, beanContainer.getValue()); + template.registerMediators(beanClassToBeanId, beanContainer.getValue(), + emitterFields.stream().map(EmitterBuildItem::getName).collect(Collectors.toList())); } } diff --git a/extensions/smallrye-reactive-messaging/deployment/src/test/java/io/quarkus/smallrye/reactivemessaging/SimpleBean.java b/extensions/smallrye-reactive-messaging/deployment/src/test/java/io/quarkus/smallrye/reactivemessaging/SimpleBean.java index 05cd2f7635540..451aea7209709 100644 --- a/extensions/smallrye-reactive-messaging/deployment/src/test/java/io/quarkus/smallrye/reactivemessaging/SimpleBean.java +++ b/extensions/smallrye-reactive-messaging/deployment/src/test/java/io/quarkus/smallrye/reactivemessaging/SimpleBean.java @@ -3,7 +3,6 @@ import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; -import javax.annotation.PostConstruct; import javax.enterprise.context.ApplicationScoped; import org.eclipse.microprofile.reactive.messaging.Incoming; diff --git a/extensions/smallrye-reactive-messaging/deployment/src/test/java/io/quarkus/smallrye/reactivemessaging/SimpleTest.java b/extensions/smallrye-reactive-messaging/deployment/src/test/java/io/quarkus/smallrye/reactivemessaging/SimpleTest.java index 333b57ecd88f0..dc29b0a4ee653 100644 --- a/extensions/smallrye-reactive-messaging/deployment/src/test/java/io/quarkus/smallrye/reactivemessaging/SimpleTest.java +++ b/extensions/smallrye-reactive-messaging/deployment/src/test/java/io/quarkus/smallrye/reactivemessaging/SimpleTest.java @@ -19,11 +19,14 @@ public class SimpleTest { @RegisterExtension static final QuarkusUnitTest config = new QuarkusUnitTest() .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) - .addClasses(SimpleBean.class, StreamConsumer.class)); + .addClasses(SimpleBean.class, StreamConsumer.class, StreamEmitter.class)); @Inject StreamConsumer streamConsumer; + @Inject + StreamEmitter streamEmitter; + @Test public void testSimpleBean() { assertEquals(4, SimpleBean.RESULT.size()); @@ -43,4 +46,14 @@ public void testStreamInject() { assertEquals("reactive", consumed.get(3)); assertEquals("message", consumed.get(4)); } + + @Test + public void testStreamEmitter() { + streamEmitter.run(); + List list = streamEmitter.list(); + assertEquals(3, list.size()); + assertEquals("a", list.get(0)); + assertEquals("b", list.get(1)); + assertEquals("c", list.get(2)); + } } diff --git a/extensions/smallrye-reactive-messaging/deployment/src/test/java/io/quarkus/smallrye/reactivemessaging/StreamConsumer.java b/extensions/smallrye-reactive-messaging/deployment/src/test/java/io/quarkus/smallrye/reactivemessaging/StreamConsumer.java index 9b8c6c43114bf..e1bea304921be 100644 --- a/extensions/smallrye-reactive-messaging/deployment/src/test/java/io/quarkus/smallrye/reactivemessaging/StreamConsumer.java +++ b/extensions/smallrye-reactive-messaging/deployment/src/test/java/io/quarkus/smallrye/reactivemessaging/StreamConsumer.java @@ -15,7 +15,7 @@ public class StreamConsumer { @Inject @Stream("source") - private Flowable> sourceStream; + Flowable> sourceStream; public List consume() { return Flowable.fromPublisher(sourceStream) diff --git a/extensions/smallrye-reactive-messaging/deployment/src/test/java/io/quarkus/smallrye/reactivemessaging/StreamEmitter.java b/extensions/smallrye-reactive-messaging/deployment/src/test/java/io/quarkus/smallrye/reactivemessaging/StreamEmitter.java new file mode 100644 index 0000000000000..5a565ea375cf7 --- /dev/null +++ b/extensions/smallrye-reactive-messaging/deployment/src/test/java/io/quarkus/smallrye/reactivemessaging/StreamEmitter.java @@ -0,0 +1,36 @@ +package io.quarkus.smallrye.reactivemessaging; + +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; + +import org.eclipse.microprofile.reactive.messaging.Incoming; + +import io.smallrye.reactive.messaging.annotations.Emitter; +import io.smallrye.reactive.messaging.annotations.Stream; + +@ApplicationScoped +public class StreamEmitter { + + @Inject + @Stream("sink") + Emitter emitter; + + private List list = new CopyOnWriteArrayList<>(); + + public void run() { + emitter.send("a").send("b").send("c").complete(); + } + + @Incoming("sink") + public void consume(String s) { + list.add(s); + } + + public List list() { + return list; + } + +} diff --git a/extensions/smallrye-reactive-messaging/runtime/pom.xml b/extensions/smallrye-reactive-messaging/runtime/pom.xml index 690fa3eacc105..9188774e21904 100644 --- a/extensions/smallrye-reactive-messaging/runtime/pom.xml +++ b/extensions/smallrye-reactive-messaging/runtime/pom.xml @@ -10,17 +10,17 @@ 4.0.0 - quarkus-smallrye-reactive-messaging-runtime + quarkus-smallrye-reactive-messaging Quarkus - SmallRye Reactive Messaging - Runtime io.quarkus - quarkus-arc-runtime + quarkus-arc io.quarkus - quarkus-smallrye-reactive-streams-operators-runtime + quarkus-smallrye-reactive-streams-operators io.smallrye.reactive @@ -37,7 +37,8 @@ - maven-dependency-plugin + io.quarkus + quarkus-bootstrap-maven-plugin maven-compiler-plugin diff --git a/extensions/smallrye-reactive-messaging/runtime/src/main/java/io/quarkus/smallrye/reactivemessaging/runtime/SmallRyeReactiveMessagingLifecycle.java b/extensions/smallrye-reactive-messaging/runtime/src/main/java/io/quarkus/smallrye/reactivemessaging/runtime/SmallRyeReactiveMessagingLifecycle.java index 95081bb3a968a..6d4c5624b4d22 100644 --- a/extensions/smallrye-reactive-messaging/runtime/src/main/java/io/quarkus/smallrye/reactivemessaging/runtime/SmallRyeReactiveMessagingLifecycle.java +++ b/extensions/smallrye-reactive-messaging/runtime/src/main/java/io/quarkus/smallrye/reactivemessaging/runtime/SmallRyeReactiveMessagingLifecycle.java @@ -1,8 +1,5 @@ package io.quarkus.smallrye.reactivemessaging.runtime; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; - import javax.enterprise.context.Dependent; import javax.enterprise.event.Observes; import javax.inject.Inject; @@ -17,12 +14,9 @@ public class SmallRyeReactiveMessagingLifecycle { MediatorManager mediatorManager; void onApplicationStart(@Observes StartupEvent event) { - CompletableFuture future = mediatorManager.initializeAndRun(); try { - future.get(); - } catch (ExecutionException e) { - throw new RuntimeException(e.getCause()); - } catch (InterruptedException e) { + mediatorManager.initializeAndRun(); + } catch (Exception e) { throw new RuntimeException(e); } } diff --git a/extensions/smallrye-reactive-messaging/runtime/src/main/java/io/quarkus/smallrye/reactivemessaging/runtime/SmallRyeReactiveMessagingTemplate.java b/extensions/smallrye-reactive-messaging/runtime/src/main/java/io/quarkus/smallrye/reactivemessaging/runtime/SmallRyeReactiveMessagingTemplate.java index b1c14a8e115b4..da07fb712945b 100644 --- a/extensions/smallrye-reactive-messaging/runtime/src/main/java/io/quarkus/smallrye/reactivemessaging/runtime/SmallRyeReactiveMessagingTemplate.java +++ b/extensions/smallrye-reactive-messaging/runtime/src/main/java/io/quarkus/smallrye/reactivemessaging/runtime/SmallRyeReactiveMessagingTemplate.java @@ -1,5 +1,6 @@ package io.quarkus.smallrye.reactivemessaging.runtime; +import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -15,9 +16,10 @@ @Template public class SmallRyeReactiveMessagingTemplate { - public void registerMediators(Map beanClassToBeanId, BeanContainer container) { + public void registerMediators(Map beanClassToBeanId, BeanContainer container, List emitters) { // Extract the configuration and register mediators MediatorManager mediatorManager = container.instance(MediatorManager.class); + mediatorManager.initializeEmitters(emitters); for (Entry entry : beanClassToBeanId.entrySet()) { try { Class beanClass = Thread.currentThread() diff --git a/extensions/smallrye-rest-client/deployment/pom.xml b/extensions/smallrye-rest-client/deployment/pom.xml index afb330917e2fe..fcd85451133d5 100644 --- a/extensions/smallrye-rest-client/deployment/pom.xml +++ b/extensions/smallrye-rest-client/deployment/pom.xml @@ -26,25 +26,25 @@ 4.0.0 - quarkus-smallrye-rest-client + quarkus-smallrye-rest-client-deployment Quarkus - SmallRye REST client - Deployment io.quarkus - quarkus-core + quarkus-core-deployment io.quarkus - quarkus-arc + quarkus-arc-deployment io.quarkus - quarkus-resteasy-common + quarkus-resteasy-common-deployment io.quarkus - quarkus-smallrye-rest-client-runtime + quarkus-smallrye-rest-client diff --git a/extensions/smallrye-rest-client/deployment/src/main/java/io/quarkus/smallrye/restclient/deployment/SmallRyeRestClientProcessor.java b/extensions/smallrye-rest-client/deployment/src/main/java/io/quarkus/smallrye/restclient/deployment/SmallRyeRestClientProcessor.java index 2554af862ef43..7eee1cf12d9bb 100644 --- a/extensions/smallrye-rest-client/deployment/src/main/java/io/quarkus/smallrye/restclient/deployment/SmallRyeRestClientProcessor.java +++ b/extensions/smallrye-rest-client/deployment/src/main/java/io/quarkus/smallrye/restclient/deployment/SmallRyeRestClientProcessor.java @@ -17,29 +17,36 @@ package io.quarkus.smallrye.restclient.deployment; import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; +import javax.ws.rs.Path; import javax.ws.rs.client.ClientRequestFilter; import javax.ws.rs.client.ClientResponseFilter; import javax.ws.rs.ext.Providers; import org.apache.commons.logging.impl.Jdk14Logger; import org.apache.commons.logging.impl.LogFactoryImpl; -import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; +import org.eclipse.microprofile.rest.client.annotation.RegisterProvider; +import org.eclipse.microprofile.rest.client.annotation.RegisterProviders; +import org.eclipse.microprofile.rest.client.ext.DefaultClientHeadersFactoryImpl; import org.eclipse.microprofile.rest.client.inject.RestClient; import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationTarget; 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.resteasy.client.jaxrs.ResteasyClientBuilder; import org.jboss.resteasy.client.jaxrs.internal.proxy.ProxyBuilderImpl; import org.jboss.resteasy.client.jaxrs.internal.proxy.ResteasyClientProxy; -import org.jboss.resteasy.core.ResteasyProviderFactoryImpl; +import org.jboss.resteasy.core.providerfactory.ResteasyProviderFactoryImpl; import org.jboss.resteasy.spi.ResteasyConfiguration; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; @@ -47,7 +54,6 @@ import io.quarkus.arc.processor.BeanConfigurator; import io.quarkus.arc.processor.BeanRegistrar; import io.quarkus.arc.processor.BuiltinScope; -import io.quarkus.arc.processor.ScopeInfo; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.annotations.ExecutionTime; @@ -58,13 +64,14 @@ import io.quarkus.deployment.builditem.SslNativeConfigBuildItem; import io.quarkus.deployment.builditem.substrate.ReflectiveClassBuildItem; import io.quarkus.deployment.builditem.substrate.ReflectiveHierarchyBuildItem; +import io.quarkus.deployment.builditem.substrate.ServiceProviderBuildItem; import io.quarkus.deployment.builditem.substrate.SubstrateProxyDefinitionBuildItem; import io.quarkus.deployment.builditem.substrate.SubstrateResourceBuildItem; -import io.quarkus.deployment.util.ServiceUtil; import io.quarkus.gizmo.MethodDescriptor; import io.quarkus.gizmo.ResultHandle; +import io.quarkus.resteasy.common.deployment.JaxrsProvidersToRegisterBuildItem; +import io.quarkus.smallrye.restclient.runtime.IncomingHeadersProvider; import io.quarkus.smallrye.restclient.runtime.RestClientBase; -import io.quarkus.smallrye.restclient.runtime.RestClientBuilderImpl; import io.quarkus.smallrye.restclient.runtime.SmallRyeRestClientTemplate; import io.smallrye.restclient.DefaultResponseExceptionMapper; import io.smallrye.restclient.RestClientProxy; @@ -73,27 +80,19 @@ class SmallRyeRestClientProcessor { private static final DotName REST_CLIENT = DotName.createSimple(RestClient.class.getName()); - private static final DotName REGISTER_REST_CLIENT = DotName.createSimple(RegisterRestClient.class.getName()); + private static final DotName PATH = DotName.createSimple(Path.class.getName()); + + private static final DotName REGISTER_PROVIDER = DotName.createSimple(RegisterProvider.class.getName()); + private static final DotName REGISTER_PROVIDERS = DotName.createSimple(RegisterProviders.class.getName()); private static final String PROVIDERS_SERVICE_FILE = "META-INF/services/" + Providers.class.getName(); @BuildStep - void setupProviders(BuildProducer reflectiveClass, - BuildProducer resources, - BuildProducer proxyDefinition) throws Exception { + void setupProviders(BuildProducer resources, + BuildProducer proxyDefinition) { proxyDefinition.produce(new SubstrateProxyDefinitionBuildItem("javax.ws.rs.ext.Providers")); - - // This abstract one is also accessed directly via reflection - reflectiveClass.produce(new ReflectiveClassBuildItem(true, true, - "org.jboss.resteasy.plugins.providers.jsonb.AbstractJsonBindingProvider")); - - // for now, register all the providers for reflection. This is not something we want to keep but not having it generates a pile of warnings. - // we will improve that later with the SmallRye REST client. resources.produce(new SubstrateResourceBuildItem(PROVIDERS_SERVICE_FILE)); - for (String provider : ServiceUtil.classNamesNamedIn(getClass().getClassLoader(), PROVIDERS_SERVICE_FILE)) { - reflectiveClass.produce(new ReflectiveClassBuildItem(true, true, provider)); - } } @BuildStep @@ -129,19 +128,23 @@ void setup(BuildProducer feature, } @BuildStep + @Record(ExecutionTime.STATIC_INIT) void processInterfaces(CombinedIndexBuildItem combinedIndexBuildItem, SslNativeConfigBuildItem sslNativeConfig, BuildProducer proxyDefinition, BuildProducer reflectiveClass, BuildProducer reflectiveHierarchy, BuildProducer beanRegistrars, - BuildProducer extensionSslNativeSupport) { + BuildProducer extensionSslNativeSupport, + BuildProducer serviceProvider, + SmallRyeRestClientTemplate smallRyeRestClientTemplate) { // According to the spec only rest client interfaces annotated with RegisterRestClient are registered as beans Map interfaces = new HashMap<>(); Set returnTypes = new HashSet<>(); - for (AnnotationInstance annotation : combinedIndexBuildItem.getIndex().getAnnotations(REGISTER_REST_CLIENT)) { + IndexView index = combinedIndexBuildItem.getIndex(); + for (AnnotationInstance annotation : index.getAnnotations(PATH)) { AnnotationTarget target = annotation.target(); ClassInfo theInfo; if (target.kind() == AnnotationTarget.Kind.CLASS) { @@ -151,9 +154,11 @@ void processInterfaces(CombinedIndexBuildItem combinedIndexBuildItem, } else { continue; } - if (!Modifier.isInterface(theInfo.flags())) { + + if (!isRestClientInterface(index, theInfo)) { continue; } + interfaces.put(theInfo.name(), theInfo); // Find Return types @@ -173,11 +178,20 @@ void processInterfaces(CombinedIndexBuildItem combinedIndexBuildItem, for (Map.Entry entry : interfaces.entrySet()) { String iName = entry.getKey().toString(); + // the SubstrateProxyDefinitions have to be separate because + // SmallRye creates a JDK proxy that delegates to a resteasy JDK proxy proxyDefinition.produce(new SubstrateProxyDefinitionBuildItem(iName, ResteasyClientProxy.class.getName())); proxyDefinition.produce(new SubstrateProxyDefinitionBuildItem(iName, RestClientProxy.class.getName())); reflectiveClass.produce(new ReflectiveClassBuildItem(true, false, iName)); } + // 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(io.smallrye.restclient.header.IncomingHeadersProvider.class.getName(), + IncomingHeadersProvider.class.getName())); + // Register Interface return types for reflection for (Type returnType : returnTypes) { reflectiveHierarchy.produce(new ReflectiveHierarchyBuildItem(returnType)); @@ -210,6 +224,39 @@ public void register(RegistrationContext registrationContext) { // Indicates that this extension would like the SSL support to be enabled extensionSslNativeSupport.produce(new ExtensionSslNativeSupportBuildItem(FeatureBuildItem.SMALLRYE_REST_CLIENT)); - RestClientBuilderImpl.SSL_ENABLED = sslNativeConfig.isEnabled(); + + smallRyeRestClientTemplate.setSslEnabled(sslNativeConfig.isEnabled()); + } + + @BuildStep + @Record(ExecutionTime.STATIC_INIT) + void registerProviders(BuildProducer reflectiveClass, + JaxrsProvidersToRegisterBuildItem jaxrsProvidersToRegisterBuildItem, + CombinedIndexBuildItem combinedIndexBuildItem, + SmallRyeRestClientTemplate smallRyeRestClientTemplate) { + smallRyeRestClientTemplate.initializeResteasyProviderFactory(jaxrsProvidersToRegisterBuildItem.useBuiltIn(), + jaxrsProvidersToRegisterBuildItem.getProviders()); + + // register the providers for reflection + for (String providerToRegister : jaxrsProvidersToRegisterBuildItem.getProviders()) { + reflectiveClass.produce(new ReflectiveClassBuildItem(false, false, providerToRegister)); + } + + // now we register all values of @RegisterProvider for constructor reflection + + IndexView index = combinedIndexBuildItem.getIndex(); + List allInstances = new ArrayList<>(index.getAnnotations(REGISTER_PROVIDER)); + for (AnnotationInstance annotation : index.getAnnotations(REGISTER_PROVIDERS)) { + allInstances.addAll(Arrays.asList(annotation.value().asNestedArray())); + } + for (AnnotationInstance annotationInstance : allInstances) { + reflectiveClass + .produce(new ReflectiveClassBuildItem(false, false, annotationInstance.value().asClass().toString())); + } + } + + private boolean isRestClientInterface(IndexView index, ClassInfo classInfo) { + return Modifier.isInterface(classInfo.flags()) + && index.getAllKnownImplementors(classInfo.name()).isEmpty(); } } diff --git a/extensions/smallrye-rest-client/runtime/pom.xml b/extensions/smallrye-rest-client/runtime/pom.xml index a893a4e0968ee..99476adf19fea 100644 --- a/extensions/smallrye-rest-client/runtime/pom.xml +++ b/extensions/smallrye-rest-client/runtime/pom.xml @@ -26,21 +26,21 @@ 4.0.0 - quarkus-smallrye-rest-client-runtime + quarkus-smallrye-rest-client Quarkus - SmallRye REST client - Runtime io.quarkus - quarkus-core-runtime + quarkus-core io.quarkus - quarkus-arc-runtime + quarkus-arc io.quarkus - quarkus-resteasy-common-runtime + quarkus-resteasy-common io.smallrye @@ -63,7 +63,8 @@ - maven-dependency-plugin + io.quarkus + quarkus-bootstrap-maven-plugin maven-compiler-plugin diff --git a/extensions/smallrye-rest-client/runtime/src/main/java/io/quarkus/smallrye/restclient/runtime/IncomingHeadersProvider.java b/extensions/smallrye-rest-client/runtime/src/main/java/io/quarkus/smallrye/restclient/runtime/IncomingHeadersProvider.java new file mode 100644 index 0000000000000..44e955ed4ad0f --- /dev/null +++ b/extensions/smallrye-rest-client/runtime/src/main/java/io/quarkus/smallrye/restclient/runtime/IncomingHeadersProvider.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2019 Red Hat, Inc. + * + * Red Hat 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.smallrye.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 io.smallrye.restclient.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/smallrye-rest-client/runtime/src/main/java/io/quarkus/smallrye/restclient/runtime/RestClientBase.java b/extensions/smallrye-rest-client/runtime/src/main/java/io/quarkus/smallrye/restclient/runtime/RestClientBase.java index c03b25d64ac1c..7c1a110f7ad5f 100644 --- a/extensions/smallrye-rest-client/runtime/src/main/java/io/quarkus/smallrye/restclient/runtime/RestClientBase.java +++ b/extensions/smallrye-rest-client/runtime/src/main/java/io/quarkus/smallrye/restclient/runtime/RestClientBase.java @@ -42,7 +42,7 @@ public Object create() { try { return builder.baseUrl(new URL(baseUrl)).build(proxyType); } catch (MalformedURLException e) { - throw new IllegalArgumentException("The value of URL was invalid " + baseUrl); + throw new IllegalArgumentException("The value of URL was invalid " + baseUrl, e); } catch (Exception e) { if ("com.oracle.svm.core.jdk.UnsupportedFeatureError".equals(e.getClass().getCanonicalName())) { throw new IllegalArgumentException(baseUrl diff --git a/extensions/smallrye-rest-client/runtime/src/main/java/io/quarkus/smallrye/restclient/runtime/RestClientBuilderImpl.java b/extensions/smallrye-rest-client/runtime/src/main/java/io/quarkus/smallrye/restclient/runtime/RestClientBuilderImpl.java index be5f5bdbc5010..8155def7b75ea 100644 --- a/extensions/smallrye-rest-client/runtime/src/main/java/io/quarkus/smallrye/restclient/runtime/RestClientBuilderImpl.java +++ b/extensions/smallrye-rest-client/runtime/src/main/java/io/quarkus/smallrye/restclient/runtime/RestClientBuilderImpl.java @@ -23,7 +23,14 @@ import java.net.URI; import java.net.URISyntaxException; import java.net.URL; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +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.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; @@ -48,15 +55,20 @@ import org.jboss.resteasy.client.jaxrs.ResteasyClient; import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder; import org.jboss.resteasy.client.jaxrs.engines.URLConnectionEngine; +import org.jboss.resteasy.client.jaxrs.internal.LocalResteasyProviderFactory; +import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.jboss.resteasy.spi.ResteasyUriBuilder; -import io.smallrye.restclient.*; +import io.smallrye.restclient.ConfigurationWrapper; +import io.smallrye.restclient.DefaultMediaTypeFilter; +import io.smallrye.restclient.DefaultResponseExceptionMapper; +import io.smallrye.restclient.ExceptionMapping; +import io.smallrye.restclient.MethodInjectionFilter; +import io.smallrye.restclient.RestClientListeners; +import io.smallrye.restclient.RestClientProxy; import io.smallrye.restclient.async.AsyncInvocationInterceptorHandler; import io.smallrye.restclient.header.ClientHeaderProviders; -/** - * Created by hbraun on 15.01.18. - */ public class RestClientBuilderImpl implements RestClientBuilder { private static final String RESTEASY_PROPERTY_PREFIX = "resteasy."; @@ -67,13 +79,21 @@ public class RestClientBuilderImpl implements RestClientBuilder { public static final MethodInjectionFilter METHOD_INJECTION_FILTER = new MethodInjectionFilter(); public static final ClientHeadersRequestFilter HEADERS_REQUEST_FILTER = new ClientHeadersRequestFilter(); - public static boolean SSL_ENABLED = false; + static boolean SSL_ENABLED = false; + static ResteasyProviderFactory PROVIDER_FACTORY; RestClientBuilderImpl() { ClientBuilder availableBuilder = ClientBuilder.newBuilder(); if (availableBuilder instanceof ResteasyClientBuilder) { builderDelegate = (ResteasyClientBuilder) availableBuilder; + + ResteasyProviderFactory localProviderFactory = new LocalResteasyProviderFactory(PROVIDER_FACTORY); + if (ResteasyProviderFactory.peekInstance() != null) { + localProviderFactory.initializeClientProviders(ResteasyProviderFactory.getInstance()); + } + builderDelegate.providerFactory(localProviderFactory); + configurationWrapper = new ConfigurationWrapper(builderDelegate.getConfiguration()); config = ConfigProvider.getConfig(); } else { diff --git a/extensions/smallrye-rest-client/runtime/src/main/java/io/quarkus/smallrye/restclient/runtime/SmallRyeRestClientTemplate.java b/extensions/smallrye-rest-client/runtime/src/main/java/io/quarkus/smallrye/restclient/runtime/SmallRyeRestClientTemplate.java index 3c6478e42aac2..0597b2fe58ae0 100644 --- a/extensions/smallrye-rest-client/runtime/src/main/java/io/quarkus/smallrye/restclient/runtime/SmallRyeRestClientTemplate.java +++ b/extensions/smallrye-rest-client/runtime/src/main/java/io/quarkus/smallrye/restclient/runtime/SmallRyeRestClientTemplate.java @@ -16,16 +16,56 @@ package io.quarkus.smallrye.restclient.runtime; +import java.util.Set; + +import javax.ws.rs.RuntimeType; + import org.eclipse.microprofile.rest.client.spi.RestClientBuilderResolver; +import org.jboss.resteasy.core.providerfactory.ClientHelper; +import org.jboss.resteasy.core.providerfactory.NOOPServerHelper; +import org.jboss.resteasy.core.providerfactory.ResteasyProviderFactoryImpl; +import org.jboss.resteasy.plugins.providers.RegisterBuiltin; +import org.jboss.resteasy.spi.ResteasyProviderFactory; import io.quarkus.runtime.annotations.Template; -/** - * @author Ken Finnigan - */ @Template public class SmallRyeRestClientTemplate { public void setRestClientBuilderResolver() { RestClientBuilderResolver.setInstance(new BuilderResolver()); } + + public void setSslEnabled(boolean sslEnabled) { + RestClientBuilderImpl.SSL_ENABLED = sslEnabled; + } + + public void initializeResteasyProviderFactory(boolean useBuiltIn, Set providersToRegister) { + ResteasyProviderFactory clientProviderFactory = new ResteasyProviderFactoryImpl(null, true) { + @Override + public RuntimeType getRuntimeType() { + return RuntimeType.CLIENT; + } + + @Override + protected void initializeUtils() { + clientHelper = new ClientHelper(this); + serverHelper = NOOPServerHelper.INSTANCE; + } + }; + + if (useBuiltIn) { + RegisterBuiltin.register(clientProviderFactory); + } + + for (String providerToRegister : providersToRegister) { + try { + clientProviderFactory + .registerProvider(Thread.currentThread().getContextClassLoader().loadClass(providerToRegister.trim())); + } catch (ClassNotFoundException e) { + throw new RuntimeException("Unable to find class for provider " + providerToRegister, e); + } + } + + RestClientBuilderImpl.PROVIDER_FACTORY = clientProviderFactory; + } } diff --git a/extensions/smallrye-rest-client/runtime/src/main/resources/META-INF/services/io.smallrye.restclient.header.IncomingHeadersProvider b/extensions/smallrye-rest-client/runtime/src/main/resources/META-INF/services/io.smallrye.restclient.header.IncomingHeadersProvider new file mode 100644 index 0000000000000..7b5ca561cf929 --- /dev/null +++ b/extensions/smallrye-rest-client/runtime/src/main/resources/META-INF/services/io.smallrye.restclient.header.IncomingHeadersProvider @@ -0,0 +1 @@ +io.quarkus.smallrye.restclient.runtime.IncomingHeadersProvider \ No newline at end of file diff --git a/extensions/spring-di/deployment/pom.xml b/extensions/spring-di/deployment/pom.xml index 992e0f7000139..20272ee330106 100644 --- a/extensions/spring-di/deployment/pom.xml +++ b/extensions/spring-di/deployment/pom.xml @@ -26,17 +26,21 @@ 4.0.0 - quarkus-spring-di + quarkus-spring-di-deployment Quarkus - Spring - DI - Deployment + + ${project.groupId} + quarkus-spring-di + io.quarkus - quarkus-core + quarkus-core-deployment io.quarkus - quarkus-arc + quarkus-arc-deployment 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 493dbc8fb6c96..1aa9c9ed62c17 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 @@ -43,7 +43,7 @@ import io.quarkus.arc.deployment.AdditionalStereotypeBuildItem; import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem; -import io.quarkus.arc.deployment.FullArchiveIndexBuildItem; +import io.quarkus.arc.deployment.BeanArchiveIndexBuildItem; import io.quarkus.arc.processor.BuiltinScope; import io.quarkus.arc.processor.DotNames; import io.quarkus.arc.processor.Transformation; @@ -105,9 +105,9 @@ FeatureBuildItem registerFeature() { @BuildStep AnnotationsTransformerBuildItem beanTransformer( - final FullArchiveIndexBuildItem fullArchiveIndexBuildItem, + final BeanArchiveIndexBuildItem beanArchiveIndexBuildItem, final BuildProducer additionalStereotypeBuildItemBuildProducer) { - final IndexView index = fullArchiveIndexBuildItem.getIndex(); + final IndexView index = beanArchiveIndexBuildItem.getIndex(); final Map> scopes = getStereotypeScopes(index); final Map> instances = new HashMap<>(); for (final DotName name : scopes.keySet()) { @@ -265,6 +265,14 @@ Set getAnnotationsToAdd( final Set scopeStereotypes = new HashSet<>(); final Set names = new HashSet<>(); final Set clazzAnnotations = classInfo.annotations().keySet(); + + for (AnnotationInstance instance : classInfo.classAnnotations()) { + // make sure that we don't mix and match Spring and CDI annotations since this can cause a lot of problems + if (BuiltinScope.from(instance.name()) != null) { + return annotationsToAdd; + } + } + for (final DotName clazzAnnotation : clazzAnnotations) { if (stereotypes.contains(clazzAnnotation)) { scopeStereotypes.add(clazzAnnotation); @@ -293,7 +301,7 @@ Set getAnnotationsToAdd( declaredScope, target, Collections.emptyList())); - } else if (!(isAnnotation && scopes.isEmpty())) { // Annotations without an explicit scope shouldn't default to anything + } else if (!(isAnnotation && scopes.isEmpty()) && !classInfo.annotations().containsKey(CONFIGURATION_ANNOTATION)) { // Annotations without an explicit scope shouldn't default to anything final DotName scope = validateScope(classInfo, scopes, scopeStereotypes); annotationsToAdd.add(create( scope, 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 ea23757d1320b..d961389d970c9 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 @@ -48,7 +48,7 @@ import org.springframework.stereotype.Component; import org.springframework.stereotype.Service; -import io.quarkus.arc.processor.BeanProcessor; +import io.quarkus.arc.processor.BeanArchives; import io.quarkus.deployment.util.IoUtil; /** @@ -171,7 +171,7 @@ private IndexView getIndex(final Class... classes) { throw new IllegalStateException("Failed to index: " + className, e); } } - return BeanProcessor.addBuiltinClasses(indexer.complete()); + return BeanArchives.buildBeanArchiveIndex(indexer.complete()); } @SafeVarargs diff --git a/extensions/spring-di/pom.xml b/extensions/spring-di/pom.xml index 47914d25d8cce..c6e31733373a8 100644 --- a/extensions/spring-di/pom.xml +++ b/extensions/spring-di/pom.xml @@ -31,5 +31,6 @@ pom deployment + runtime diff --git a/extensions/spring-di/runtime/pom.xml b/extensions/spring-di/runtime/pom.xml new file mode 100644 index 0000000000000..86bbe436a291b --- /dev/null +++ b/extensions/spring-di/runtime/pom.xml @@ -0,0 +1,40 @@ + + + + + quarkus-spring-di-parent + io.quarkus + 999-SNAPSHOT + ../ + + + 4.0.0 + + quarkus-spring-di + Quarkus - Spring - DI - Runtime + + + + + io.quarkus + quarkus-bootstrap-maven-plugin + + + + diff --git a/extensions/swagger-ui/deployment/pom.xml b/extensions/swagger-ui/deployment/pom.xml new file mode 100644 index 0000000000000..338c591d437cf --- /dev/null +++ b/extensions/swagger-ui/deployment/pom.xml @@ -0,0 +1,70 @@ + + + + quarkus-swagger-ui-parent + io.quarkus + 999-SNAPSHOT + ../ + + 4.0.0 + + quarkus-swagger-ui-deployment + Quarkus - Swagger UI - Deployment + + + + io.quarkus + quarkus-core-deployment + + + io.quarkus + quarkus-undertow-deployment + + + io.quarkus + quarkus-arc-deployment + + + io.quarkus + quarkus-smallrye-openapi-common-deployment + + + io.quarkus + quarkus-swagger-ui + + + org.webjars + swagger-ui + + + io.quarkus + quarkus-junit5-internal + test + + + io.rest-assured + rest-assured + test + + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + + 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 new file mode 100644 index 0000000000000..70e62ca3ecc2f --- /dev/null +++ b/extensions/swagger-ui/deployment/src/main/java/io/quarkus/swaggerui/deployment/SwaggerUiProcessor.java @@ -0,0 +1,156 @@ +package io.quarkus.swaggerui.deployment; + +import static java.lang.String.format; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Enumeration; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.inject.Inject; + +import org.jboss.logging.Logger; + +import io.quarkus.arc.deployment.BeanContainerBuildItem; +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.LaunchModeBuildItem; +import io.quarkus.deployment.index.ClassPathArtifactResolver; +import io.quarkus.deployment.index.ResolvedArtifact; +import io.quarkus.deployment.util.FileUtil; +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigRoot; +import io.quarkus.smallrye.openapi.common.deployment.SmallRyeOpenApiConfig; +import io.quarkus.swaggerui.runtime.SwaggerUiTemplate; +import io.quarkus.undertow.deployment.ServletExtensionBuildItem; + +public class SwaggerUiProcessor { + + private static final Logger log = Logger.getLogger(SwaggerUiProcessor.class.getName()); + + private static final String SWAGGER_UI_WEBJAR_GROUP_ID = "org.webjars"; + private static final String SWAGGER_UI_WEBJAR_ARTIFACT_ID = "swagger-ui"; + private static final String SWAGGER_UI_WEBJAR_PREFIX = "META-INF/resources/webjars/swagger-ui"; + private static final Pattern SWAGGER_UI_DEFAULT_API_URL_PATTERN = Pattern.compile("(.* url: \")(.*)(\",.*)", + Pattern.DOTALL); + private static final String TEMP_DIR_PREFIX = "quarkus-swagger-ui_" + System.nanoTime(); + + /** + * The configuration for Swagger UI. + */ + SwaggerUiConfig swaggerUiConfig; + + SmallRyeOpenApiConfig openapi; + + private static String cachedOpenAPIPath; + private static String cachedDirectory; + + @Inject + private LaunchModeBuildItem launch; + + @BuildStep + void feature(BuildProducer feature) { + if (launch.getLaunchMode().isDevOrTest()) { + feature.produce(new FeatureBuildItem(FeatureBuildItem.SWAGGER_UI)); + } + } + + @BuildStep + @Record(ExecutionTime.STATIC_INIT) + public void registerSwaggerUiServletExtension(SwaggerUiTemplate template, + BuildProducer servletExtension, + BeanContainerBuildItem container) { + if (launch.getLaunchMode().isDevOrTest()) { + if (cachedDirectory == null) { + Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { + @Override + public void run() { + try { + FileUtil.deleteDirectory(Paths.get(cachedDirectory)); + } catch (IOException e) { + log.error("Failed to clean Swagger UI temp directory on shutdown", e); + } + } + }, "Swagger UI Shutdown Hook")); + } + if (cachedDirectory == null || !cachedOpenAPIPath.equals(openapi.path)) { + if (cachedDirectory != null) { + try { + FileUtil.deleteDirectory(Paths.get(cachedDirectory)); + } catch (IOException e) { + log.error("Failed to clean Swagger UI temp directory on shutdown", e); + } + cachedDirectory = null; + cachedOpenAPIPath = null; + } + } + try { + ResolvedArtifact artifact = getSwaggerUiArtifact(); + Path tempDir = Files.createTempDirectory(TEMP_DIR_PREFIX); + extractSwaggerUi(artifact, tempDir); + updateApiUrl(tempDir.resolve("index.html")); + cachedDirectory = tempDir.toAbsolutePath().toString(); + cachedOpenAPIPath = openapi.path; + } catch (IOException e) { + throw new RuntimeException(e); + } + servletExtension.produce( + new ServletExtensionBuildItem( + template.createSwaggerUiExtension( + swaggerUiConfig.path, + cachedDirectory, + container.getValue()))); + } + } + + private ResolvedArtifact getSwaggerUiArtifact() { + ClassPathArtifactResolver resolver = new ClassPathArtifactResolver(SwaggerUiProcessor.class.getClassLoader()); + return resolver.getArtifact(SWAGGER_UI_WEBJAR_GROUP_ID, SWAGGER_UI_WEBJAR_ARTIFACT_ID, null); + } + + private void extractSwaggerUi(ResolvedArtifact artifact, Path resourceDir) throws IOException { + File artifactFile = artifact.getArtifactPath().toFile(); + try (JarFile jarFile = new JarFile(artifactFile)) { + Enumeration entries = jarFile.entries(); + String versionedSwaggerUiWebjarPrefix = format("%s/%s/", SWAGGER_UI_WEBJAR_PREFIX, artifact.getVersion()); + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + if (entry.getName().startsWith(versionedSwaggerUiWebjarPrefix) && !entry.isDirectory()) { + InputStream inputStream = jarFile.getInputStream(entry); + String filename = entry.getName().replace(versionedSwaggerUiWebjarPrefix, ""); + Files.copy(inputStream, resourceDir.resolve(filename)); + } + } + } + } + + private void updateApiUrl(Path indexHtml) throws IOException { + String content = new String(Files.readAllBytes(indexHtml), "UTF-8"); + Matcher uriMatcher = SWAGGER_UI_DEFAULT_API_URL_PATTERN.matcher(content); + if (uriMatcher.matches()) { + content = uriMatcher.replaceFirst("$1" + openapi.path + "$3"); + Files.write(indexHtml, content.getBytes("UTF-8")); + } else { + log.warn("Unable to replace the default URL of Swagger UI"); + } + } + + @ConfigRoot + static final class SwaggerUiConfig { + /** + * The path of the swagger-ui servlet. + */ + @ConfigItem(defaultValue = "/swagger-ui") + String path; + } +} diff --git a/extensions/swagger-ui/deployment/src/test/java/io/quarkus/swaggerui/deployment/CustomConfigTest.java b/extensions/swagger-ui/deployment/src/test/java/io/quarkus/swaggerui/deployment/CustomConfigTest.java new file mode 100644 index 0000000000000..8022146407b8a --- /dev/null +++ b/extensions/swagger-ui/deployment/src/test/java/io/quarkus/swaggerui/deployment/CustomConfigTest.java @@ -0,0 +1,25 @@ +package io.quarkus.swaggerui.deployment; + +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.QuarkusUnitTest; +import io.restassured.RestAssured; + +public class CustomConfigTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addAsResource("application-custom.properties", "application.properties")); + + @Test + public void shouldUseCustomConfig() { + RestAssured.when().get("/custom").then().statusCode(200).body(containsString("/openapi")); + RestAssured.when().get("/custom/index.html").then().statusCode(200).body(containsString("/openapi")); + } +} diff --git a/extensions/swagger-ui/deployment/src/test/java/io/quarkus/swaggerui/deployment/NoConfigTest.java b/extensions/swagger-ui/deployment/src/test/java/io/quarkus/swaggerui/deployment/NoConfigTest.java new file mode 100644 index 0000000000000..ec30617ed5aa0 --- /dev/null +++ b/extensions/swagger-ui/deployment/src/test/java/io/quarkus/swaggerui/deployment/NoConfigTest.java @@ -0,0 +1,25 @@ +package io.quarkus.swaggerui.deployment; + +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.QuarkusUnitTest; +import io.restassured.RestAssured; + +public class NoConfigTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)); + + @Test + public void shouldUseDefaultConfig() { + RestAssured.when().get("/swagger-ui").then().statusCode(200).body(containsString("/openapi")); + RestAssured.when().get("/swagger-ui/index.html").then().statusCode(200).body(containsString("/openapi")); + + } +} diff --git a/extensions/swagger-ui/deployment/src/test/resources/application-custom.properties b/extensions/swagger-ui/deployment/src/test/resources/application-custom.properties new file mode 100644 index 0000000000000..32954fa3496cf --- /dev/null +++ b/extensions/swagger-ui/deployment/src/test/resources/application-custom.properties @@ -0,0 +1 @@ +quarkus.swagger-ui.path=/custom \ No newline at end of file diff --git a/extensions/swagger-ui/pom.xml b/extensions/swagger-ui/pom.xml new file mode 100644 index 0000000000000..90abfcf33b60c --- /dev/null +++ b/extensions/swagger-ui/pom.xml @@ -0,0 +1,22 @@ + + + + quarkus-build-parent + io.quarkus + 999-SNAPSHOT + ../../build-parent/pom.xml + + 4.0.0 + + quarkus-swagger-ui-parent + Quarkus - Swagger UI + pom + + + deployment + runtime + + + diff --git a/extensions/swagger-ui/runtime/pom.xml b/extensions/swagger-ui/runtime/pom.xml new file mode 100644 index 0000000000000..ec91eb055bb7d --- /dev/null +++ b/extensions/swagger-ui/runtime/pom.xml @@ -0,0 +1,52 @@ + + + + quarkus-swagger-ui-parent + io.quarkus + 999-SNAPSHOT + ../ + + 4.0.0 + + quarkus-swagger-ui + Quarkus - Swagger UI - Runtime + + + + io.quarkus + quarkus-core + + + io.quarkus + quarkus-undertow + + + io.quarkus + quarkus-arc + + + + + + + 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/swagger-ui/runtime/src/main/java/io/quarkus/swaggerui/runtime/SwaggerUiServletExtension.java b/extensions/swagger-ui/runtime/src/main/java/io/quarkus/swaggerui/runtime/SwaggerUiServletExtension.java new file mode 100644 index 0000000000000..7b3fd8cf0ac95 --- /dev/null +++ b/extensions/swagger-ui/runtime/src/main/java/io/quarkus/swaggerui/runtime/SwaggerUiServletExtension.java @@ -0,0 +1,54 @@ +package io.quarkus.swaggerui.runtime; + +import static io.undertow.Handlers.resource; + +import java.io.File; + +import javax.servlet.ServletContext; + +import org.jboss.logging.Logger; + +import io.undertow.Handlers; +import io.undertow.server.handlers.resource.FileResourceManager; +import io.undertow.server.handlers.resource.ResourceManager; +import io.undertow.servlet.ServletExtension; +import io.undertow.servlet.api.DeploymentInfo; + +public class SwaggerUiServletExtension implements ServletExtension { + + private static final Logger log = Logger.getLogger(SwaggerUiServletExtension.class.getName()); + + private String path; + private String resourceDir; + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public String getResourceDir() { + return resourceDir; + } + + public void setResourceDir(String resourceDir) { + this.resourceDir = resourceDir; + } + + /** + * This registers the SwaggerUiServletExtension + * + * @param deploymentInfo - the deployment to augment + * @param servletContext - the ServletContext for the deployment + */ + @Override + public void handleDeployment(DeploymentInfo deploymentInfo, ServletContext servletContext) { + ResourceManager resourceManager = new FileResourceManager(new File(resourceDir)); + deploymentInfo.addOuterHandlerChainWrapper( + (handler) -> Handlers.path(handler).addPrefixPath(path, resource(resourceManager))); + log.info("Swagger UI available at " + path); + } + +} diff --git a/extensions/swagger-ui/runtime/src/main/java/io/quarkus/swaggerui/runtime/SwaggerUiTemplate.java b/extensions/swagger-ui/runtime/src/main/java/io/quarkus/swaggerui/runtime/SwaggerUiTemplate.java new file mode 100644 index 0000000000000..a7fcc5ca53ed5 --- /dev/null +++ b/extensions/swagger-ui/runtime/src/main/java/io/quarkus/swaggerui/runtime/SwaggerUiTemplate.java @@ -0,0 +1,16 @@ +package io.quarkus.swaggerui.runtime; + +import io.quarkus.arc.runtime.BeanContainer; +import io.quarkus.runtime.annotations.Template; +import io.undertow.servlet.ServletExtension; + +@Template +public class SwaggerUiTemplate { + + public ServletExtension createSwaggerUiExtension(String path, String resourceDir, BeanContainer container) { + SwaggerUiServletExtension extension = container.instance(SwaggerUiServletExtension.class); + extension.setPath(path); + extension.setResourceDir(resourceDir); + return extension; + } +} diff --git a/extensions/undertow-websockets/deployment/pom.xml b/extensions/undertow-websockets/deployment/pom.xml index 5d07985164d57..2bd4c65fcbfb7 100644 --- a/extensions/undertow-websockets/deployment/pom.xml +++ b/extensions/undertow-websockets/deployment/pom.xml @@ -26,21 +26,21 @@ 4.0.0 - quarkus-undertow-websockets + quarkus-undertow-websockets-deployment Quarkus - Undertow - WebSockets - Deployment io.quarkus - quarkus-core + quarkus-core-deployment io.quarkus - quarkus-undertow + quarkus-undertow-deployment io.quarkus - quarkus-undertow-websockets-runtime + quarkus-undertow-websockets diff --git a/extensions/undertow-websockets/deployment/src/main/java/io/quarkus/undertow/websockets/deployment/AnnotatedWebsocketEndpointBuildItem.java b/extensions/undertow-websockets/deployment/src/main/java/io/quarkus/undertow/websockets/deployment/AnnotatedWebsocketEndpointBuildItem.java new file mode 100644 index 0000000000000..e351893a0f947 --- /dev/null +++ b/extensions/undertow-websockets/deployment/src/main/java/io/quarkus/undertow/websockets/deployment/AnnotatedWebsocketEndpointBuildItem.java @@ -0,0 +1,25 @@ +package io.quarkus.undertow.websockets.deployment; + +import io.quarkus.builder.item.MultiBuildItem; + +/** + * A server websocket endpoint + */ +public final class AnnotatedWebsocketEndpointBuildItem extends MultiBuildItem { + + final String className; + final boolean client; + + public AnnotatedWebsocketEndpointBuildItem(String className, boolean client) { + this.className = className; + this.client = client; + } + + public String getClassName() { + return className; + } + + public boolean isClient() { + return client; + } +} diff --git a/extensions/undertow-websockets/deployment/src/main/java/io/quarkus/undertow/websockets/deployment/HotReplacementWebsocketEndpoint.java b/extensions/undertow-websockets/deployment/src/main/java/io/quarkus/undertow/websockets/deployment/HotReplacementWebsocketEndpoint.java new file mode 100644 index 0000000000000..abd557205e21a --- /dev/null +++ b/extensions/undertow-websockets/deployment/src/main/java/io/quarkus/undertow/websockets/deployment/HotReplacementWebsocketEndpoint.java @@ -0,0 +1,208 @@ +package io.quarkus.undertow.websockets.deployment; + +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.BlockingDeque; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.TimeUnit; +import java.util.zip.InflaterInputStream; + +import javax.websocket.HandshakeResponse; +import javax.websocket.OnClose; +import javax.websocket.OnError; +import javax.websocket.OnMessage; +import javax.websocket.OnOpen; +import javax.websocket.Session; +import javax.websocket.server.HandshakeRequest; +import javax.websocket.server.ServerEndpoint; +import javax.websocket.server.ServerEndpointConfig; + +import org.eclipse.microprofile.config.ConfigProvider; +import org.jboss.logging.Logger; +import org.xnio.IoUtils; + +import io.quarkus.deployment.devmode.HotReplacementContext; + +@ServerEndpoint(value = HotReplacementWebsocketEndpoint.QUARKUS_HOT_RELOAD, configurator = HotReplacementWebsocketEndpoint.ServerConfigurator.class) +public class HotReplacementWebsocketEndpoint { + + static final String QUARKUS_HOT_RELOAD = "/quarkus/live-reload"; + static final String QUARKUS_SECURITY_KEY = "quarkus-security-key"; + static final String QUARKUS_HOT_RELOAD_PASSWORD = "quarkus.live-reload.password"; + private static Logger logger = Logger.getLogger(HotReplacementWebsocketEndpoint.class); + + private static final long MAX_WAIT_TIME = 15000; + + private static final int CLASS_CHANGE_RESPONSE = 2; + private static final int CLASS_CHANGE_REQUEST = 1; + + private static final Object lock = new Object(); + /** + * The current connection, managed under lock + *

+ * There will only ever be one connection at a time + */ + private static ConnectionContext connection; + private ConnectionContext currentConnection; + + @OnClose + void close() { + synchronized (lock) { + if (connection == currentConnection) { + connection = null; + } + } + this.currentConnection.messages.add(new Message()); //unblock a waiting thread + } + + @OnError + public void error(Throwable t) { + logger.error("Error in hot replacement websocket connection", t); + currentConnection.messages.add(new Message()); //unblock a waiting thread + IoUtils.safeClose(currentConnection.connection); + } + + @OnOpen + public void onConnect(Session session) { + Session old = null; + synchronized (lock) { + if (connection != null) { + old = HotReplacementWebsocketEndpoint.connection.connection; + //add an empty message to unblock a waiting request + HotReplacementWebsocketEndpoint.connection.messages.add(new Message()); + } + currentConnection = new ConnectionContext(session); + connection = currentConnection; + } + //only one open connection at a time + IoUtils.safeClose(old); + } + + public static void checkForChanges(HotReplacementContext hrc) { + final ConnectionContext con; + synchronized (lock) { + con = connection; + } + if (con == null) { + //we return if there is no connection + return; + } + try { + con.connection.getBasicRemote().sendBinary(ByteBuffer.wrap(new byte[] { CLASS_CHANGE_REQUEST })); + } catch (IOException e) { + try { + con.connection.close(); + } catch (IOException ignored) { + + } + //add an empty message so the request can continue + con.messages.add(new Message()); + } + try { + Message m = con.messages.poll(MAX_WAIT_TIME, TimeUnit.MILLISECONDS); + if (m == null) { + logger.error("Timed out processing hot replacement"); + } else { + if (!m.srcFiles.isEmpty() || + !m.resources.isEmpty()) { + if (hrc.getSourcesDir() != null) { + for (Map.Entry i : m.srcFiles.entrySet()) { + Path path = hrc.getSourcesDir().resolve(i.getKey()); + Files.createDirectories(path.getParent()); + try (FileOutputStream out = new FileOutputStream( + path.toFile())) { + out.write(i.getValue()); + } + } + } + //TODO: fixme + List resourcesDir = hrc.getResourcesDir(); + if (resourcesDir != null && !resourcesDir.isEmpty()) { + for (Map.Entry i : m.resources.entrySet()) { + Path path = resourcesDir.get(0).resolve(i.getKey()); + Files.createDirectories(path.getParent()); + try (FileOutputStream out = new FileOutputStream( + path.toFile())) { + out.write(i.getValue()); + } + } + } + } + } + } catch (Exception e) { + logger.error("Failed to process hot deployment", e); + } + } + + @OnMessage + public void handleResponseMessage(byte[] message) throws IOException { + byte first = message[0]; + if (first == CLASS_CHANGE_RESPONSE) { + Message m = new Message(); + //a response message + try (DataInputStream in = new DataInputStream( + new InflaterInputStream(new ByteArrayInputStream(message, 1, message.length - 1)))) { + Map srcFiles = m.srcFiles; + Map resources = m.resources; + String key; + byte[] rd; + int count = in.readInt(); + for (int i = 0; i < count; ++i) { + key = in.readUTF(); + int byteLength = in.readInt(); + rd = new byte[byteLength]; + in.readFully(rd); + srcFiles.put(key, rd); + } + count = in.readInt(); + for (int i = 0; i < count; ++i) { + key = in.readUTF(); + int byteLength = in.readInt(); + rd = new byte[byteLength]; + in.readFully(rd); + resources.put(key, rd); + } + currentConnection.messages.add(m); + } + } + } + + static final class Message { + + Map srcFiles = new HashMap<>(); + Map resources = new HashMap<>(); + } + + private static final class ConnectionContext { + + final Session connection; + final BlockingDeque messages = new LinkedBlockingDeque<>(); + + private ConnectionContext(Session connection) { + this.connection = connection; + } + } + + public static final class ServerConfigurator extends ServerEndpointConfig.Configurator { + @Override + public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) { + List headers = request.getHeaders().get(QUARKUS_SECURITY_KEY); + if (headers == null || headers.isEmpty()) { + throw new RuntimeException("No security key present"); + } + if (!headers.get(0).equals(WebsocketHotReloadSetup.replacementPassword)) { + throw new RuntimeException("Security key did not match"); + } + + super.modifyHandshake(sec, request, response); + } + } +} 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 83a1fa2322d40..7a63fedbb21e7 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 @@ -19,6 +19,7 @@ import java.lang.reflect.Modifier; import java.util.Collection; import java.util.HashSet; +import java.util.List; import java.util.Set; import javax.websocket.ClientEndpoint; @@ -40,6 +41,7 @@ import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.deployment.builditem.ServiceStartBuildItem; import io.quarkus.deployment.builditem.substrate.ReflectiveClassBuildItem; +import io.quarkus.runtime.annotations.ConfigRoot; import io.quarkus.undertow.deployment.ServletContextAttributeBuildItem; import io.quarkus.undertow.deployment.UndertowBuildItem; import io.quarkus.undertow.websockets.runtime.UndertowWebsocketTemplate; @@ -54,16 +56,8 @@ public class UndertowWebsocketProcessor { private static final DotName ENDPOINT = DotName.createSimple(Endpoint.class.getName()); @BuildStep - @Record(ExecutionTime.STATIC_INIT) - public ServletContextAttributeBuildItem deploy(final CombinedIndexBuildItem indexBuildItem, - UndertowWebsocketTemplate template, - BuildProducer reflection, BuildProducer feature) throws Exception { - - feature.produce(new FeatureBuildItem(FeatureBuildItem.UNDERTOW_WEBSOCKETS)); - - final Set annotatedEndpoints = new HashSet<>(); - final Set endpoints = new HashSet<>(); - final Set config = new HashSet<>(); + void scanForAnnotatedEndpoints(CombinedIndexBuildItem indexBuildItem, + BuildProducer annotatedProducer) { final IndexView index = indexBuildItem.getIndex(); @@ -72,7 +66,7 @@ public ServletContextAttributeBuildItem deploy(final CombinedIndexBuildItem inde if (endpoint.target() instanceof ClassInfo) { ClassInfo clazz = (ClassInfo) endpoint.target(); if (!Modifier.isAbstract(clazz.flags())) { - annotatedEndpoints.add(clazz.name().toString()); + annotatedProducer.produce(new AnnotatedWebsocketEndpointBuildItem(clazz.name().toString(), false)); } } } @@ -82,11 +76,26 @@ public ServletContextAttributeBuildItem deploy(final CombinedIndexBuildItem inde if (endpoint.target() instanceof ClassInfo) { ClassInfo clazz = (ClassInfo) endpoint.target(); if (!Modifier.isAbstract(clazz.flags())) { - annotatedEndpoints.add(clazz.name().toString()); + annotatedProducer.produce(new AnnotatedWebsocketEndpointBuildItem(clazz.name().toString(), true)); } } } + } + + @BuildStep + @Record(ExecutionTime.STATIC_INIT) + public ServletContextAttributeBuildItem deploy(final CombinedIndexBuildItem indexBuildItem, + UndertowWebsocketTemplate template, + BuildProducer reflection, BuildProducer feature, + List annotatedEndpoints) throws Exception { + + feature.produce(new FeatureBuildItem(FeatureBuildItem.UNDERTOW_WEBSOCKETS)); + + final Set endpoints = new HashSet<>(); + final Set config = new HashSet<>(); + + final IndexView index = indexBuildItem.getIndex(); final Collection subclasses = index.getAllKnownImplementors(SERVER_APPLICATION_CONFIG); for (final ClassInfo clazz : subclasses) { @@ -107,12 +116,16 @@ public ServletContextAttributeBuildItem deploy(final CombinedIndexBuildItem inde config.isEmpty()) { return null; } + Set annotated = new HashSet<>(); + for (AnnotatedWebsocketEndpointBuildItem i : annotatedEndpoints) { + annotated.add(i.className); + } reflection.produce( - new ReflectiveClassBuildItem(true, false, annotatedEndpoints.toArray(new String[annotatedEndpoints.size()]))); + new ReflectiveClassBuildItem(true, false, annotated.toArray(new String[annotated.size()]))); reflection.produce(new ReflectiveClassBuildItem(false, false, JsrWebSocketFilter.class.getName())); return new ServletContextAttributeBuildItem(WebSocketDeploymentInfo.ATTRIBUTE_NAME, - template.createDeploymentInfo(annotatedEndpoints, endpoints, config)); + template.createDeploymentInfo(annotated, endpoints, config)); } @BuildStep @@ -128,4 +141,18 @@ void beanDefiningAnnotations(BuildProducer anno annotations.produce(new BeanDefiningAnnotationBuildItem(ENDPOINT)); annotations.produce(new BeanDefiningAnnotationBuildItem(CLIENT_ENDPOINT)); } + + @ConfigRoot + static class HotReloadConfig { + + /** + * The security key for remote hot deployment + */ + String password; + + /** + * The remote URL to connect to + */ + String url; + } } 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 new file mode 100644 index 0000000000000..71926d3b77fc8 --- /dev/null +++ b/extensions/undertow-websockets/deployment/src/main/java/io/quarkus/undertow/websockets/deployment/WebsocketHotReloadSetup.java @@ -0,0 +1,98 @@ +package io.quarkus.undertow.websockets.deployment; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +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; +import io.quarkus.deployment.devmode.HotReplacementSetup; +import io.quarkus.undertow.runtime.UndertowDeploymentTemplate; +import io.quarkus.undertow.websockets.runtime.WorkerSupplier; +import io.undertow.Handlers; +import io.undertow.predicate.Predicates; +import io.undertow.server.HandlerWrapper; +import io.undertow.server.HttpHandler; +import io.undertow.servlet.Servlets; +import io.undertow.servlet.api.DeploymentInfo; +import io.undertow.servlet.api.DeploymentManager; +import io.undertow.servlet.api.ServletContainer; +import io.undertow.websockets.jsr.WebSocketDeploymentInfo; + +public class WebsocketHotReloadSetup implements HotReplacementSetup { + + private static Logger logger = Logger.getLogger(WebsocketHotReloadSetup.class); + + static volatile String replacementPassword; + + @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); + } + } + } + } + if (replacementPassword != null) { + logger.info("Using websocket based hot deployment"); + } else { + return; + } + try { + hotReplacementContext.addPreScanStep(new Runnable() { + @Override + public void run() { + HotReplacementWebsocketEndpoint.checkForChanges(hotReplacementContext); + } + }); + //we need a special websocket setup + //this will likely change + //but we create a servlet deployment that lasts for the life of the server + WebSocketDeploymentInfo info = new WebSocketDeploymentInfo(); + info.setWorker(new WorkerSupplier()); + info.addEndpoint(HotReplacementWebsocketEndpoint.class); + + DeploymentInfo d = new DeploymentInfo(); + d.setDeploymentName("hot-replacement-websockets"); + d.setContextPath("/"); + d.setClassLoader(getClass().getClassLoader()); + d.addServletContextAttribute(WebSocketDeploymentInfo.ATTRIBUTE_NAME, info); + ServletContainer servletContainer = Servlets.defaultContainer(); + DeploymentManager manager = servletContainer.addDeployment(d); + manager.deploy(); + HttpHandler ws = manager.start(); + UndertowDeploymentTemplate.addHotDeploymentWrapper(new HandlerWrapper() { + @Override + public HttpHandler wrap(HttpHandler handler) { + return Handlers.predicate(Predicates.path(HotReplacementWebsocketEndpoint.QUARKUS_HOT_RELOAD), ws, handler); + } + }); + + } catch (Exception e) { + throw new RuntimeException(e); + } + + } +} diff --git a/extensions/undertow-websockets/deployment/src/main/resources/META-INF/services/io.quarkus.deployment.devmode.HotReplacementSetup b/extensions/undertow-websockets/deployment/src/main/resources/META-INF/services/io.quarkus.deployment.devmode.HotReplacementSetup new file mode 100644 index 0000000000000..dc95ee2834109 --- /dev/null +++ b/extensions/undertow-websockets/deployment/src/main/resources/META-INF/services/io.quarkus.deployment.devmode.HotReplacementSetup @@ -0,0 +1 @@ +io.quarkus.undertow.websockets.deployment.WebsocketHotReloadSetup \ No newline at end of file diff --git a/extensions/undertow-websockets/runtime/pom.xml b/extensions/undertow-websockets/runtime/pom.xml index 155b7085e728a..f9f1ff9537ccd 100644 --- a/extensions/undertow-websockets/runtime/pom.xml +++ b/extensions/undertow-websockets/runtime/pom.xml @@ -26,7 +26,7 @@ 4.0.0 - quarkus-undertow-websockets-runtime + quarkus-undertow-websockets Quarkus - Undertow - WebSockets - Runtime @@ -36,11 +36,11 @@ io.quarkus - quarkus-core-runtime + quarkus-core io.quarkus - quarkus-undertow-runtime + quarkus-undertow io.undertow @@ -51,7 +51,8 @@ - maven-dependency-plugin + io.quarkus + quarkus-bootstrap-maven-plugin maven-compiler-plugin diff --git a/extensions/undertow/deployment/pom.xml b/extensions/undertow/deployment/pom.xml index 3b471e645d26f..39020530ee4fc 100644 --- a/extensions/undertow/deployment/pom.xml +++ b/extensions/undertow/deployment/pom.xml @@ -25,7 +25,7 @@ 4.0.0 - quarkus-undertow + quarkus-undertow-deployment Quarkus - Undertow - Deployment @@ -35,15 +35,19 @@ io.quarkus - quarkus-core + quarkus-core-deployment io.quarkus - quarkus-arc + quarkus-arc-deployment io.quarkus - quarkus-undertow-runtime + quarkus-undertow + + + io.quarkus + quarkus-kubernetes-spi org.jboss.metadata diff --git a/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/FilterBuildItem.java b/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/FilterBuildItem.java index d3afe7e922337..0c4cb8e2cb8df 100644 --- a/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/FilterBuildItem.java +++ b/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/FilterBuildItem.java @@ -25,8 +25,7 @@ import javax.servlet.DispatcherType; import javax.servlet.Filter; -import org.jboss.builder.item.MultiBuildItem; - +import io.quarkus.builder.item.MultiBuildItem; import io.undertow.servlet.api.InstanceFactory; public final class FilterBuildItem extends MultiBuildItem { @@ -114,7 +113,8 @@ public DispatcherType getDispatcher() { } public enum MappingType { - URL, SERVLET; + URL, + SERVLET; } } diff --git a/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/HttpHandlerWrapperBuildItem.java b/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/HttpHandlerWrapperBuildItem.java index 6a11715767424..970fadae1a67c 100644 --- a/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/HttpHandlerWrapperBuildItem.java +++ b/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/HttpHandlerWrapperBuildItem.java @@ -16,8 +16,7 @@ package io.quarkus.undertow.deployment; -import org.jboss.builder.item.MultiBuildItem; - +import io.quarkus.builder.item.MultiBuildItem; import io.undertow.server.HandlerWrapper; public final class HttpHandlerWrapperBuildItem extends MultiBuildItem { diff --git a/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/ListenerBuildItem.java b/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/ListenerBuildItem.java index 03d56ea54d623..10f6f3c1fd665 100644 --- a/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/ListenerBuildItem.java +++ b/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/ListenerBuildItem.java @@ -16,7 +16,7 @@ package io.quarkus.undertow.deployment; -import org.jboss.builder.item.MultiBuildItem; +import io.quarkus.builder.item.MultiBuildItem; public final class ListenerBuildItem extends MultiBuildItem { diff --git a/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/ServletBuildItem.java b/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/ServletBuildItem.java index 6aa599c123a2e..3a433e9766eee 100644 --- a/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/ServletBuildItem.java +++ b/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/ServletBuildItem.java @@ -24,8 +24,7 @@ import javax.servlet.Servlet; -import org.jboss.builder.item.MultiBuildItem; - +import io.quarkus.builder.item.MultiBuildItem; import io.undertow.servlet.api.InstanceFactory; public final class ServletBuildItem extends MultiBuildItem { diff --git a/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/ServletContextAttributeBuildItem.java b/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/ServletContextAttributeBuildItem.java index 4e7651c118c1b..14a47c9a828b7 100644 --- a/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/ServletContextAttributeBuildItem.java +++ b/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/ServletContextAttributeBuildItem.java @@ -16,7 +16,7 @@ package io.quarkus.undertow.deployment; -import org.jboss.builder.item.MultiBuildItem; +import io.quarkus.builder.item.MultiBuildItem; public final class ServletContextAttributeBuildItem extends MultiBuildItem { diff --git a/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/ServletDeploymentManagerBuildItem.java b/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/ServletDeploymentManagerBuildItem.java index bca3c73235c89..74121221df299 100644 --- a/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/ServletDeploymentManagerBuildItem.java +++ b/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/ServletDeploymentManagerBuildItem.java @@ -16,8 +16,7 @@ package io.quarkus.undertow.deployment; -import org.jboss.builder.item.SimpleBuildItem; - +import io.quarkus.builder.item.SimpleBuildItem; import io.undertow.servlet.api.DeploymentManager; public final class ServletDeploymentManagerBuildItem extends SimpleBuildItem { diff --git a/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/ServletExtensionBuildItem.java b/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/ServletExtensionBuildItem.java index 638f824c89ad4..ddb866f4ea14f 100644 --- a/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/ServletExtensionBuildItem.java +++ b/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/ServletExtensionBuildItem.java @@ -16,8 +16,7 @@ package io.quarkus.undertow.deployment; -import org.jboss.builder.item.MultiBuildItem; - +import io.quarkus.builder.item.MultiBuildItem; import io.undertow.servlet.ServletExtension; public final class ServletExtensionBuildItem extends MultiBuildItem { diff --git a/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/ServletInitParamBuildItem.java b/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/ServletInitParamBuildItem.java index 57a1d498b2d28..23d59058a4e7b 100644 --- a/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/ServletInitParamBuildItem.java +++ b/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/ServletInitParamBuildItem.java @@ -16,7 +16,7 @@ package io.quarkus.undertow.deployment; -import org.jboss.builder.item.MultiBuildItem; +import io.quarkus.builder.item.MultiBuildItem; public final class ServletInitParamBuildItem extends MultiBuildItem { diff --git a/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/UndertowBuildItem.java b/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/UndertowBuildItem.java index 90c94ccc6c36f..2df29decd845e 100644 --- a/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/UndertowBuildItem.java +++ b/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/UndertowBuildItem.java @@ -16,8 +16,7 @@ package io.quarkus.undertow.deployment; -import org.jboss.builder.item.SimpleBuildItem; - +import io.quarkus.builder.item.SimpleBuildItem; import io.quarkus.runtime.RuntimeValue; import io.undertow.Undertow; 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 5389c87a378c0..c5d9685b981b2 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 @@ -23,17 +23,20 @@ import static javax.servlet.DispatcherType.REQUEST; import java.io.FileInputStream; -import java.io.IOException; +import java.net.JarURLConnection; +import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; +import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Consumer; +import java.util.jar.JarEntry; import java.util.stream.Collectors; import javax.annotation.security.DeclareRoles; @@ -98,19 +101,20 @@ import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.builditem.ApplicationArchivesBuildItem; -import io.quarkus.deployment.builditem.ArchiveRootBuildItem; import io.quarkus.deployment.builditem.CombinedIndexBuildItem; +import io.quarkus.deployment.builditem.ExecutorBuildItem; import io.quarkus.deployment.builditem.HotDeploymentConfigFileBuildItem; import io.quarkus.deployment.builditem.LaunchModeBuildItem; import io.quarkus.deployment.builditem.ObjectSubstitutionBuildItem; import io.quarkus.deployment.builditem.ServiceStartBuildItem; import io.quarkus.deployment.builditem.ShutdownContextBuildItem; import io.quarkus.deployment.builditem.substrate.ReflectiveClassBuildItem; +import io.quarkus.deployment.builditem.substrate.RuntimeReinitializedClassBuildItem; import io.quarkus.deployment.builditem.substrate.SubstrateConfigBuildItem; import io.quarkus.deployment.builditem.substrate.SubstrateResourceBuildItem; import io.quarkus.deployment.recording.RecorderContext; +import io.quarkus.kubernetes.spi.KubernetesPortBuildItem; import io.quarkus.runtime.RuntimeValue; -import io.quarkus.runtime.annotations.ConfigItem; import io.quarkus.undertow.runtime.HttpConfig; import io.quarkus.undertow.runtime.HttpSessionContext; import io.quarkus.undertow.runtime.ServletProducer; @@ -135,16 +139,11 @@ public class UndertowBuildStep { public static final DotName MULTIPART_CONFIG = DotName.createSimple(MultipartConfig.class.getName()); public static final DotName SERVLET_SECURITY = DotName.createSimple(ServletSecurity.class.getName()); public static final String WEB_XML = "META-INF/web.xml"; + protected static final String META_INF_RESOURCES = "META-INF/resources"; @Inject CombinedIndexBuildItem combinedIndexBuildItem; - /** - * Configuration which applies to the HTTP server. - */ - @ConfigItem(name = "http") - HttpConfig config; - @BuildStep @Record(RUNTIME_INIT) public ServiceStartBuildItem boot(UndertowDeploymentTemplate template, @@ -152,8 +151,11 @@ public ServiceStartBuildItem boot(UndertowDeploymentTemplate template, List wrappers, ShutdownContextBuildItem shutdown, Consumer undertowProducer, - LaunchModeBuildItem launchMode) throws Exception { - RuntimeValue ut = template.startUndertow(shutdown, servletDeploymentManagerBuildItem.getDeploymentManager(), + LaunchModeBuildItem launchMode, + ExecutorBuildItem executorBuildItem, + HttpConfig config) throws Exception { + RuntimeValue ut = template.startUndertow(shutdown, executorBuildItem.getExecutorProxy(), + servletDeploymentManagerBuildItem.getDeploymentManager(), config, wrappers.stream().map(HttpHandlerWrapperBuildItem::getValue).collect(Collectors.toList()), launchMode.getLaunchMode()); undertowProducer.accept(new UndertowBuildItem(ut)); @@ -182,6 +184,12 @@ SubstrateConfigBuildItem config() { .build(); } + @BuildStep + void runtimeReinit(BuildProducer producer) { + producer.produce(new RuntimeReinitializedClassBuildItem("org.wildfly.common.net.HostName")); + producer.produce(new RuntimeReinitializedClassBuildItem("org.wildfly.common.os.Process")); + } + @BuildStep HotDeploymentConfigFileBuildItem configFile() { return new HotDeploymentConfigFileBuildItem(WEB_XML); @@ -219,13 +227,18 @@ WebMetadataBuildItem createWebMetadata(ApplicationArchivesBuildItem applicationA } } additionalBeanBuildItemConsumer - .accept(new AdditionalBeanBuildItem(false, additionalBeans.toArray(new String[additionalBeans.size()]))); + .accept(AdditionalBeanBuildItem.builder().setUnremovable().addBeanClasses(additionalBeans).build()); } else { result = new WebMetaData(); } return new WebMetadataBuildItem(result); } + @BuildStep + public void kubernetes(HttpConfig config, BuildProducer portProducer) { + portProducer.produce(new KubernetesPortBuildItem(config.port, "http")); + } + @Record(STATIC_INIT) @BuildStep() public ServletDeploymentManagerBuildItem build(ApplicationArchivesBuildItem applicationArchivesBuildItem, @@ -241,7 +254,8 @@ public ServletDeploymentManagerBuildItem build(ApplicationArchivesBuildItem appl BuildProducer substitutions, Consumer reflectiveClasses, LaunchModeBuildItem launchMode, - ShutdownContextBuildItem shutdownContext) throws Exception { + ShutdownContextBuildItem shutdownContext, + BuildProducer substrateResourceBuildItemBuildProducer) throws Exception { ObjectSubstitutionBuildItem.Holder holder = new ObjectSubstitutionBuildItem.Holder(ServletSecurityInfo.class, ServletSecurityInfoProxy.class, ServletSecurityInfoSubstitution.class); @@ -254,7 +268,7 @@ public ServletDeploymentManagerBuildItem build(ApplicationArchivesBuildItem appl Set knownFiles = new HashSet<>(); Set knownDirectories = new HashSet<>(); for (ApplicationArchive i : applicationArchivesBuildItem.getAllApplicationArchives()) { - Path resource = i.getChildPath("META-INF/resources"); + Path resource = i.getChildPath(META_INF_RESOURCES); if (resource != null && Files.exists(resource)) { Files.walk(resource).forEach(new Consumer() { @Override @@ -273,7 +287,30 @@ public void accept(Path path) { }); } } - + Enumeration resources = getClass().getClassLoader().getResources(META_INF_RESOURCES); + while (resources.hasMoreElements()) { + URL url = resources.nextElement(); + if (url.getProtocol().equals("jar")) { + JarURLConnection jar = (JarURLConnection) url.openConnection(); + Enumeration entries = jar.getJarFile().entries(); + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + if (entry.getName().startsWith(META_INF_RESOURCES)) { + String sub = entry.getName().substring(META_INF_RESOURCES.length() + 1); + if (!sub.isEmpty()) { + if (entry.getName().endsWith("/")) { + String dir = sub.substring(0, sub.length() - 1); + knownDirectories.add(dir); + } else { + knownFiles.add(sub); + } + } + } + } + } + } + substrateResourceBuildItemBuildProducer.produce(new SubstrateResourceBuildItem( + knownFiles.stream().map((s) -> META_INF_RESOURCES + "/" + s).collect(Collectors.toList()))); RuntimeValue deployment = template.createDeployment("test", knownFiles, knownDirectories, launchMode.getLaunchMode(), shutdownContext); @@ -437,24 +474,6 @@ public void accept(Path path) { } - @BuildStep - SubstrateResourceBuildItem registerSubstrateResources(ArchiveRootBuildItem root, - ApplicationArchivesBuildItem applicationArchivesBuildItem) throws IOException { - List res = new ArrayList<>(); - Path resources = applicationArchivesBuildItem.getRootArchive().getChildPath("META-INF/resources"); - if (resources != null) { - Files.walk(resources).forEach(new Consumer() { - @Override - public void accept(Path path) { - if (!Files.isDirectory(path)) { - res.add(root.getPath().relativize(path).toString()); - } - } - }); - } - return new SubstrateResourceBuildItem(res); - } - /** * Process a single index. * diff --git a/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/WebMetadataBuildItem.java b/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/WebMetadataBuildItem.java index 2b618ce64cd85..bdcfb66a9e842 100644 --- a/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/WebMetadataBuildItem.java +++ b/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/WebMetadataBuildItem.java @@ -1,8 +1,9 @@ package io.quarkus.undertow.deployment; -import org.jboss.builder.item.SimpleBuildItem; import org.jboss.metadata.web.spec.WebMetaData; +import io.quarkus.builder.item.SimpleBuildItem; + public final class WebMetadataBuildItem extends SimpleBuildItem { private final WebMetaData webMetaData; diff --git a/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/devmode/UndertowHotReplacementSetup.java b/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/devmode/UndertowHotReplacementSetup.java index 94add88cf0b3e..6567c65c3c4c3 100644 --- a/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/devmode/UndertowHotReplacementSetup.java +++ b/extensions/undertow/deployment/src/main/java/io/quarkus/undertow/deployment/devmode/UndertowHotReplacementSetup.java @@ -1,14 +1,22 @@ package io.quarkus.undertow.deployment.devmode; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + import io.quarkus.deployment.devmode.HotReplacementContext; import io.quarkus.deployment.devmode.HotReplacementSetup; +import io.quarkus.deployment.devmode.ReplacementDebugPage; import io.quarkus.undertow.runtime.UndertowDeploymentTemplate; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; +import io.undertow.util.Headers; public class UndertowHotReplacementSetup implements HotReplacementSetup { + protected static final String META_INF_SERVICES = "META-INF/resources"; private volatile long nextUpdate; private HotReplacementContext context; @@ -18,7 +26,15 @@ public class UndertowHotReplacementSetup implements HotReplacementSetup { public void setupHotDeployment(HotReplacementContext context) { this.context = context; HandlerWrapper wrapper = createHandlerWrapper(); - UndertowDeploymentTemplate.setHotDeployment(wrapper); + UndertowDeploymentTemplate.addHotDeploymentWrapper(wrapper); + List resources = new ArrayList<>(); + for (Path i : context.getResourcesDir()) { + Path resolved = i.resolve(META_INF_SERVICES); + if (Files.exists(resolved)) { + resources.add(resolved); + } + } + UndertowDeploymentTemplate.setHotDeploymentResources(resources); } private HandlerWrapper createHandlerWrapper() { @@ -43,7 +59,7 @@ void handleHotDeploymentRequest(HttpServerExchange exchange, HttpHandler next) t if (nextUpdate > System.currentTimeMillis()) { if (context.getDeploymentProblem() != null) { - ReplacementDebugPage.handleRequest(exchange, context.getDeploymentProblem()); + handleDeploymentProblem(exchange, context.getDeploymentProblem()); return; } next.handleRequest(exchange); @@ -52,15 +68,22 @@ void handleHotDeploymentRequest(HttpServerExchange exchange, HttpHandler next) t synchronized (this) { if (nextUpdate < System.currentTimeMillis()) { context.doScan(); - //we update at most once every 2s + // we update at most once every 2s nextUpdate = System.currentTimeMillis() + TWO_SECONDS; } } if (context.getDeploymentProblem() != null) { - ReplacementDebugPage.handleRequest(exchange, context.getDeploymentProblem()); + handleDeploymentProblem(exchange, context.getDeploymentProblem()); return; } next.handleRequest(exchange); } + + private void handleDeploymentProblem(HttpServerExchange exchange, final Throwable exception) { + String bodyText = ReplacementDebugPage.generateHtml(exception); + exchange.setStatusCode(500); + exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/html; charset=UTF-8"); + exchange.getResponseSender().send(bodyText); + } } diff --git a/extensions/undertow/deployment/src/test/java/io/quarkus/undertow/test/ServletEmptyWebXmlTestCase.java b/extensions/undertow/deployment/src/test/java/io/quarkus/undertow/test/ServletEmptyWebXmlTestCase.java new file mode 100644 index 0000000000000..27983e108caa1 --- /dev/null +++ b/extensions/undertow/deployment/src/test/java/io/quarkus/undertow/test/ServletEmptyWebXmlTestCase.java @@ -0,0 +1,52 @@ +/* + * Copyright 2018 Red Hat, Inc. + * + * Licensed 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.undertow.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.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +public class ServletEmptyWebXmlTestCase { + + static final String WEB_XML = "\n" + + "\n" + + "\n" + + ""; + + @RegisterExtension + static QuarkusUnitTest runner = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(WebXmlServlet.class) + .addAsManifestResource(new StringAsset(WEB_XML), "web.xml")); + + @Test + public void testWebXmlServlet() { + RestAssured.when().get("/").then() + .statusCode(403); + } + +} diff --git a/extensions/undertow/runtime/pom.xml b/extensions/undertow/runtime/pom.xml index b5da9cc8a6ce0..473635333d98b 100644 --- a/extensions/undertow/runtime/pom.xml +++ b/extensions/undertow/runtime/pom.xml @@ -25,13 +25,13 @@ 4.0.0 - quarkus-undertow-runtime + quarkus-undertow Quarkus - Undertow - Runtime io.quarkus - quarkus-arc-runtime + quarkus-arc com.oracle.substratevm @@ -51,14 +51,15 @@ io.quarkus - quarkus-core-runtime + quarkus-core - maven-dependency-plugin + io.quarkus + quarkus-bootstrap-maven-plugin maven-compiler-plugin diff --git a/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/DelegatingResourceManager.java b/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/DelegatingResourceManager.java new file mode 100644 index 0000000000000..1e5d762933035 --- /dev/null +++ b/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/DelegatingResourceManager.java @@ -0,0 +1,51 @@ +package io.quarkus.undertow.runtime; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +import io.undertow.server.handlers.resource.Resource; +import io.undertow.server.handlers.resource.ResourceChangeListener; +import io.undertow.server.handlers.resource.ResourceManager; + +public class DelegatingResourceManager implements ResourceManager { + + private final List delegates; + + public DelegatingResourceManager(ResourceManager... delegates) { + this.delegates = Arrays.asList(delegates); + } + + @Override + public Resource getResource(String path) throws IOException { + for (ResourceManager i : delegates) { + Resource res = i.getResource(path); + if (res != null) { + return res; + } + } + return null; + } + + @Override + public boolean isResourceChangeListenerSupported() { + return false; + } + + @Override + public void registerResourceChangeListener(ResourceChangeListener listener) { + + } + + @Override + public void removeResourceChangeListener(ResourceChangeListener listener) { + + } + + @Override + public void close() throws IOException { + for (ResourceManager i : delegates) { + i.close(); + } + } +} diff --git a/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/HttpConfig.java b/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/HttpConfig.java index 6ed57728b93f0..76bf4c08a8e14 100644 --- a/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/HttpConfig.java +++ b/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/HttpConfig.java @@ -24,6 +24,9 @@ import io.quarkus.runtime.annotations.ConfigRoot; import io.quarkus.runtime.configuration.ssl.ServerSslConfig; +/** + * Configuration which applies to the HTTP server. + */ @ConfigRoot(phase = ConfigPhase.RUN_TIME) public class HttpConfig { @@ -56,13 +59,6 @@ public class HttpConfig { @ConfigItem(defaultValue = "0.0.0.0") public String host; - /** - * The number of worker threads used for blocking tasks, this will be automatically set to a reasonable value - * based on the number of CPU core if it is not provided - */ - @ConfigItem - public OptionalInt workerThreads; - /** * The number if IO threads used to perform IO. This will be automatically set to a reasonable value based on * the number of CPU cores if it is not provided diff --git a/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/UndertowDeploymentTemplate.java b/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/UndertowDeploymentTemplate.java index d19952e0d81b0..1e687c4da6ba8 100644 --- a/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/UndertowDeploymentTemplate.java +++ b/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/UndertowDeploymentTemplate.java @@ -19,12 +19,17 @@ import java.io.IOException; import java.net.InetSocketAddress; import java.net.SocketAddress; +import java.nio.file.Path; import java.nio.file.Paths; import java.security.SecureRandom; +import java.util.ArrayList; import java.util.EventListener; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; import java.util.stream.Collectors; import javax.net.ssl.SSLContext; @@ -37,6 +42,8 @@ import org.jboss.logging.Logger; import org.wildfly.common.net.Inet; +import org.xnio.Xnio; +import org.xnio.XnioWorker; import io.quarkus.arc.ManagedContext; import io.quarkus.arc.runtime.BeanContainer; @@ -54,6 +61,7 @@ import io.undertow.server.handlers.resource.CachingResourceManager; import io.undertow.server.handlers.resource.ClassPathResourceManager; import io.undertow.server.handlers.resource.PathResourceManager; +import io.undertow.server.handlers.resource.Resource; import io.undertow.server.handlers.resource.ResourceManager; import io.undertow.server.session.SessionIdGenerator; import io.undertow.servlet.ServletExtension; @@ -88,12 +96,16 @@ public void handleRequest(HttpServerExchange exchange) throws Exception { currentRoot.handleRequest(exchange); } }; - private static final String RESOURCES_PROP = "quarkus.undertow.resources"; private static volatile Undertow undertow; - private static volatile HandlerWrapper hotDeploymentWrapper; + private static final List hotDeploymentWrappers = new CopyOnWriteArrayList<>(); + private static volatile List hotDeploymentResourcePaths; private static volatile HttpHandler currentRoot = ResponseCodeHandler.HANDLE_404; + public static void setHotDeploymentResources(List resources) { + hotDeploymentResourcePaths = resources; + } + public RuntimeValue createDeployment(String name, Set knownFile, Set knownDirectories, LaunchMode launchMode, ShutdownContext context) { DeploymentInfo d = new DeploymentInfo(); @@ -109,14 +121,19 @@ public RuntimeValue createDeployment(String name, Set kn } d.setClassLoader(cl); //TODO: we need better handling of static resources - String resourcesDir = System.getProperty(RESOURCES_PROP); ResourceManager resourceManager; - if (resourcesDir == null) { + if (hotDeploymentResourcePaths == null) { resourceManager = new KnownPathResourceManager(knownFile, knownDirectories, new ClassPathResourceManager(d.getClassLoader(), "META-INF/resources")); } else { - resourceManager = new PathResourceManager(Paths.get(resourcesDir)); + List managers = new ArrayList<>(); + for (Path i : hotDeploymentResourcePaths) { + managers.add(new PathResourceManager(i)); + } + managers.add(new ClassPathResourceManager(d.getClassLoader(), "META-INF/resources")); + resourceManager = new DelegatingResourceManager(managers.toArray(new ResourceManager[0])); } + if (launchMode == LaunchMode.NORMAL) { //todo: cache configuration resourceManager = new CachingResourceManager(1000, 0, null, resourceManager, 2000); @@ -253,19 +270,22 @@ public void addServltInitParameter(RuntimeValue info, String nam info.getValue().addInitParameter(name, value); } - public RuntimeValue startUndertow(ShutdownContext shutdown, DeploymentManager manager, HttpConfig config, + public RuntimeValue startUndertow(ShutdownContext shutdown, ExecutorService executorService, + DeploymentManager manager, HttpConfig config, List wrappers, LaunchMode launchMode) throws Exception { if (undertow == null) { SSLContext context = config.ssl.toSSLContext(); - doServerStart(config, launchMode, context); + doServerStart(config, launchMode, context, executorService); if (launchMode != LaunchMode.DEVELOPMENT) { //in development mode undertow should not be shut down shutdown.addShutdownTask(new Runnable() { @Override public void run() { + XnioWorker worker = undertow.getWorker(); undertow.stop(); + worker.shutdown(); undertow = null; } }); @@ -303,8 +323,8 @@ public void run() { return new RuntimeValue<>(undertow); } - public static void setHotDeployment(HandlerWrapper handlerWrapper) { - hotDeploymentWrapper = handlerWrapper; + public static void addHotDeploymentWrapper(HandlerWrapper handlerWrapper) { + hotDeploymentWrappers.add(handlerWrapper); } /** @@ -314,31 +334,33 @@ public static void setHotDeployment(HandlerWrapper handlerWrapper) { * be no chance to use hot deployment to fix the error. In development mode we start Undertow early, so any error * on boot can be corrected via the hot deployment handler */ - private static void doServerStart(HttpConfig config, LaunchMode launchMode, SSLContext sslContext) + private static void doServerStart(HttpConfig config, LaunchMode launchMode, SSLContext sslContext, ExecutorService executor) throws ServletException { if (undertow == null) { int port = config.determinePort(launchMode); int sslPort = config.determineSslPort(launchMode); log.debugf("Starting Undertow on port %d", port); HttpHandler rootHandler = new CanonicalPathHandler(ROOT_HANDLER); - if (hotDeploymentWrapper != null) { - rootHandler = hotDeploymentWrapper.wrap(rootHandler); + for (HandlerWrapper i : hotDeploymentWrappers) { + rootHandler = i.wrap(rootHandler); } + XnioWorker.Builder workerBuilder = Xnio.getInstance().createWorkerBuilder() + .setExternalExecutorService(executor); + Undertow.Builder builder = Undertow.builder() .addHttpListener(port, config.host) .setHandler(rootHandler); if (config.ioThreads.isPresent()) { - builder.setIoThreads(config.ioThreads.getAsInt()); + workerBuilder.setWorkerIoThreads(config.ioThreads.getAsInt()); } else if (launchMode.isDevOrTest()) { //we limit the number of IO and worker threads in development and testing mode - builder.setIoThreads(2); - } - if (config.workerThreads.isPresent()) { - builder.setWorkerThreads(config.workerThreads.getAsInt()); - } else if (launchMode.isDevOrTest()) { - builder.setWorkerThreads(6); + workerBuilder.setWorkerIoThreads(2); + } else { + workerBuilder.setWorkerIoThreads(Runtime.getRuntime().availableProcessors() * 2); } + XnioWorker worker = workerBuilder.build(); + builder.setWorker(worker); if (sslContext != null) { log.debugf("Starting Undertow HTTPS listener on port %d", sslPort); builder.addHttpsListener(sslPort, config.host, sslContext); diff --git a/extensions/vertx-web/deployment/pom.xml b/extensions/vertx-web/deployment/pom.xml new file mode 100644 index 0000000000000..1e13046ca2738 --- /dev/null +++ b/extensions/vertx-web/deployment/pom.xml @@ -0,0 +1,69 @@ + + + + + + quarkus-vertx-web-parent + io.quarkus + 999-SNAPSHOT + ../ + + 4.0.0 + + quarkus-vertx-web-deployment + Quarkus - Vert.x Web - Deployment + + + + io.quarkus + quarkus-vertx-deployment + + + io.quarkus + quarkus-vertx-web + + + + io.quarkus + quarkus-junit5-internal + + + io.rest-assured + rest-assured + + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + + diff --git a/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/RouteHandlerBuildItem.java b/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/RouteHandlerBuildItem.java new file mode 100644 index 0000000000000..9db8873d8ff79 --- /dev/null +++ b/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/RouteHandlerBuildItem.java @@ -0,0 +1,35 @@ +package io.quarkus.vertx.web.deployment; + +import java.util.List; + +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.MethodInfo; + +import io.quarkus.arc.processor.BeanInfo; +import io.quarkus.builder.item.MultiBuildItem; + +public final class RouteHandlerBuildItem extends MultiBuildItem { + + private final BeanInfo bean; + private final List routes; + private final MethodInfo method; + + public RouteHandlerBuildItem(BeanInfo bean, MethodInfo method, List routes) { + this.bean = bean; + this.method = method; + this.routes = routes; + } + + public BeanInfo getBean() { + return bean; + } + + public MethodInfo getMethod() { + return method; + } + + public List getRoutes() { + return routes; + } + +} 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 new file mode 100644 index 0000000000000..62735974f292e --- /dev/null +++ b/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/VertxWebProcessor.java @@ -0,0 +1,267 @@ +package io.quarkus.vertx.web.deployment; + +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import javax.inject.Singleton; + +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.DotName; +import org.jboss.jandex.MethodInfo; +import org.jboss.jandex.Type; +import org.jboss.logging.Logger; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.ArcContainer; +import io.quarkus.arc.InjectableBean; +import io.quarkus.arc.InstanceHandle; +import io.quarkus.arc.deployment.AdditionalBeanBuildItem; +import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem; +import io.quarkus.arc.deployment.BeanContainerBuildItem; +import io.quarkus.arc.deployment.BeanDeploymentValidatorBuildItem; +import io.quarkus.arc.deployment.UnremovableBeanBuildItem; +import io.quarkus.arc.deployment.UnremovableBeanBuildItem.BeanClassAnnotationExclusion; +import io.quarkus.arc.processor.AnnotationStore; +import io.quarkus.arc.processor.AnnotationsTransformer; +import io.quarkus.arc.processor.BeanDeploymentValidator; +import io.quarkus.arc.processor.BeanInfo; +import io.quarkus.arc.processor.BuiltinScope; +import io.quarkus.arc.processor.DotNames; +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.AnnotationProxyBuildItem; +import io.quarkus.deployment.builditem.FeatureBuildItem; +import io.quarkus.deployment.builditem.GeneratedClassBuildItem; +import io.quarkus.deployment.builditem.LaunchModeBuildItem; +import io.quarkus.deployment.builditem.ServiceStartBuildItem; +import io.quarkus.deployment.builditem.ShutdownContextBuildItem; +import io.quarkus.deployment.builditem.substrate.ReflectiveClassBuildItem; +import io.quarkus.deployment.util.HashUtil; +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.vertx.deployment.VertxBuildItem; +import io.quarkus.vertx.web.Route; +import io.quarkus.vertx.web.RoutingExchange; +import io.quarkus.vertx.web.runtime.RouterProducer; +import io.quarkus.vertx.web.runtime.RoutingExchangeImpl; +import io.quarkus.vertx.web.runtime.VertxHttpConfiguration; +import io.quarkus.vertx.web.runtime.VertxWebTemplate; +import io.vertx.core.Handler; +import io.vertx.ext.web.RoutingContext; + +class VertxWebProcessor { + + private static final Logger LOGGER = Logger.getLogger(VertxWebProcessor.class.getName()); + + 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 ROUTING_CONTEXT = DotName.createSimple(RoutingContext.class.getName()); + private static final DotName RX_ROUTING_CONTEXT = DotName + .createSimple(io.vertx.reactivex.ext.web.RoutingContext.class.getName()); + private static final DotName ROUTING_EXCHANGE = DotName.createSimple(RoutingExchange.class.getName()); + private static final String HANDLER_SUFFIX = "_RouteHandler"; + + VertxHttpConfiguration vertxHttpConfiguration; + + @BuildStep + BeanDeploymentValidatorBuildItem initialize(BuildProducer feature, + BuildProducer additionalBean, + BuildProducer routeHandlerBusinessMethods, + BuildProducer unremovableBeans) { + + additionalBean.produce(AdditionalBeanBuildItem.unremovableOf(RouterProducer.class)); + feature.produce(new FeatureBuildItem(FeatureBuildItem.VERTX_WEB)); + unremovableBeans.produce(new UnremovableBeanBuildItem(new BeanClassAnnotationExclusion(ROUTE))); + unremovableBeans.produce(new UnremovableBeanBuildItem(new BeanClassAnnotationExclusion(ROUTES))); + + return new BeanDeploymentValidatorBuildItem(new BeanDeploymentValidator() { + + @Override + public void validate(ValidationContext validationContext) { + // We need to collect all business methods annotated with @Route first + AnnotationStore annotationStore = validationContext.get(Key.ANNOTATION_STORE); + for (BeanInfo bean : validationContext.get(Key.BEANS)) { + if (bean.isClassBean()) { + // TODO: inherited business methods? + for (MethodInfo method : bean.getTarget().get().asClass().methods()) { + List routes = new LinkedList<>(); + AnnotationInstance routeAnnotation = annotationStore.getAnnotation(method, ROUTE); + if (routeAnnotation != null) { + validateMethod(bean, method); + routes.add(routeAnnotation); + } + if (routes.isEmpty()) { + AnnotationInstance routesAnnotation = annotationStore.getAnnotation(method, ROUTES); + if (routesAnnotation != null) { + validateMethod(bean, method); + Collections.addAll(routes, routesAnnotation.value().asNestedArray()); + } + } + if (!routes.isEmpty()) { + LOGGER.debugf("Found route handler business method %s declared on %s", method, bean); + routeHandlerBusinessMethods.produce(new RouteHandlerBuildItem(bean, method, routes)); + } + } + } + } + } + }); + } + + @BuildStep + @Record(ExecutionTime.RUNTIME_INIT) + ServiceStartBuildItem build(VertxWebTemplate template, BeanContainerBuildItem beanContainer, + List routeHandlerBusinessMethods, + BuildProducer generatedClass, AnnotationProxyBuildItem annotationProxy, + LaunchModeBuildItem launchMode, + BuildProducer reflectiveClasses, + ShutdownContextBuildItem shutdown, + VertxBuildItem vertx) { + + ClassOutput classOutput = new ClassOutput() { + @Override + public void write(String name, byte[] data) { + generatedClass.produce(new GeneratedClassBuildItem(true, name, data)); + } + }; + Map> routeConfigs = new HashMap<>(); + for (RouteHandlerBuildItem businessMethod : routeHandlerBusinessMethods) { + String handlerClass = generateHandler(businessMethod.getBean(), businessMethod.getMethod(), classOutput); + List routes = businessMethod.getRoutes().stream() + .map(annotationInstance -> annotationProxy.from(annotationInstance, Route.class)) + .collect(Collectors.toList()); + routeConfigs.put(handlerClass, routes); + reflectiveClasses.produce(new ReflectiveClassBuildItem(false, false, handlerClass)); + } + template.configureRouter(vertx.getVertx(), beanContainer.getValue(), routeConfigs, vertxHttpConfiguration, + launchMode.getLaunchMode(), + shutdown); + return new ServiceStartBuildItem("vertx-web"); + } + + @BuildStep + AnnotationsTransformerBuildItem annotationTransformer() { + return new AnnotationsTransformerBuildItem(new AnnotationsTransformer() { + + @Override + public boolean appliesTo(org.jboss.jandex.AnnotationTarget.Kind kind) { + return kind == org.jboss.jandex.AnnotationTarget.Kind.CLASS; + } + + @Override + public void transform(TransformationContext context) { + if (context.getAnnotations().isEmpty()) { + // Class with no annotations but with a method annotated with @Route + if (context.getTarget().asClass().annotations().containsKey(ROUTE) + || context.getTarget().asClass().annotations().containsKey(ROUTES)) { + LOGGER.debugf( + "Found route handler business methods on a class %s with no scope annotation - adding @Singleton", + context.getTarget()); + context.transform().add(Singleton.class).done(); + } + } + } + }); + } + + private void validateMethod(BeanInfo bean, MethodInfo method) { + if (!method.returnType().kind().equals(Type.Kind.VOID)) { + throw new IllegalStateException( + String.format("Route handler business method must return void [method: %s, bean: %s]", method, bean)); + } + List params = method.parameters(); + boolean hasInvalidParam = true; + if (params.size() == 1) { + DotName paramTypeName = params.get(0).name(); + if (ROUTING_CONTEXT.equals(paramTypeName) || RX_ROUTING_CONTEXT.equals(paramTypeName) + || ROUTING_EXCHANGE.equals(paramTypeName)) { + hasInvalidParam = false; + } + } + if (hasInvalidParam) { + throw new IllegalStateException(String.format( + "Route handler business method must accept exactly one parameter of type RoutingContext/RoutingExchange: %s [method: %s, bean: %s]", + params, method, bean)); + } + } + + private String generateHandler(BeanInfo bean, MethodInfo method, ClassOutput classOutput) { + + String baseName; + if (bean.getImplClazz().enclosingClass() != null) { + baseName = DotNames.simpleName(bean.getImplClazz().enclosingClass()) + "_" + + DotNames.simpleName(bean.getImplClazz().name()); + } else { + baseName = DotNames.simpleName(bean.getImplClazz().name()); + } + String targetPackage = DotNames.packageName(bean.getImplClazz().name()); + + StringBuilder sigBuilder = new StringBuilder(); + sigBuilder.append(method.name()).append("_").append(method.returnType().name().toString()); + for (Type i : method.parameters()) { + sigBuilder.append(i.name().toString()); + } + String generatedName = targetPackage.replace('.', '/') + "/" + baseName + HANDLER_SUFFIX + "_" + method.name() + "_" + + HashUtil.sha1(sigBuilder.toString()); + + ClassCreator invokerCreator = ClassCreator.builder().classOutput(classOutput).className(generatedName) + .interfaces(Handler.class).build(); + + MethodCreator invoke = invokerCreator.getMethodCreator("handle", void.class, Object.class); + // ArcContainer container = Arc.container(); + // InjectableBean handle = container().instance(bean); + // handle.get().foo(ctx); + ResultHandle containerHandle = invoke + .invokeStaticMethod(MethodDescriptor.ofMethod(Arc.class, "container", ArcContainer.class)); + ResultHandle beanHandle = invoke.invokeInterfaceMethod( + MethodDescriptor.ofMethod(ArcContainer.class, "bean", InjectableBean.class, String.class), + containerHandle, invoke.load(bean.getIdentifier())); + ResultHandle instanceHandle = invoke.invokeInterfaceMethod( + MethodDescriptor.ofMethod(ArcContainer.class, "instance", InstanceHandle.class, InjectableBean.class), + containerHandle, beanHandle); + ResultHandle beanInstanceHandle = invoke + .invokeInterfaceMethod(MethodDescriptor.ofMethod(InstanceHandle.class, "get", Object.class), instanceHandle); + + ResultHandle paramHandle; + MethodDescriptor methodDescriptor; + if (method.parameters().get(0).name().equals(ROUTING_CONTEXT)) { + paramHandle = invoke.getMethodParam(0); + methodDescriptor = MethodDescriptor.ofMethod(bean.getImplClazz().name().toString(), method.name(), void.class, + RoutingContext.class); + } else if (method.parameters().get(0).name().equals(RX_ROUTING_CONTEXT)) { + paramHandle = invoke.newInstance( + MethodDescriptor.ofConstructor(io.vertx.reactivex.ext.web.RoutingContext.class, RoutingContext.class), + invoke.getMethodParam(0)); + methodDescriptor = MethodDescriptor.ofMethod(bean.getImplClazz().name().toString(), method.name(), void.class, + io.vertx.reactivex.ext.web.RoutingContext.class); + } else { + paramHandle = invoke.newInstance(MethodDescriptor.ofConstructor(RoutingExchangeImpl.class, RoutingContext.class), + invoke.getMethodParam(0)); + methodDescriptor = MethodDescriptor.ofMethod(bean.getImplClazz().name().toString(), method.name(), void.class, + RoutingExchange.class); + } + + // Invoke the business method handler + invoke.invokeVirtualMethod(methodDescriptor, beanInstanceHandle, paramHandle); + + // handle.destroy() - destroy dependent instance afterwards + if (BuiltinScope.DEPENDENT.is(bean.getScope())) { + invoke.invokeInterfaceMethod(MethodDescriptor.ofMethod(InstanceHandle.class, "destroy", void.class), + instanceHandle); + } + invoke.returnValue(null); + + invokerCreator.close(); + return generatedName.replace('/', '.'); + } +} diff --git a/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/devmode/VertxHotReplacementSetup.java b/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/devmode/VertxHotReplacementSetup.java new file mode 100644 index 0000000000000..5b0aee139da9b --- /dev/null +++ b/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/devmode/VertxHotReplacementSetup.java @@ -0,0 +1,63 @@ +package io.quarkus.vertx.web.deployment.devmode; + +import io.quarkus.deployment.devmode.HotReplacementContext; +import io.quarkus.deployment.devmode.HotReplacementSetup; +import io.quarkus.deployment.devmode.ReplacementDebugPage; +import io.quarkus.vertx.web.runtime.VertxWebTemplate; +import io.vertx.core.http.HttpServerResponse; +import io.vertx.ext.web.RoutingContext; + +public class VertxHotReplacementSetup implements HotReplacementSetup { + + private volatile long nextUpdate; + private HotReplacementContext hotReplacementContext; + + private static final long HOT_REPLACEMENT_INTERVAL = 2000; + + @Override + public void setupHotDeployment(HotReplacementContext context) { + this.hotReplacementContext = context; + VertxWebTemplate.setHotReplacement(this::handleHotReplacementRequest); + } + + void handleHotReplacementRequest(RoutingContext routingContext) { + + if (nextUpdate > System.currentTimeMillis()) { + if (hotReplacementContext.getDeploymentProblem() != null) { + handleDeploymentProblem(routingContext, hotReplacementContext.getDeploymentProblem()); + return; + } + routingContext.next(); + return; + } + boolean restart = false; + synchronized (this) { + if (nextUpdate < System.currentTimeMillis()) { + try { + restart = hotReplacementContext.doScan(); + } catch (Exception e) { + throw new IllegalStateException("Unable to perform hot replacement scanning", e); + } + nextUpdate = System.currentTimeMillis() + HOT_REPLACEMENT_INTERVAL; + } + } + if (hotReplacementContext.getDeploymentProblem() != null) { + handleDeploymentProblem(routingContext, hotReplacementContext.getDeploymentProblem()); + return; + } + if (restart) { + routingContext.reroute(routingContext.request().path()); + } else { + routingContext.next(); + } + } + + public static void handleDeploymentProblem(RoutingContext routingContext, final Throwable exception) { + String bodyText = ReplacementDebugPage.generateHtml(exception); + HttpServerResponse response = routingContext.response(); + response.setStatusCode(500); + response.headers().add("Content-Type", "text/html; charset=UTF-8"); + response.end(bodyText); + } + +} diff --git a/extensions/vertx-web/deployment/src/main/resources/META-INF/services/io.quarkus.deployment.devmode.HotReplacementSetup b/extensions/vertx-web/deployment/src/main/resources/META-INF/services/io.quarkus.deployment.devmode.HotReplacementSetup new file mode 100644 index 0000000000000..44306b5e0ad17 --- /dev/null +++ b/extensions/vertx-web/deployment/src/main/resources/META-INF/services/io.quarkus.deployment.devmode.HotReplacementSetup @@ -0,0 +1 @@ +io.quarkus.vertx.web.deployment.devmode.VertxHotReplacementSetup \ No newline at end of file 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 new file mode 100644 index 0000000000000..49e0bcc9c02f6 --- /dev/null +++ b/extensions/vertx-web/deployment/src/test/java/io/quarkus/vertx/web/SimpleRouteTest.java @@ -0,0 +1,118 @@ +package io.quarkus.vertx.web; + +import static io.vertx.core.http.HttpMethod.DELETE; +import static io.vertx.core.http.HttpMethod.GET; +import static org.hamcrest.Matchers.is; + +import java.util.Objects; +import java.util.stream.Collectors; + +import javax.enterprise.event.Observes; +import javax.inject.Inject; + +import org.hamcrest.Matchers; +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.ConsumeEvent; +import io.restassured.RestAssured; +import io.vertx.core.eventbus.EventBus; +import io.vertx.ext.web.Router; +import io.vertx.ext.web.RoutingContext; + +public class SimpleRouteTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class).addClasses(SimpleBean.class, + SimpleEventBusBean.class, SimpleRoutesBean.class)); + + @Test + public void testSimpleRoute() { + RestAssured.when().get("/hello").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(404); + RestAssured.when().delete("/delete").then().statusCode(200).body(is("deleted")); + RestAssured.when().get("/routes").then().statusCode(200) + .body(Matchers.containsString("/hello-event-bus")); + } + + static class SimpleBean { + + @Route(path = "/hello") + @Route(path = "/foo") + void hello(RoutingContext context) { + String name = context.request().getParam("name"); + context.response().setStatusCode(200).end("Hello " + (name != null ? name : "world") + "!"); + } + + @Route(path = "/rx-hello") + void rxHello(io.vertx.reactivex.ext.web.RoutingContext context) { + String name = context.request().getParam("name"); + context.response().setStatusCode(200).end("Hello " + (name != null ? name : "world") + "!"); + } + + @Route(path = "/bzuk") + void bzuk(RoutingExchange exchange) { + exchange.ok("Hello " + exchange.getParam("name").orElse("world") + "!"); + } + + @Route(path = "/delete", methods = DELETE) + void deleteHttpMethod(RoutingExchange exchange) { + exchange.ok("deleted"); + } + + } + + static class SimpleRoutesBean { + + @Inject + Router router; + + @Route(path = "/routes", methods = GET) + void getRoutes(RoutingContext context) { + context.response().setStatusCode(200).end( + router.getRoutes().stream().map(r -> r.getPath()).filter(Objects::nonNull) + .collect(Collectors.joining(","))); + } + + void addBar(@Observes Router router) { + router.get("/bar").handler(ctx -> ctx.response().setStatusCode(200).end("Hello bar!")); + } + + } + + static class SimpleEventBusBean { + + @Inject + EventBus eventBus; + + @Route(path = "/hello-event-bus", methods = GET) + void helloEventBus(RoutingExchange exchange) { + eventBus.send("hello", exchange.getParam("name").orElse("missing"), ar -> { + if (ar.succeeded()) { + exchange.ok(ar.result().body().toString()); + } else { + exchange.serverError().end(ar.cause().getMessage()); + } + }); + } + } + + static class HelloGenerator { + + @ConsumeEvent("hello") + String generate(String name) { + return "Hello " + name.toUpperCase() + "!"; + } + + } + +} diff --git a/extensions/vertx-web/pom.xml b/extensions/vertx-web/pom.xml new file mode 100644 index 0000000000000..bd5afd10598f6 --- /dev/null +++ b/extensions/vertx-web/pom.xml @@ -0,0 +1,36 @@ + + + + + + quarkus-build-parent + io.quarkus + 999-SNAPSHOT + ../../build-parent/pom.xml + + 4.0.0 + + quarkus-vertx-web-parent + Quarkus - Vert.x Web + pom + + deployment + runtime + + diff --git a/extensions/vertx-web/runtime/pom.xml b/extensions/vertx-web/runtime/pom.xml new file mode 100644 index 0000000000000..be8a09f1df4d3 --- /dev/null +++ b/extensions/vertx-web/runtime/pom.xml @@ -0,0 +1,78 @@ + + + + + + quarkus-vertx-web-parent + io.quarkus + 999-SNAPSHOT + ../ + + 4.0.0 + + quarkus-vertx-web + Quarkus - Vert.x Web - Runtime + + + + io.quarkus + quarkus-core + + + io.quarkus + quarkus-vertx + + + io.vertx + vertx-web + + + com.fasterxml.jackson.core + jackson-databind + + + + + junit + junit + test + + + + + + + io.quarkus + quarkus-bootstrap-maven-plugin + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + 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 new file mode 100644 index 0000000000000..6112fb2665f60 --- /dev/null +++ b/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/Route.java @@ -0,0 +1,104 @@ +package io.quarkus.vertx.web; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import io.quarkus.vertx.web.Route.Routes; +import io.vertx.core.Handler; +import io.vertx.core.http.HttpMethod; +import io.vertx.ext.web.Router; +import io.vertx.ext.web.RoutingContext; + +/** + * Annotation used to configure a {@link io.quarkus.vertx.web.Route} in a declarative way. + *

+ * The target business method must return {@code void} and accept exacly one argument of type {@link RoutingContext}. + */ +@Repeatable(Routes.class) +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface Route { + + /** + * + * @see Router#route(String) + * @return the path + */ + String path() default ""; + + /** + * + * @see Router#routeWithRegex(String) + * @return the path regex + */ + String regex() default ""; + + /** + * + * @see io.quarkus.vertx.web.Route#method(HttpMethod) + * @return the HTTP methods + */ + HttpMethod[] methods() default {}; + + /** + * + * @return the type of the handler + */ + HandlerType type() default HandlerType.NORMAL; + + /** + * If set to {@link Integer#MIN_VALUE} the order of the route is not modified. + * + * @see io.quarkus.vertx.web.Route#order(int) + */ + int order() default Integer.MIN_VALUE; + + /** + * + * @see io.quarkus.vertx.web.Route#produces(String) + * @return the produced content types + */ + String[] produces() default {}; + + /** + * + * @see io.quarkus.vertx.web.Route#consumes(String) + * @return the consumed content types + */ + String[] consumes() default {}; + + enum HandlerType { + + /** + * A request handler. + * + * @see io.vertx.ext.web.Route#handler(Handler) + */ + NORMAL, + /** + * A blocking request handler. + * + * @see io.vertx.ext.web.Route#blockingHandler(Handler) + */ + BLOCKING, + /** + * A failure handler. + * + * @see io.vertx.ext.web.Route#failureHandler(Handler) + */ + FAILURE + + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + @interface Routes { + + Route[] value(); + + } + +} diff --git a/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/RoutingExchange.java b/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/RoutingExchange.java new file mode 100644 index 0000000000000..ffa5869f200c7 --- /dev/null +++ b/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/RoutingExchange.java @@ -0,0 +1,60 @@ +package io.quarkus.vertx.web; + +import java.util.Optional; + +import io.vertx.core.http.HttpServerRequest; +import io.vertx.core.http.HttpServerResponse; +import io.vertx.ext.web.RoutingContext; + +/** + * Convenient wrapper of {@link RoutingContext}. + */ +public interface RoutingExchange { + + RoutingContext context(); + + default HttpServerRequest request() { + return context().request(); + } + + /** + * + * @param paramName + * @return the request parameter + * @see HttpServerRequest#getParam(String) + */ + default Optional getParam(String paramName) { + return Optional.ofNullable(request().getParam(paramName)); + } + + /** + * + * @param paramName + * @return the first header value with the specified name + * @see HttpServerRequest#getHeader(CharSequence) + */ + default Optional getHeader(CharSequence headerName) { + return Optional.ofNullable(request().getHeader(headerName)); + } + + default HttpServerResponse response() { + return context().response(); + } + + default HttpServerResponse ok() { + return response().setStatusCode(200); + } + + default void ok(String chunk) { + ok().end(chunk); + } + + default HttpServerResponse serverError() { + return response().setStatusCode(500); + } + + default HttpServerResponse notFound() { + return response().setStatusCode(404); + } + +} diff --git a/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/RouterProducer.java b/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/RouterProducer.java new file mode 100644 index 0000000000000..671ebded49fa8 --- /dev/null +++ b/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/RouterProducer.java @@ -0,0 +1,25 @@ +package io.quarkus.vertx.web.runtime; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Produces; +import javax.inject.Singleton; + +import io.vertx.ext.web.Router; + +@Singleton +public class RouterProducer { + + private volatile Router router; + + void initialize(Router router) { + this.router = router; + } + + // Note that we need a client proxy because if a bean also @Observes Router a null value would be injected + @ApplicationScoped + @Produces + Router produceRouter() { + return router; + } + +} diff --git a/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/RoutingExchangeImpl.java b/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/RoutingExchangeImpl.java new file mode 100644 index 0000000000000..31f929acc5612 --- /dev/null +++ b/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/RoutingExchangeImpl.java @@ -0,0 +1,18 @@ +package io.quarkus.vertx.web.runtime; + +import io.quarkus.vertx.web.RoutingExchange; +import io.vertx.ext.web.RoutingContext; + +public class RoutingExchangeImpl implements RoutingExchange { + + private final RoutingContext context; + + public RoutingExchangeImpl(RoutingContext context) { + this.context = context; + } + + @Override + public RoutingContext context() { + return context; + } +} diff --git a/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/VertxHttpConfiguration.java b/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/VertxHttpConfiguration.java new file mode 100644 index 0000000000000..ca529f8ccd4f6 --- /dev/null +++ b/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/VertxHttpConfiguration.java @@ -0,0 +1,49 @@ +/* + * Copyright 2018 Red Hat, Inc. + * + * Licensed 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.vertx.web.runtime; + +import io.quarkus.runtime.LaunchMode; +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; + +@ConfigRoot(phase = ConfigPhase.RUN_TIME) +public class VertxHttpConfiguration { + + /** + * The HTTP port + */ + @ConfigItem(defaultValue = "8080") + public int port; + + /** + * The HTTP port used to run tests + */ + @ConfigItem(defaultValue = "8081") + public int testPort; + + /** + * The HTTP host + */ + @ConfigItem(defaultValue = "0.0.0.0") + public String host; + + public int determinePort(LaunchMode launchMode) { + return launchMode == LaunchMode.TEST ? testPort : port; + } + +} diff --git a/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/VertxWebTemplate.java b/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/VertxWebTemplate.java new file mode 100644 index 0000000000000..f3aa6ac818157 --- /dev/null +++ b/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/VertxWebTemplate.java @@ -0,0 +1,179 @@ +package io.quarkus.vertx.web.runtime; + +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.CountDownLatch; + +import javax.enterprise.event.Event; + +import org.jboss.logging.Logger; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.runtime.BeanContainer; +import io.quarkus.runtime.LaunchMode; +import io.quarkus.runtime.RuntimeValue; +import io.quarkus.runtime.ShutdownContext; +import io.quarkus.runtime.Timing; +import io.quarkus.runtime.annotations.Template; +import io.quarkus.vertx.web.Route; +import io.vertx.core.Handler; +import io.vertx.core.Vertx; +import io.vertx.core.http.HttpMethod; +import io.vertx.core.http.HttpServer; +import io.vertx.core.http.HttpServerOptions; +import io.vertx.ext.web.Router; +import io.vertx.ext.web.RoutingContext; +import io.vertx.ext.web.handler.BodyHandler; + +@Template +public class VertxWebTemplate { + + public static void setHotReplacement(Handler handler) { + hotReplacementHandler = handler; + } + + private static final Logger LOGGER = Logger.getLogger(VertxWebTemplate.class.getName()); + + private static volatile Handler hotReplacementHandler; + + private static volatile Router router; + private static volatile HttpServer server; + + public void configureRouter(RuntimeValue vertx, BeanContainer container, Map> routeHandlers, + VertxHttpConfiguration vertxHttpConfiguration, LaunchMode launchMode, ShutdownContext shutdown) { + + List appRoutes = initialize(vertx.getValue(), vertxHttpConfiguration, routeHandlers, + launchMode); + container.instance(RouterProducer.class).initialize(router); + + if (launchMode == LaunchMode.DEVELOPMENT) { + shutdown.addShutdownTask(new Runnable() { + @Override + public void run() { + for (io.vertx.ext.web.Route route : appRoutes) { + route.remove(); + } + } + }); + } + } + + List initialize(Vertx vertx, VertxHttpConfiguration vertxHttpConfiguration, + Map> routeHandlers, + LaunchMode launchMode) { + List routes = new ArrayList<>(); + if (router == null) { + router = Router.router(vertx); + router.route().handler(BodyHandler.create()); + if (hotReplacementHandler != null) { + router.route().blockingHandler(hotReplacementHandler); + } + } + for (Entry> entry : routeHandlers.entrySet()) { + Handler handler = createHandler(entry.getKey()); + for (Route route : entry.getValue()) { + routes.add(addRoute(router, handler, route)); + } + } + // Make it also possible to register the route handlers programatically + Event event = Arc.container().beanManager().getEvent(); + event.select(Router.class).fire(router); + + // Start the server + if (server == null) { + CountDownLatch latch = new CountDownLatch(1); + // Http server configuration + HttpServerOptions httpServerOptions = createHttpServerOptions(vertxHttpConfiguration, launchMode); + event.select(HttpServerOptions.class).fire(httpServerOptions); + server = vertx.createHttpServer(httpServerOptions).requestHandler(router) + .listen(ar -> { + if (ar.succeeded()) { + // TODO log proper message + Timing.setHttpServer(String.format( + "Listening on: http://%s:%s", httpServerOptions.getHost(), httpServerOptions.getPort())); + latch.countDown(); + } + }); + try { + latch.await(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new IllegalStateException("Unable to start the HTTP server", e); + } + } + return routes; + } + + private HttpServerOptions createHttpServerOptions(VertxHttpConfiguration vertxHttpConfiguration, LaunchMode launchMode) { + // TODO other config properties + HttpServerOptions options = new HttpServerOptions(); + options.setHost(vertxHttpConfiguration.host); + options.setPort(vertxHttpConfiguration.determinePort(launchMode)); + return options; + } + + private io.vertx.ext.web.Route addRoute(Router router, Handler handler, Route routeAnnotation) { + io.vertx.ext.web.Route route; + if (!routeAnnotation.regex().isEmpty()) { + route = router.routeWithRegex(routeAnnotation.regex()); + } else if (!routeAnnotation.path().isEmpty()) { + route = router.route(routeAnnotation.path()); + } else { + route = router.route(); + } + if (routeAnnotation.methods().length > 0) { + for (HttpMethod method : routeAnnotation.methods()) { + route.method(method); + } + } + if (routeAnnotation.order() != Integer.MIN_VALUE) { + route.order(routeAnnotation.order()); + } + if (routeAnnotation.produces().length > 0) { + for (String produces : routeAnnotation.produces()) { + route.produces(produces); + } + } + if (routeAnnotation.consumes().length > 0) { + for (String consumes : routeAnnotation.consumes()) { + route.consumes(consumes); + } + } + switch (routeAnnotation.type()) { + case NORMAL: + route.handler(handler); + break; + case BLOCKING: + // We don't mind if blocking handlers are executed in parallel + route.blockingHandler(handler, false); + break; + case FAILURE: + route.failureHandler(handler); + break; + default: + throw new IllegalStateException("Unsupported handler type: " + routeAnnotation.type()); + } + LOGGER.debugf("Route registered for %s", routeAnnotation); + return route; + } + + @SuppressWarnings("unchecked") + private Handler createHandler(String handlerClassName) { + try { + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + if (cl == null) { + cl = RouterProducer.class.getClassLoader(); + } + Class> handlerClazz = (Class>) cl + .loadClass(handlerClassName); + return handlerClazz.getDeclaredConstructor().newInstance(); + } catch (InstantiationException | IllegalAccessException | ClassNotFoundException | NoSuchMethodException + | InvocationTargetException e) { + throw new IllegalStateException("Unable to create invoker: " + handlerClassName, e); + } + } + +} diff --git a/extensions/vertx/deployment/pom.xml b/extensions/vertx/deployment/pom.xml index 285b92aa9cc41..6e12165291cde 100644 --- a/extensions/vertx/deployment/pom.xml +++ b/extensions/vertx/deployment/pom.xml @@ -26,27 +26,27 @@ 4.0.0 - quarkus-vertx + quarkus-vertx-deployment Quarkus - Vert.x - Deployment io.quarkus - quarkus-core + quarkus-core-deployment io.quarkus - quarkus-arc + quarkus-arc-deployment io.quarkus - quarkus-netty + quarkus-netty-deployment io.quarkus - quarkus-vertx-runtime + quarkus-vertx - + io.quarkus quarkus-junit5-internal diff --git a/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/EventConsumerBusinessMethodItem.java b/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/EventConsumerBusinessMethodItem.java index c18e990d358af..5b52c3d697cea 100644 --- a/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/EventConsumerBusinessMethodItem.java +++ b/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/EventConsumerBusinessMethodItem.java @@ -1,10 +1,10 @@ package io.quarkus.vertx.deployment; -import org.jboss.builder.item.MultiBuildItem; import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.MethodInfo; import io.quarkus.arc.processor.BeanInfo; +import io.quarkus.builder.item.MultiBuildItem; public final class EventConsumerBusinessMethodItem extends MultiBuildItem { diff --git a/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/VertxBuildItem.java b/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/VertxBuildItem.java new file mode 100644 index 0000000000000..f7ee8ea705342 --- /dev/null +++ b/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/VertxBuildItem.java @@ -0,0 +1,19 @@ +package io.quarkus.vertx.deployment; + +import io.quarkus.builder.item.SimpleBuildItem; +import io.quarkus.runtime.RuntimeValue; +import io.vertx.core.Vertx; + +public final class VertxBuildItem extends SimpleBuildItem { + + private final RuntimeValue vertx; + + public VertxBuildItem(RuntimeValue vertx) { + this.vertx = vertx; + } + + public RuntimeValue getVertx() { + return vertx; + } + +} 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 215c95c74c1bf..0ade65fc79b96 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 @@ -16,6 +16,7 @@ package io.quarkus.vertx.deployment; +import java.lang.annotation.Annotation; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -26,6 +27,7 @@ import javax.inject.Singleton; import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationValue; import org.jboss.jandex.DotName; import org.jboss.jandex.MethodInfo; import org.jboss.jandex.Type; @@ -54,21 +56,30 @@ import io.quarkus.deployment.builditem.AnnotationProxyBuildItem; import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.deployment.builditem.GeneratedClassBuildItem; +import io.quarkus.deployment.builditem.LaunchModeBuildItem; +import io.quarkus.deployment.builditem.ShutdownContextBuildItem; import io.quarkus.deployment.builditem.substrate.ReflectiveClassBuildItem; import io.quarkus.deployment.builditem.substrate.SubstrateConfigBuildItem; import io.quarkus.deployment.util.HashUtil; +import io.quarkus.gizmo.AssignableResultHandle; import io.quarkus.gizmo.BytecodeCreator; +import io.quarkus.gizmo.CatchBlockCreator; import io.quarkus.gizmo.ClassCreator; import io.quarkus.gizmo.ClassOutput; import io.quarkus.gizmo.FunctionCreator; import io.quarkus.gizmo.MethodCreator; import io.quarkus.gizmo.MethodDescriptor; import io.quarkus.gizmo.ResultHandle; +import io.quarkus.gizmo.TryBlock; +import io.quarkus.runtime.RuntimeValue; import io.quarkus.vertx.ConsumeEvent; import io.quarkus.vertx.runtime.EventConsumerInvoker; import io.quarkus.vertx.runtime.VertxConfiguration; import io.quarkus.vertx.runtime.VertxProducer; import io.quarkus.vertx.runtime.VertxTemplate; +import io.vertx.core.Future; +import io.vertx.core.Handler; +import io.vertx.core.Vertx; import io.vertx.core.eventbus.Message; class VertxProcessor { @@ -77,35 +88,60 @@ class VertxProcessor { private static final DotName CONSUME_EVENT = DotName.createSimple(ConsumeEvent.class.getName()); private static final DotName MESSAGE = DotName.createSimple(Message.class.getName()); + private static final DotName RX_MESSAGE = DotName.createSimple(io.vertx.reactivex.core.eventbus.Message.class.getName()); + private static final DotName AXLE_MESSAGE = DotName.createSimple(io.vertx.axle.core.eventbus.Message.class.getName()); private static final DotName COMPLETION_STAGE = DotName.createSimple(CompletionStage.class.getName()); private static final String INVOKER_SUFFIX = "_VertxInvoker"; + private static final MethodDescriptor ARC_CONTAINER = MethodDescriptor.ofMethod(Arc.class, "container", ArcContainer.class); + private static final MethodDescriptor INSTANCE_HANDLE_GET = MethodDescriptor.ofMethod(InstanceHandle.class, "get", + Object.class); + private static final MethodDescriptor ARC_CONTAINER_BEAN = MethodDescriptor.ofMethod(ArcContainer.class, "bean", + InjectableBean.class, String.class); + private static final MethodDescriptor ARC_CONTAINER_INSTANCE_FOR_BEAN = MethodDescriptor.ofMethod(ArcContainer.class, + "instance", InstanceHandle.class, + InjectableBean.class); + private static final MethodDescriptor ARC_CONTAINER_INSTANCE_FOR_TYPE = MethodDescriptor.ofMethod(ArcContainer.class, + "instance", InstanceHandle.class, + Class.class, Annotation[].class); + private static final MethodDescriptor VERTX_EXECUTE_BLOCKING = MethodDescriptor.ofMethod(Vertx.class, + "executeBlocking", void.class, Handler.class, boolean.class, Handler.class); + private static final MethodDescriptor FUTURE_COMPLETE = MethodDescriptor.ofMethod(Future.class, + "complete", void.class, Object.class); + private static final MethodDescriptor FUTURE_FAIL = MethodDescriptor.ofMethod(Future.class, + "fail", void.class, Throwable.class); + private static final MethodDescriptor RX_MESSAGE_NEW_INSTANCE = MethodDescriptor.ofMethod( + io.vertx.reactivex.core.eventbus.Message.class, + "newInstance", io.vertx.reactivex.core.eventbus.Message.class, Message.class); + private static final MethodDescriptor AXLE_MESSAGE_NEW_INSTANCE = MethodDescriptor.ofMethod( + io.vertx.axle.core.eventbus.Message.class, + "newInstance", io.vertx.axle.core.eventbus.Message.class, Message.class); + private static final MethodDescriptor MESSAGE_REPLY = MethodDescriptor.ofMethod(Message.class, "reply", void.class, + Object.class); + private static final MethodDescriptor MESSAGE_BODY = MethodDescriptor.ofMethod(Message.class, "body", Object.class); + private static final MethodDescriptor INSTANCE_HANDLE_DESTROY = MethodDescriptor.ofMethod(InstanceHandle.class, "destroy", + void.class); + @Inject BuildProducer reflectiveClass; @BuildStep SubstrateConfigBuildItem build() { - return SubstrateConfigBuildItem.builder() - .addNativeImageSystemProperty("vertx.disableDnsResolver", "true") - .build(); + return SubstrateConfigBuildItem.builder().addNativeImageSystemProperty("vertx.disableDnsResolver", "true").build(); } - /** - * The Vert.x configuration, if set. - */ - VertxConfiguration vertx; - @BuildStep AdditionalBeanBuildItem registerBean() { - return new AdditionalBeanBuildItem(false, VertxProducer.class); + return AdditionalBeanBuildItem.unremovableOf(VertxProducer.class); } @BuildStep @Record(ExecutionTime.RUNTIME_INIT) - void build(VertxTemplate template, BeanContainerBuildItem beanContainer, BuildProducer feature, + VertxBuildItem build(VertxTemplate template, BeanContainerBuildItem beanContainer, BuildProducer feature, List messageConsumerBusinessMethods, BuildProducer generatedClass, - AnnotationProxyBuildItem annotationProxy) { + AnnotationProxyBuildItem annotationProxy, LaunchModeBuildItem launchMode, ShutdownContextBuildItem shutdown, + VertxConfiguration config) { feature.produce(new FeatureBuildItem(FeatureBuildItem.VERTX)); Map messageConsumerConfigurations = new HashMap<>(); ClassOutput classOutput = new ClassOutput() { @@ -115,13 +151,17 @@ public void write(String name, byte[] data) { } }; for (EventConsumerBusinessMethodItem businessMethod : messageConsumerBusinessMethods) { - String invokerClass = generateInvoker(businessMethod.getBean(), businessMethod.getMethod(), classOutput); + String invokerClass = generateInvoker(businessMethod.getBean(), businessMethod.getMethod(), + businessMethod.getConsumeEvent(), classOutput); messageConsumerConfigurations.put(invokerClass, annotationProxy.builder(businessMethod.getConsumeEvent(), ConsumeEvent.class) .withDefaultValue("value", businessMethod.getBean().getBeanClass().toString()).build()); reflectiveClass.produce(new ReflectiveClassBuildItem(false, false, invokerClass)); } - template.configureVertx(beanContainer.getValue(), vertx, messageConsumerConfigurations); + RuntimeValue vertx = template.configureVertx(beanContainer.getValue(), config, messageConsumerConfigurations, + launchMode.getLaunchMode(), + shutdown); + return new VertxBuildItem(vertx); } @BuildStep @@ -187,7 +227,7 @@ public void transform(TransformationContext context) { }); } - private String generateInvoker(BeanInfo bean, MethodInfo method, ClassOutput classOutput) { + private String generateInvoker(BeanInfo bean, MethodInfo method, AnnotationInstance consumeEvent, ClassOutput classOutput) { String baseName; if (bean.getImplClazz().enclosingClass() != null) { @@ -210,42 +250,82 @@ private String generateInvoker(BeanInfo bean, MethodInfo method, ClassOutput cla .interfaces(EventConsumerInvoker.class).build(); MethodCreator invoke = invokerCreator.getMethodCreator("invoke", void.class, Message.class); - // InjectableBean handle = Arc.container().instance(bean); - // handle.get().foo(message); - ResultHandle containerHandle = invoke - .invokeStaticMethod(MethodDescriptor.ofMethod(Arc.class, "container", ArcContainer.class)); - ResultHandle beanHandle = invoke.invokeInterfaceMethod( - MethodDescriptor.ofMethod(ArcContainer.class, "bean", InjectableBean.class, String.class), - containerHandle, invoke.load(bean.getIdentifier())); - ResultHandle instanceHandle = invoke.invokeInterfaceMethod( - MethodDescriptor.ofMethod(ArcContainer.class, "instance", InstanceHandle.class, InjectableBean.class), - containerHandle, beanHandle); + ResultHandle containerHandle = invoke.invokeStaticMethod(ARC_CONTAINER); + + AnnotationValue blocking = consumeEvent.value("blocking"); + if (blocking != null && blocking.asBoolean()) { + // Blocking operation must be performed on a worker thread + ResultHandle vertxHandle = invoke + .invokeInterfaceMethod(INSTANCE_HANDLE_GET, + invoke.invokeInterfaceMethod(ARC_CONTAINER_INSTANCE_FOR_TYPE, containerHandle, + invoke.loadClass(Vertx.class), + invoke.newArray(Annotation.class.getName(), invoke.load(0)))); + + FunctionCreator func = invoke.createFunction(Handler.class); + BytecodeCreator funcBytecode = func.getBytecode(); + AssignableResultHandle messageHandle = funcBytecode.createVariable(Message.class); + funcBytecode.assign(messageHandle, invoke.getMethodParam(0)); + TryBlock tryBlock = funcBytecode.tryBlock(); + invoke(bean, method, messageHandle, tryBlock); + tryBlock.invokeInterfaceMethod(FUTURE_COMPLETE, funcBytecode.getMethodParam(0), tryBlock.loadNull()); + CatchBlockCreator catchBlock = tryBlock.addCatch(Exception.class); + catchBlock.invokeInterfaceMethod(FUTURE_FAIL, funcBytecode.getMethodParam(0), catchBlock.getMethodParam(0)); + funcBytecode.returnValue(null); + + invoke.invokeInterfaceMethod(VERTX_EXECUTE_BLOCKING, vertxHandle, func.getInstance(), invoke.load(false), + invoke.loadNull()); + } else { + invoke(bean, method, invoke.getMethodParam(0), invoke); + } + invoke.returnValue(null); + invokerCreator.close(); + return generatedName.replace('/', '.'); + } + + private void invoke(BeanInfo bean, MethodInfo method, ResultHandle messageHandle, BytecodeCreator invoke) { + ResultHandle containerHandle = invoke.invokeStaticMethod(ARC_CONTAINER); + ResultHandle beanHandle = invoke.invokeInterfaceMethod(ARC_CONTAINER_BEAN, containerHandle, + invoke.load(bean.getIdentifier())); + ResultHandle instanceHandle = invoke.invokeInterfaceMethod(ARC_CONTAINER_INSTANCE_FOR_BEAN, containerHandle, + beanHandle); ResultHandle beanInstanceHandle = invoke - .invokeInterfaceMethod(MethodDescriptor.ofMethod(InstanceHandle.class, "get", Object.class), instanceHandle); + .invokeInterfaceMethod(INSTANCE_HANDLE_GET, instanceHandle); Type paramType = method.parameters().get(0); if (paramType.name().equals(MESSAGE)) { - // Parameter is io.vertx.core.eventbus.Message + // io.vertx.core.eventbus.Message invoke.invokeVirtualMethod( MethodDescriptor.ofMethod(bean.getImplClazz().name().toString(), method.name(), void.class, Message.class), - beanInstanceHandle, invoke.getMethodParam(0)); + beanInstanceHandle, messageHandle); + } else if (paramType.name().equals(RX_MESSAGE)) { + // io.vertx.reactivex.core.eventbus.Message + ResultHandle rxMessageHandle = invoke.invokeStaticMethod(RX_MESSAGE_NEW_INSTANCE, messageHandle); + invoke.invokeVirtualMethod( + MethodDescriptor.ofMethod(bean.getImplClazz().name().toString(), method.name(), void.class, + io.vertx.reactivex.core.eventbus.Message.class), + beanInstanceHandle, rxMessageHandle); + } else if (paramType.name().equals(AXLE_MESSAGE)) { + // io.vertx.axle.core.eventbus.Message + ResultHandle axleMessageHandle = invoke.invokeStaticMethod(AXLE_MESSAGE_NEW_INSTANCE, messageHandle); + invoke.invokeVirtualMethod( + MethodDescriptor.ofMethod(bean.getImplClazz().name().toString(), method.name(), void.class, + io.vertx.axle.core.eventbus.Message.class), + beanInstanceHandle, axleMessageHandle); } else { // Parameter is payload - ResultHandle payloadHandle = invoke.invokeInterfaceMethod( - MethodDescriptor.ofMethod(Message.class, "body", Object.class), invoke.getMethodParam(0)); + ResultHandle bodyHandle = invoke.invokeInterfaceMethod(MESSAGE_BODY, messageHandle); ResultHandle replyHandle = invoke.invokeVirtualMethod( MethodDescriptor.ofMethod(bean.getImplClazz().name().toString(), method.name(), method.returnType().name().toString(), paramType.name().toString()), - beanInstanceHandle, payloadHandle); + beanInstanceHandle, bodyHandle); if (replyHandle != null) { if (method.returnType().name().equals(COMPLETION_STAGE)) { // If the return type is CompletionStage use thenAccept() FunctionCreator func = invoke.createFunction(Consumer.class); BytecodeCreator funcBytecode = func.getBytecode(); funcBytecode.invokeInterfaceMethod( - MethodDescriptor.ofMethod(Message.class, "reply", void.class, Object.class), - invoke.getMethodParam(0), + MESSAGE_REPLY, + messageHandle, funcBytecode.getMethodParam(0)); funcBytecode.returnValue(null); // returnValue.thenAccept(reply -> Message.reply(reply)) @@ -255,21 +335,15 @@ private String generateInvoker(BeanInfo bean, MethodInfo method, ClassOutput cla replyHandle, func.getInstance()); } else { // Message.reply(returnValue) - invoke.invokeInterfaceMethod(MethodDescriptor.ofMethod(Message.class, "reply", void.class, Object.class), - invoke.getMethodParam(0), - replyHandle); + invoke.invokeInterfaceMethod(MESSAGE_REPLY, messageHandle, replyHandle); } } } // handle.destroy() - destroy dependent instance afterwards if (BuiltinScope.DEPENDENT.is(bean.getScope())) { - invoke.invokeInterfaceMethod(MethodDescriptor.ofMethod(InstanceHandle.class, "destroy", void.class), - instanceHandle); + invoke.invokeInterfaceMethod(INSTANCE_HANDLE_DESTROY, instanceHandle); } - invoke.returnValue(null); - - invokerCreator.close(); - return generatedName.replace('/', '.'); } + } diff --git a/extensions/vertx/deployment/src/test/java/io/quarkus/vertx/MessageConsumerFailureTest.java b/extensions/vertx/deployment/src/test/java/io/quarkus/vertx/MessageConsumerFailureTest.java new file mode 100644 index 0000000000000..bee04f4a431bb --- /dev/null +++ b/extensions/vertx/deployment/src/test/java/io/quarkus/vertx/MessageConsumerFailureTest.java @@ -0,0 +1,95 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Licensed 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.vertx; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +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.test.QuarkusUnitTest; +import io.vertx.core.eventbus.EventBus; +import io.vertx.core.eventbus.Message; +import io.vertx.core.eventbus.ReplyException; + +public class MessageConsumerFailureTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class).addClasses(SimpleBean.class)); + + @Inject + SimpleBean simpleBean; + + @Inject + EventBus eventBus; + + @Test + public void testFailure() throws InterruptedException { + verifyFailure("foo", "Foo is dead"); + verifyFailure("foo-message", null); + verifyFailure("foo-completion-stage", "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 { + synchronizer.put(ar.cause()); + } catch (InterruptedException e) { + throw new IllegalStateException(e); + } + } + }); + Object ret = synchronizer.poll(2, TimeUnit.SECONDS); + assertTrue(ret instanceof ReplyException); + ReplyException replyException = (ReplyException) ret; + assertEquals(ConsumeEvent.FAILURE_CODE, replyException.failureCode()); + assertEquals(expectedMessage, replyException.getMessage()); + } + + static class SimpleBean { + + @ConsumeEvent("foo") + String fail(String message) { + throw new IllegalStateException("Foo is dead"); + } + + @ConsumeEvent("foo-message") + void failMessage(Message message) { + throw new NullPointerException(); + } + + @ConsumeEvent("foo-completion-stage") + CompletionStage failCompletionStage(String message) { + throw new NullPointerException("Something is null"); + } + + } + +} diff --git a/extensions/vertx/deployment/src/test/java/io/quarkus/vertx/MessageConsumerMethodTest.java b/extensions/vertx/deployment/src/test/java/io/quarkus/vertx/MessageConsumerMethodTest.java index 4a35ebb5ad6f7..9e8b135a0ac69 100644 --- a/extensions/vertx/deployment/src/test/java/io/quarkus/vertx/MessageConsumerMethodTest.java +++ b/extensions/vertx/deployment/src/test/java/io/quarkus/vertx/MessageConsumerMethodTest.java @@ -38,6 +38,7 @@ import io.quarkus.arc.Arc; import io.quarkus.test.QuarkusUnitTest; +import io.vertx.core.Context; import io.vertx.core.eventbus.EventBus; import io.vertx.core.eventbus.Message; @@ -106,6 +107,7 @@ public void testSendDefaultAddress() throws InterruptedException { @Test public void testPublish() throws InterruptedException { + SimpleBean.MESSAGES.clear(); EventBus eventBus = Arc.container().instance(EventBus.class).get(); SimpleBean.latch = new CountDownLatch(2); eventBus.publish("pub", "Hello"); @@ -114,6 +116,38 @@ public void testPublish() throws InterruptedException { assertTrue(SimpleBean.MESSAGES.contains("HELLO")); } + @Test + public void testBlockingConsumer() throws InterruptedException { + SimpleBean.MESSAGES.clear(); + EventBus eventBus = Arc.container().instance(EventBus.class).get(); + SimpleBean.latch = new CountDownLatch(1); + eventBus.publish("blocking", "Hello"); + SimpleBean.latch.await(2, TimeUnit.SECONDS); + assertEquals(1, SimpleBean.MESSAGES.size()); + String message = SimpleBean.MESSAGES.get(0); + assertTrue(message.contains("hello::true")); + } + + @Test + public void testPublishRx() throws InterruptedException { + SimpleBean.MESSAGES.clear(); + EventBus eventBus = Arc.container().instance(EventBus.class).get(); + SimpleBean.latch = new CountDownLatch(1); + eventBus.publish("pub-rx", "Hello"); + SimpleBean.latch.await(2, TimeUnit.SECONDS); + assertTrue(SimpleBean.MESSAGES.contains("HELLO")); + } + + @Test + public void testPublishAxle() throws InterruptedException { + SimpleBean.MESSAGES.clear(); + EventBus eventBus = Arc.container().instance(EventBus.class).get(); + SimpleBean.latch = new CountDownLatch(1); + eventBus.publish("pub-axle", "Hello"); + SimpleBean.latch.await(2, TimeUnit.SECONDS); + assertTrue(SimpleBean.MESSAGES.contains("HELLO")); + } + static class SimpleBean { static volatile CountDownLatch latch; @@ -146,6 +180,24 @@ void consume(Message message) { CompletionStage replyAsync(String message) { return CompletableFuture.completedFuture(new StringBuilder(message).reverse().toString()); } + + @ConsumeEvent(value = "blocking", blocking = true) + void consumeBlocking(String message) { + MESSAGES.add(message.toLowerCase() + "::" + Context.isOnWorkerThread()); + latch.countDown(); + } + + @ConsumeEvent("pub-axle") + void consume(io.vertx.axle.core.eventbus.Message message) { + MESSAGES.add(message.body().toUpperCase()); + latch.countDown(); + } + + @ConsumeEvent("pub-rx") + void consume(io.vertx.reactivex.core.eventbus.Message message) { + MESSAGES.add(message.body().toUpperCase()); + latch.countDown(); + } } } diff --git a/extensions/vertx/runtime/pom.xml b/extensions/vertx/runtime/pom.xml index 018b9e621113e..3a87d31102c64 100644 --- a/extensions/vertx/runtime/pom.xml +++ b/extensions/vertx/runtime/pom.xml @@ -26,21 +26,26 @@ 4.0.0 - quarkus-vertx-runtime + quarkus-vertx Quarkus - Vert.x - Runtime io.quarkus - quarkus-core-runtime + quarkus-core + + + org.jboss.spec.javax.ws.rs + jboss-jaxrs-api_2.1_spec + provided io.quarkus - quarkus-arc-runtime + quarkus-arc io.quarkus - quarkus-netty-runtime + quarkus-netty io.smallrye.reactive @@ -75,7 +80,8 @@ - maven-dependency-plugin + io.quarkus + quarkus-bootstrap-maven-plugin maven-compiler-plugin 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 0c9dd8c1ad388..b04f08b99caa2 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 @@ -5,19 +5,18 @@ import java.lang.annotation.Retention; import java.lang.annotation.Target; -import java.util.concurrent.CompletionStage; - -import io.vertx.core.eventbus.EventBus; -import io.vertx.core.eventbus.Message; /** * Marks a business method to be automatically registered as a Vertx message consumer. *

- * The method must accept exactly one parameter. If it accepts {@link Message} then the return type must be void. For any other - * type the {@link Message#body()} - * is passed as the parameter value and the method may return an object that is passed to {@link Message#reply(Object)}, either + * 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 - * {@link CompletionStage#thenAccept(java.util.function.Consumer)} in case of the method returns a completion stage. + * {@link java.util.concurrent.CompletionStage#thenAccept(java.util.function.Consumer)} in case of the method returns a + * completion stage. * *

  * @ApplicationScoped
@@ -28,21 +27,31 @@
  *         return msg.toUpperCase();
  *     }
  * 
- *     @ConsumeEvent("echo")
+ *     @ConsumeEvent("echoMessage")
  *     void echoMessage(Message msg) {
  *         msg.reply(msg.body().toUpperCase());
  *     }
+ * 
+ *     @ConsumeEvent(value = "echoMessageBlocking", blocking = true)
+ *     void echoMessageBlocking(Message msg) {
+ *         msg.reply(msg.body().toUpperCase());
+ *     }
  * }
  * 
* - * @see EventBus + * @see io.vertx.core.eventbus.EventBus */ @Target({ METHOD }) @Retention(RUNTIME) public @interface ConsumeEvent { /** - * The address a consumer will be registered to. By default, the fully qualified name of the declaring bean class is + * Failure code used when a message consumer method throws an exception. + */ + int FAILURE_CODE = 0x1FF9; + + /** + * The address the consumer will be registered to. By default, the fully qualified name of the declaring bean class is * assumed. * * @return the address @@ -52,8 +61,15 @@ /** * * @return {@code true} if the address should not be propagated across the cluster - * @see EventBus#localConsumer(String) + * @see io.vertx.core.eventbus.EventBus#localConsumer(String) */ boolean local() default false; + /** + * + * @return {@code true} if the consumer should be invoked as a blocking operation using a worker thread + * @see io.vertx.core.Vertx#executeBlocking(io.vertx.core.Handler, boolean, io.vertx.core.Handler) + */ + boolean blocking() default false; + } diff --git a/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/JsonArrayReader.java b/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/JsonArrayReader.java new file mode 100644 index 0000000000000..60d88ab55fa73 --- /dev/null +++ b/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/JsonArrayReader.java @@ -0,0 +1,67 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Red Hat 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.vertx.runtime; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; + +import javax.ws.rs.Produces; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.NoContentException; +import javax.ws.rs.ext.MessageBodyReader; +import javax.ws.rs.ext.Provider; + +import io.vertx.core.buffer.Buffer; +import io.vertx.core.json.JsonArray; + +/** + * A body reader that allows to get a Vert.x {@link JsonArray} as JAX-RS request content. + */ +@Provider +@Produces(MediaType.APPLICATION_JSON) +public class JsonArrayReader implements MessageBodyReader { + + @Override + public boolean isReadable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { + return type == JsonArray.class; + } + + @Override + public JsonArray readFrom(Class type, Type genericType, Annotation[] annotations, MediaType mediaType, + MultivaluedMap httpHeaders, InputStream entityStream) throws IOException, WebApplicationException { + byte[] bytes = getBytes(entityStream); + if (bytes.length == 0) { + throw new NoContentException("Cannot create JsonArray"); + } + return new JsonArray(Buffer.buffer(bytes)); + } + + private static byte[] getBytes(InputStream entityStream) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + byte[] buffer = new byte[4096]; + int len; + while ((len = entityStream.read(buffer)) != -1) { + baos.write(buffer, 0, len); + } + return baos.toByteArray(); + } +} diff --git a/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/JsonArrayWriter.java b/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/JsonArrayWriter.java new file mode 100644 index 0000000000000..54cf8b54f37b6 --- /dev/null +++ b/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/JsonArrayWriter.java @@ -0,0 +1,54 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Red Hat 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.vertx.runtime; + +import java.io.IOException; +import java.io.OutputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; + +import javax.ws.rs.Produces; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.ext.MessageBodyWriter; +import javax.ws.rs.ext.Provider; + +import io.vertx.core.json.JsonArray; + +/** + * A body writer that allows to return a Vert.x {@link JsonArray} as JAX-RS response content. + * + * @author Thomas Segismont + */ +@Provider +@Produces(MediaType.APPLICATION_JSON) +public class JsonArrayWriter implements MessageBodyWriter { + + @Override + public boolean isWriteable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { + return type == JsonArray.class; + } + + @Override + public void writeTo(JsonArray jsonArray, Class type, Type genericType, Annotation[] annotations, MediaType mediaType, + MultivaluedMap httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException { + entityStream.write(jsonArray.toBuffer().getBytes()); + entityStream.flush(); + entityStream.close(); + } +} diff --git a/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/JsonObjectReader.java b/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/JsonObjectReader.java new file mode 100644 index 0000000000000..c943d6ab3e064 --- /dev/null +++ b/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/JsonObjectReader.java @@ -0,0 +1,67 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Red Hat 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.vertx.runtime; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; + +import javax.ws.rs.Produces; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.NoContentException; +import javax.ws.rs.ext.MessageBodyReader; +import javax.ws.rs.ext.Provider; + +import io.vertx.core.buffer.Buffer; +import io.vertx.core.json.JsonObject; + +/** + * A body reader that allows to get a Vert.x {@link JsonObject} as JAX-RS request content. + */ +@Provider +@Produces(MediaType.APPLICATION_JSON) +public class JsonObjectReader implements MessageBodyReader { + + @Override + public boolean isReadable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { + return type == JsonObject.class; + } + + @Override + public JsonObject readFrom(Class type, Type genericType, Annotation[] annotations, MediaType mediaType, + MultivaluedMap httpHeaders, InputStream entityStream) throws IOException, WebApplicationException { + byte[] bytes = getBytes(entityStream); + if (bytes.length == 0) { + throw new NoContentException("Cannot create JsonObject"); + } + return new JsonObject(Buffer.buffer(bytes)); + } + + private static byte[] getBytes(InputStream entityStream) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + byte[] buffer = new byte[4096]; + int len; + while ((len = entityStream.read(buffer)) != -1) { + baos.write(buffer, 0, len); + } + return baos.toByteArray(); + } +} diff --git a/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/JsonObjectWriter.java b/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/JsonObjectWriter.java new file mode 100644 index 0000000000000..69178581e64b7 --- /dev/null +++ b/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/JsonObjectWriter.java @@ -0,0 +1,54 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Red Hat 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.vertx.runtime; + +import java.io.IOException; +import java.io.OutputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; + +import javax.ws.rs.Produces; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.ext.MessageBodyWriter; +import javax.ws.rs.ext.Provider; + +import io.vertx.core.json.JsonObject; + +/** + * A body writer that allows to return a Vert.x {@link JsonObject} as JAX-RS response content. + * + * @author Thomas Segismont + */ +@Provider +@Produces(MediaType.APPLICATION_JSON) +public class JsonObjectWriter implements MessageBodyWriter { + + @Override + public boolean isWriteable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { + return type == JsonObject.class; + } + + @Override + public void writeTo(JsonObject jsonObject, Class type, Type genericType, Annotation[] annotations, MediaType mediaType, + MultivaluedMap httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException { + entityStream.write(jsonObject.toBuffer().getBytes()); + entityStream.flush(); + entityStream.close(); + } +} diff --git a/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/VertxConfiguration.java b/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/VertxConfiguration.java index 275ae689ce60c..0e8e30fc3604a 100644 --- a/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/VertxConfiguration.java +++ b/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/VertxConfiguration.java @@ -7,7 +7,7 @@ import io.quarkus.runtime.annotations.ConfigPhase; import io.quarkus.runtime.annotations.ConfigRoot; -@ConfigRoot(phase = ConfigPhase.RUN_TIME_STATIC) +@ConfigRoot(phase = ConfigPhase.RUN_TIME) public class VertxConfiguration { /** diff --git a/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/VertxProducer.java b/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/VertxProducer.java index 9c9d18dea341d..ccb40ec1b1701 100644 --- a/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/VertxProducer.java +++ b/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/VertxProducer.java @@ -1,32 +1,11 @@ package io.quarkus.vertx.runtime; -import java.lang.reflect.InvocationTargetException; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.atomic.AtomicReference; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - -import javax.annotation.PreDestroy; import javax.enterprise.context.ApplicationScoped; import javax.enterprise.inject.Produces; import javax.inject.Singleton; -import io.quarkus.vertx.ConsumeEvent; import io.vertx.core.Vertx; -import io.vertx.core.VertxOptions; import io.vertx.core.eventbus.EventBus; -import io.vertx.core.eventbus.EventBusOptions; -import io.vertx.core.eventbus.MessageConsumer; -import io.vertx.core.file.FileSystemOptions; -import io.vertx.core.http.ClientAuth; -import io.vertx.core.net.JksOptions; -import io.vertx.core.net.PemKeyCertOptions; -import io.vertx.core.net.PemTrustOptions; -import io.vertx.core.net.PfxOptions; /** * Produces a configured Vert.x instance. @@ -35,263 +14,50 @@ @ApplicationScoped public class VertxProducer { - private volatile VertxConfiguration conf; private volatile Vertx vertx; - private io.vertx.axle.core.Vertx axle; - private io.vertx.reactivex.core.Vertx rx; - - private void createCompanions(Vertx instance) { - this.vertx = instance == null ? Vertx.vertx() : instance; - this.axle = io.vertx.axle.core.Vertx.newInstance(this.vertx); - this.rx = io.vertx.reactivex.core.Vertx.newInstance(this.vertx); - } - - private void initialize() { - if (conf == null) { - createCompanions(null); - return; - } - - VertxOptions options = convertToVertxOptions(conf); - - if (!conf.useAsyncDNS) { - System.setProperty("vertx.disableDnsResolver", "true"); - } - - System.setProperty("vertx.cacheDirBase", System.getProperty("java.io.tmpdir")); - - if (options.isClustered()) { - AtomicReference failure = new AtomicReference<>(); - CountDownLatch latch = new CountDownLatch(1); - Vertx.clusteredVertx(options, ar -> { - if (ar.failed()) { - failure.set(ar.cause()); - } else { - createCompanions(ar.result()); - } - latch.countDown(); - }); - try { - latch.await(); - if (failure.get() != null) { - throw new IllegalStateException("Unable to initialize the Vert.x instance", failure.get()); - } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new IllegalStateException("Unable to initialize the Vert.x instance", e); - } - } else { - createCompanions(Vertx.vertx(options)); - } - } - - private VertxOptions convertToVertxOptions(VertxConfiguration conf) { - VertxOptions options = new VertxOptions(); - // Order matters, as the cluster options modifies the event bus options. - setEventBusOptions(options); - initializeClusterOptions(options); - - options.setFileSystemOptions(new FileSystemOptions() - .setFileCachingEnabled(conf.caching) - .setClassPathResolvingEnabled(conf.classpathResolving)); - options.setWorkerPoolSize(conf.workerPoolSize); - options.setBlockedThreadCheckInterval(conf.warningExceptionTime.toMillis()); - options.setInternalBlockingPoolSize(conf.internalBlockingPoolSize); - if (conf.eventLoopsPoolSize.isPresent()) { - options.setEventLoopPoolSize(conf.eventLoopsPoolSize.getAsInt()); - } - // TODO - Add the ability to configure these times in ns when long will be supported - // options.setMaxEventLoopExecuteTime(conf.maxEventLoopExecuteTime) - // .setMaxWorkerExecuteTime(conf.maxWorkerExecuteTime) - options.setWarningExceptionTime(conf.warningExceptionTime.toNanos()); - - return options; - } - - @PreDestroy - public void destroy() { - if (vertx != null) { - vertx.close(); - } - } - - private void initializeClusterOptions(VertxOptions options) { - ClusterConfiguration cluster = conf.cluster; - options.setClustered(cluster.clustered); - options.setClusterPingReplyInterval(cluster.pingReplyInterval.toMillis()); - options.setClusterPingInterval(cluster.pingInterval.toMillis()); - if (cluster.host != null) { - options.setClusterHost(cluster.host); - } - if (cluster.port.isPresent()) { - options.setClusterPort(cluster.port.getAsInt()); - } - cluster.publicHost.ifPresent(options::setClusterPublicHost); - if (cluster.publicPort.isPresent()) { - options.setClusterPort(cluster.publicPort.getAsInt()); - } - } - - private void setEventBusOptions(VertxOptions options) { - EventBusConfiguration eb = conf.eventbus; - EventBusOptions opts = new EventBusOptions(); - opts.setAcceptBacklog(eb.acceptBacklog.orElse(-1)); - opts.setClientAuth(ClientAuth.valueOf(eb.clientAuth.toUpperCase())); - opts.setConnectTimeout((int) (Math.min(Integer.MAX_VALUE, eb.connectTimeout.toMillis()))); - // todo: use timeUnit cleverly - opts.setIdleTimeout( - eb.idleTimeout.isPresent() ? (int) Math.max(1, Math.min(Integer.MAX_VALUE, eb.idleTimeout.get().getSeconds())) - : 0); - opts.setSendBufferSize(eb.sendBufferSize.orElse(-1)); - opts.setSoLinger(eb.soLinger.orElse(-1)); - opts.setSsl(eb.ssl); - opts.setReceiveBufferSize(eb.receiveBufferSize.orElse(-1)); - opts.setReconnectAttempts(eb.reconnectAttempts); - opts.setReconnectInterval(eb.reconnectInterval.toMillis()); - opts.setReuseAddress(eb.reuseAddress); - opts.setReusePort(eb.reusePort); - opts.setTrafficClass(eb.trafficClass.orElse(-1)); - opts.setTcpKeepAlive(eb.tcpKeepAlive); - opts.setTcpNoDelay(eb.tcpNoDelay); - opts.setTrustAll(eb.trustAll); - - // Certificates and trust. - if (eb.keyCertificatePem != null) { - List certs = new ArrayList<>(); - List keys = new ArrayList<>(); - eb.keyCertificatePem.certs.ifPresent( - s -> certs.addAll(Pattern.compile(",").splitAsStream(s).map(String::trim).collect(Collectors.toList()))); - eb.keyCertificatePem.keys.ifPresent( - s -> keys.addAll(Pattern.compile(",").splitAsStream(s).map(String::trim).collect(Collectors.toList()))); - PemKeyCertOptions o = new PemKeyCertOptions() - .setCertPaths(certs) - .setKeyPaths(keys); - opts.setPemKeyCertOptions(o); - } - - if (eb.keyCertificateJks != null) { - JksOptions o = new JksOptions(); - eb.keyCertificateJks.path.ifPresent(o::setPath); - eb.keyCertificateJks.password.ifPresent(o::setPassword); - opts.setKeyStoreOptions(o); - } - - if (eb.keyCertificatePfx != null) { - PfxOptions o = new PfxOptions(); - eb.keyCertificatePfx.path.ifPresent(o::setPath); - eb.keyCertificatePfx.password.ifPresent(o::setPassword); - opts.setPfxKeyCertOptions(o); - } + private volatile io.vertx.axle.core.Vertx axleVertx; + private volatile io.vertx.reactivex.core.Vertx rxVertx; - if (eb.trustCertificatePem != null) { - eb.trustCertificatePem.certs.ifPresent(s -> { - PemTrustOptions o = new PemTrustOptions(); - Pattern.compile(",").splitAsStream(s).map(String::trim).forEach(o::addCertPath); - opts.setPemTrustOptions(o); - }); - } - - if (eb.trustCertificateJks != null) { - JksOptions o = new JksOptions(); - eb.trustCertificateJks.path.ifPresent(o::setPath); - eb.trustCertificateJks.password.ifPresent(o::setPassword); - opts.setTrustStoreOptions(o); - } - - if (eb.trustCertificatePfx != null) { - PfxOptions o = new PfxOptions(); - eb.trustCertificatePfx.path.ifPresent(o::setPath); - eb.trustCertificatePfx.password.ifPresent(o::setPassword); - opts.setPfxTrustOptions(o); - } - options.setEventBusOptions(opts); + void initialize(Vertx vertx) { + this.vertx = vertx; + this.axleVertx = io.vertx.axle.core.Vertx.newInstance(vertx); + this.rxVertx = io.vertx.reactivex.core.Vertx.newInstance(vertx); } @Singleton @Produces - public synchronized Vertx vertx() { - if (vertx != null) { - return vertx; - } - initialize(); - return this.vertx; + public Vertx vertx() { + return vertx; } @Singleton @Produces - public synchronized io.vertx.axle.core.Vertx axle() { - if (this.axle != null) { - return this.axle; - } - initialize(); - return this.axle; + public io.vertx.axle.core.Vertx axle() { + return axleVertx; } @Singleton @Produces - public synchronized io.vertx.reactivex.core.Vertx rx() { - if (this.rx != null) { - return this.rx; - } - initialize(); - return this.rx; + public io.vertx.reactivex.core.Vertx rx() { + return rxVertx; } @Singleton @Produces - public synchronized EventBus eventbus() { - if (vertx == null) { - initialize(); - } - return this.vertx.eventBus(); + public EventBus eventbus() { + return vertx.eventBus(); } - void configure(VertxConfiguration config) { - this.conf = config; + @Singleton + @Produces + public io.vertx.axle.core.eventbus.EventBus axleEventbus() { + return axleVertx.eventBus(); } - void registerMessageConsumers(Map messageConsumerConfigurations) { - if (!messageConsumerConfigurations.isEmpty()) { - EventBus eventBus = eventbus(); - CountDownLatch latch = new CountDownLatch(messageConsumerConfigurations.size()); - for (Entry entry : messageConsumerConfigurations.entrySet()) { - EventConsumerInvoker invoker = createInvoker(entry.getKey()); - String address = entry.getValue().value(); - MessageConsumer consumer; - if (entry.getValue().local()) { - consumer = eventBus.localConsumer(address); - } else { - consumer = eventBus.consumer(address); - } - consumer.handler(m -> invoker.invoke(m)); - consumer.completionHandler(ar -> { - if (ar.succeeded()) { - latch.countDown(); - } - }); - } - try { - latch.await(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new IllegalStateException("Unable to register all message consumer methods", e); - } - } + @Singleton + @Produces + public synchronized io.vertx.reactivex.core.eventbus.EventBus rxRventbus() { + return rxVertx.eventBus(); } - @SuppressWarnings("unchecked") - private EventConsumerInvoker createInvoker(String invokerClassName) { - try { - ClassLoader cl = Thread.currentThread().getContextClassLoader(); - if (cl == null) { - cl = VertxProducer.class.getClassLoader(); - } - Class invokerClazz = (Class) cl - .loadClass(invokerClassName); - return invokerClazz.getDeclaredConstructor().newInstance(); - } catch (InstantiationException | IllegalAccessException | ClassNotFoundException | NoSuchMethodException - | InvocationTargetException e) { - throw new IllegalStateException("Unable to create invoker: " + invokerClassName, e); - } - } } diff --git a/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/VertxTemplate.java b/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/VertxTemplate.java index f5f5ab9e61bd8..e75c5bdfd4837 100644 --- a/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/VertxTemplate.java +++ b/extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/VertxTemplate.java @@ -1,18 +1,317 @@ package io.quarkus.vertx.runtime; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.List; import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicReference; +import java.util.regex.Pattern; +import java.util.stream.Collectors; import io.quarkus.arc.runtime.BeanContainer; +import io.quarkus.runtime.LaunchMode; +import io.quarkus.runtime.RuntimeValue; +import io.quarkus.runtime.ShutdownContext; import io.quarkus.runtime.annotations.Template; import io.quarkus.vertx.ConsumeEvent; +import io.vertx.core.Vertx; +import io.vertx.core.VertxOptions; +import io.vertx.core.eventbus.EventBus; +import io.vertx.core.eventbus.EventBusOptions; +import io.vertx.core.eventbus.MessageConsumer; +import io.vertx.core.file.FileSystemOptions; +import io.vertx.core.http.ClientAuth; +import io.vertx.core.net.JksOptions; +import io.vertx.core.net.PemKeyCertOptions; +import io.vertx.core.net.PemTrustOptions; +import io.vertx.core.net.PfxOptions; @Template public class VertxTemplate { - public void configureVertx(BeanContainer container, VertxConfiguration config, - Map messageConsumerConfigurations) { - VertxProducer instance = container.instance(VertxProducer.class); - instance.configure(config); - instance.registerMessageConsumers(messageConsumerConfigurations); + static volatile Vertx vertx; + static volatile List> messageConsumers; + + public RuntimeValue configureVertx(BeanContainer container, VertxConfiguration config, + Map messageConsumerConfigurations, + LaunchMode launchMode, ShutdownContext shutdown) { + + initialize(config); + registerMessageConsumers(messageConsumerConfigurations); + + VertxProducer producer = container.instance(VertxProducer.class); + producer.initialize(vertx); + if (launchMode == LaunchMode.DEVELOPMENT) { + shutdown.addShutdownTask(new Runnable() { + @Override + public void run() { + unregisterMessageConsumers(); + } + }); + } else { + shutdown.addShutdownTask(new Runnable() { + @Override + public void run() { + destroy(); + } + }); + } + return new RuntimeValue(vertx); } + + void initialize(VertxConfiguration conf) { + if (vertx != null) { + return; + } + if (conf == null) { + vertx = Vertx.vertx(); + return; + } + + VertxOptions options = convertToVertxOptions(conf); + + if (!conf.useAsyncDNS) { + System.setProperty("vertx.disableDnsResolver", "true"); + } + + System.setProperty("vertx.cacheDirBase", System.getProperty("java.io.tmpdir")); + + if (options.isClustered()) { + AtomicReference failure = new AtomicReference<>(); + CountDownLatch latch = new CountDownLatch(1); + Vertx.clusteredVertx(options, ar -> { + if (ar.failed()) { + failure.set(ar.cause()); + } else { + vertx = ar.result(); + } + latch.countDown(); + }); + try { + latch.await(); + if (failure.get() != null) { + throw new IllegalStateException("Unable to initialize the Vert.x instance", failure.get()); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new IllegalStateException("Unable to initialize the Vert.x instance", e); + } + } else { + vertx = Vertx.vertx(options); + } + + messageConsumers = new ArrayList<>(); + } + + private VertxOptions convertToVertxOptions(VertxConfiguration conf) { + VertxOptions options = new VertxOptions(); + // Order matters, as the cluster options modifies the event bus options. + setEventBusOptions(conf, options); + initializeClusterOptions(conf, options); + + options.setFileSystemOptions(new FileSystemOptions() + .setFileCachingEnabled(conf.caching) + .setClassPathResolvingEnabled(conf.classpathResolving)); + options.setWorkerPoolSize(conf.workerPoolSize); + options.setBlockedThreadCheckInterval(conf.warningExceptionTime.toMillis()); + options.setInternalBlockingPoolSize(conf.internalBlockingPoolSize); + if (conf.eventLoopsPoolSize.isPresent()) { + options.setEventLoopPoolSize(conf.eventLoopsPoolSize.getAsInt()); + } + // TODO - Add the ability to configure these times in ns when long will be supported + // options.setMaxEventLoopExecuteTime(conf.maxEventLoopExecuteTime) + // .setMaxWorkerExecuteTime(conf.maxWorkerExecuteTime) + options.setWarningExceptionTime(conf.warningExceptionTime.toNanos()); + + return options; + } + + void destroy() { + if (vertx != null) { + CountDownLatch latch = new CountDownLatch(1); + AtomicReference problem = new AtomicReference<>(); + vertx.close(ar -> { + if (ar.failed()) { + problem.set(ar.cause()); + } + latch.countDown(); + }); + try { + latch.await(); + if (problem.get() != null) { + throw new IllegalStateException("Error when closing Vertx instance", problem.get()); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new IllegalStateException("Interrupted when closing Vertx instance", e); + } + vertx = null; + messageConsumers = null; + } + } + + private void initializeClusterOptions(VertxConfiguration conf, VertxOptions options) { + ClusterConfiguration cluster = conf.cluster; + options.setClustered(cluster.clustered); + options.setClusterPingReplyInterval(cluster.pingReplyInterval.toMillis()); + options.setClusterPingInterval(cluster.pingInterval.toMillis()); + if (cluster.host != null) { + options.setClusterHost(cluster.host); + } + if (cluster.port.isPresent()) { + options.setClusterPort(cluster.port.getAsInt()); + } + cluster.publicHost.ifPresent(options::setClusterPublicHost); + if (cluster.publicPort.isPresent()) { + options.setClusterPort(cluster.publicPort.getAsInt()); + } + } + + private void setEventBusOptions(VertxConfiguration conf, VertxOptions options) { + EventBusConfiguration eb = conf.eventbus; + EventBusOptions opts = new EventBusOptions(); + opts.setAcceptBacklog(eb.acceptBacklog.orElse(-1)); + opts.setClientAuth(ClientAuth.valueOf(eb.clientAuth.toUpperCase())); + opts.setConnectTimeout((int) (Math.min(Integer.MAX_VALUE, eb.connectTimeout.toMillis()))); + // todo: use timeUnit cleverly + opts.setIdleTimeout( + eb.idleTimeout.isPresent() ? (int) Math.max(1, Math.min(Integer.MAX_VALUE, eb.idleTimeout.get().getSeconds())) + : 0); + opts.setSendBufferSize(eb.sendBufferSize.orElse(-1)); + opts.setSoLinger(eb.soLinger.orElse(-1)); + opts.setSsl(eb.ssl); + opts.setReceiveBufferSize(eb.receiveBufferSize.orElse(-1)); + opts.setReconnectAttempts(eb.reconnectAttempts); + opts.setReconnectInterval(eb.reconnectInterval.toMillis()); + opts.setReuseAddress(eb.reuseAddress); + opts.setReusePort(eb.reusePort); + opts.setTrafficClass(eb.trafficClass.orElse(-1)); + opts.setTcpKeepAlive(eb.tcpKeepAlive); + opts.setTcpNoDelay(eb.tcpNoDelay); + opts.setTrustAll(eb.trustAll); + + // Certificates and trust. + if (eb.keyCertificatePem != null) { + List certs = new ArrayList<>(); + List keys = new ArrayList<>(); + eb.keyCertificatePem.certs.ifPresent( + s -> certs.addAll(Pattern.compile(",").splitAsStream(s).map(String::trim).collect(Collectors.toList()))); + eb.keyCertificatePem.keys.ifPresent( + s -> keys.addAll(Pattern.compile(",").splitAsStream(s).map(String::trim).collect(Collectors.toList()))); + PemKeyCertOptions o = new PemKeyCertOptions() + .setCertPaths(certs) + .setKeyPaths(keys); + opts.setPemKeyCertOptions(o); + } + + if (eb.keyCertificateJks != null) { + JksOptions o = new JksOptions(); + eb.keyCertificateJks.path.ifPresent(o::setPath); + eb.keyCertificateJks.password.ifPresent(o::setPassword); + opts.setKeyStoreOptions(o); + } + + if (eb.keyCertificatePfx != null) { + PfxOptions o = new PfxOptions(); + eb.keyCertificatePfx.path.ifPresent(o::setPath); + eb.keyCertificatePfx.password.ifPresent(o::setPassword); + opts.setPfxKeyCertOptions(o); + } + + if (eb.trustCertificatePem != null) { + eb.trustCertificatePem.certs.ifPresent(s -> { + PemTrustOptions o = new PemTrustOptions(); + Pattern.compile(",").splitAsStream(s).map(String::trim).forEach(o::addCertPath); + opts.setPemTrustOptions(o); + }); + } + + if (eb.trustCertificateJks != null) { + JksOptions o = new JksOptions(); + eb.trustCertificateJks.path.ifPresent(o::setPath); + eb.trustCertificateJks.password.ifPresent(o::setPassword); + opts.setTrustStoreOptions(o); + } + + if (eb.trustCertificatePfx != null) { + PfxOptions o = new PfxOptions(); + eb.trustCertificatePfx.path.ifPresent(o::setPath); + eb.trustCertificatePfx.password.ifPresent(o::setPassword); + opts.setPfxTrustOptions(o); + } + options.setEventBusOptions(opts); + } + + void registerMessageConsumers(Map messageConsumerConfigurations) { + if (!messageConsumerConfigurations.isEmpty()) { + EventBus eventBus = vertx.eventBus(); + CountDownLatch latch = new CountDownLatch(messageConsumerConfigurations.size()); + for (Entry entry : messageConsumerConfigurations.entrySet()) { + EventConsumerInvoker invoker = createInvoker(entry.getKey()); + String address = entry.getValue().value(); + MessageConsumer consumer; + if (entry.getValue().local()) { + consumer = eventBus.localConsumer(address); + } else { + consumer = eventBus.consumer(address); + } + consumer.handler(m -> { + try { + invoker.invoke(m); + } catch (Throwable e) { + m.fail(ConsumeEvent.FAILURE_CODE, e.getMessage()); + } + }); + consumer.completionHandler(ar -> { + if (ar.succeeded()) { + latch.countDown(); + } + }); + messageConsumers.add(consumer); + } + try { + latch.await(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new IllegalStateException("Unable to register all message consumer methods", e); + } + } + } + + void unregisterMessageConsumers() { + CountDownLatch latch = new CountDownLatch(messageConsumers.size()); + for (MessageConsumer messageConsumer : messageConsumers) { + messageConsumer.unregister(ar -> { + if (ar.succeeded()) { + latch.countDown(); + } + }); + } + try { + latch.await(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new IllegalStateException("Unable to unregister all message consumer methods", e); + } + messageConsumers.clear(); + } + + @SuppressWarnings("unchecked") + private EventConsumerInvoker createInvoker(String invokerClassName) { + try { + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + if (cl == null) { + cl = VertxProducer.class.getClassLoader(); + } + Class invokerClazz = (Class) cl + .loadClass(invokerClassName); + return invokerClazz.getDeclaredConstructor().newInstance(); + } catch (InstantiationException | IllegalAccessException | ClassNotFoundException | NoSuchMethodException + | InvocationTargetException e) { + throw new IllegalStateException("Unable to create invoker: " + invokerClassName, e); + } + } + } diff --git a/extensions/vertx/runtime/src/main/resources/META-INF/services/javax.ws.rs.ext.Providers b/extensions/vertx/runtime/src/main/resources/META-INF/services/javax.ws.rs.ext.Providers new file mode 100644 index 0000000000000..644673e27c22c --- /dev/null +++ b/extensions/vertx/runtime/src/main/resources/META-INF/services/javax.ws.rs.ext.Providers @@ -0,0 +1,4 @@ +io.quarkus.vertx.runtime.JsonObjectWriter +io.quarkus.vertx.runtime.JsonArrayWriter +io.quarkus.vertx.runtime.JsonObjectReader +io.quarkus.vertx.runtime.JsonArrayReader diff --git a/extensions/vertx/runtime/src/test/java/io/quarkus/vertx/runtime/VertxProducerTest.java b/extensions/vertx/runtime/src/test/java/io/quarkus/vertx/runtime/VertxProducerTest.java index 38b755df95009..b6d9eb198865d 100644 --- a/extensions/vertx/runtime/src/test/java/io/quarkus/vertx/runtime/VertxProducerTest.java +++ b/extensions/vertx/runtime/src/test/java/io/quarkus/vertx/runtime/VertxProducerTest.java @@ -8,41 +8,60 @@ import java.util.Optional; import java.util.OptionalInt; +import org.junit.After; +import org.junit.Before; import org.junit.Test; public class VertxProducerTest { + private VertxTemplate template; + private VertxProducer producer; + + @Before + public void setUp() throws Exception { + producer = new VertxProducer(); + template = new VertxTemplate(); + } + + @After + public void tearDown() throws Exception { + template.destroy(); + } + @Test public void shouldNotFailWithoutConfig() { - VertxProducer producer = new VertxProducer(); - producer.configure(null); + template.initialize(null); + producer.initialize(VertxTemplate.vertx); + verifyProducer(); + } + private void verifyProducer() { assertThat(producer.vertx(), is(notNullValue())); assertFalse(producer.vertx().isClustered()); assertThat(producer.eventbus(), is(notNullValue())); - producer.destroy(); + assertThat(producer.axle(), is(notNullValue())); + assertFalse(producer.axle().isClustered()); + assertThat(producer.axleEventbus(), is(notNullValue())); + + assertThat(producer.rx(), is(notNullValue())); + assertFalse(producer.rx().isClustered()); + assertThat(producer.rxRventbus(), is(notNullValue())); } @Test public void shouldNotFailWithDefaultConfig() { - VertxProducer producer = new VertxProducer(); VertxConfiguration configuration = createDefaultConfiguration(); configuration.workerPoolSize = 10; configuration.warningExceptionTime = Duration.ofSeconds(1); configuration.internalBlockingPoolSize = 5; - producer.configure(configuration); - - assertThat(producer.vertx(), is(notNullValue())); - assertFalse(producer.vertx().isClustered()); - assertThat(producer.eventbus(), is(notNullValue())); - - producer.destroy(); + template.initialize(configuration); + producer.initialize(VertxTemplate.vertx); + verifyProducer(); } @Test public void shouldEnableClustering() { - VertxProducer producer = new VertxProducer(); VertxConfiguration configuration = createDefaultConfiguration(); ClusterConfiguration cc = configuration.cluster; cc.clustered = true; @@ -56,15 +75,12 @@ public void shouldEnableClustering() { configuration.eventbus.acceptBacklog = OptionalInt.empty(); configuration.cluster = cc; - producer.configure(configuration); try { - producer.vertx(); + template.initialize(configuration); fail("It should not have a cluster manager on the classpath, and so fail the creation"); } catch (IllegalStateException e) { assertTrue(e.getMessage().contains("No ClusterManagerFactory")); } - - producer.destroy(); } private VertxConfiguration createDefaultConfiguration() { diff --git a/ide-config/eclipse-format.xml b/ide-config/eclipse-format.xml index fa2bdf19cee0e..f6e4206e5f3f4 100644 --- a/ide-config/eclipse-format.xml +++ b/ide-config/eclipse-format.xml @@ -1,279 +1,322 @@ - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + - - - - - - - - - + + - + + + + + + + + + - - - - - - - - - + + + + + - - + - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - - + + - - + + + + - - - - - - - - - - - - + + + - - - - - - - - - - - + + + + + + + + + + + + + + - - + + + + + + + + + + + + + + - + + + - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + diff --git a/independent-projects/arc/ide-config/eclipse-format.xml b/independent-projects/arc/ide-config/eclipse-format.xml new file mode 100644 index 0000000000000..fa2bdf19cee0e --- /dev/null +++ b/independent-projects/arc/ide-config/eclipse-format.xml @@ -0,0 +1,279 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/independent-projects/arc/ide-config/eclipse.importorder b/independent-projects/arc/ide-config/eclipse.importorder new file mode 100644 index 0000000000000..058fb14e630c4 --- /dev/null +++ b/independent-projects/arc/ide-config/eclipse.importorder @@ -0,0 +1,6 @@ +#Organize Import Order +#Wed Jan 23 12:03:29 AEDT 2019 +0=java +1=javax +2=org +3=com diff --git a/independent-projects/arc/pom.xml b/independent-projects/arc/pom.xml index e88b836f1a6d3..b15717028cf16 100644 --- a/independent-projects/arc/pom.xml +++ b/independent-projects/arc/pom.xml @@ -1,3 +1,4 @@ + - - 4.0.0 - - - org.jboss - jboss-parent - 31 - - io.quarkus.arc - arc-parent - ArC - Parent pom - pom - 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"> + 4.0.0 - - - Apache License, Version 2.0 - repo - http://www.apache.org/licenses/LICENSE-2.0.html - - + + org.jboss + jboss-parent + 31 + + io.quarkus.arc + arc-parent + ArC - Parent pom + pom + 999-SNAPSHOT - - https://github.com/quarkusio/quarkus - scm:git:git@github.com:quarkusio/quarkus.git - scm:git:git@github.com:quarkusio/quarkus.git - HEAD - + + + Apache License, Version 2.0 + repo + http://www.apache.org/licenses/LICENSE-2.0.html + + - - UTF-8 - 1.8 - 1.8 - - 2.0.SP1 - 2.1.0.Final - 4.12 - 3.5.2 - 3.3.2.Final - 1.3.2 - 1.0.0.Alpha2 - 2.2 - + + https://github.com/quarkusio/quarkus + scm:git:git@github.com:quarkusio/quarkus.git + scm:git:git@github.com:quarkusio/quarkus.git + HEAD + - - runtime - processor - tests - + + UTF-8 + 1.8 + 1.8 + + 2.0.SP1 + 2.1.0.Final + 4.12 + 3.5.2 + 3.3.2.Final + 1.3.2 + 1.0.0.Alpha3 + 2.2 + 1.6.8 + - + + runtime + processor + tests + - + - - io.quarkus.arc - arc-runtime - ${project.version} - + - - io.quarkus.arc - arc-processor - ${project.version} - + + io.quarkus.arc + arc + ${project.version} + - - javax.enterprise - cdi-api - ${version.cdi} - + + io.quarkus.arc + arc-processor + ${project.version} + - - org.jboss - jandex - ${version.jandex} - + + javax.enterprise + cdi-api + ${version.cdi} + - - io.quarkus.gizmo - gizmo - ${version.gizmo} - + + org.jboss + jandex + ${version.jandex} + - - junit - junit - ${version.junit4} - test - + + io.quarkus.gizmo + gizmo + ${version.gizmo} + - - org.apache.maven - maven-plugin-api - ${version.maven} - provided - + + junit + junit + ${version.junit4} + test + - - org.apache.maven.plugin-tools - maven-plugin-annotations - ${version.maven} - provided - + + org.apache.maven + maven-plugin-api + ${version.maven} + provided + - - org.apache.maven - maven-core - ${version.maven} - provided - + + org.apache.maven.plugin-tools + maven-plugin-annotations + ${version.maven} + provided + - - org.jboss.logging - jboss-logging - ${version.jboss-logging} - + + org.apache.maven + maven-core + ${version.maven} + provided + - - javax.annotation - javax.annotation-api - ${version.javax-annotation} - - + + org.jboss.logging + jboss-logging + ${version.jboss-logging} + - - javax.persistence - javax.persistence-api - ${version.jpa} - test - + + javax.annotation + javax.annotation-api + ${version.javax-annotation} + + - + + javax.persistence + javax.persistence-api + ${version.jpa} + test + - + - - - - - maven-javadoc-plugin - - true - none - - - - org.apache.maven.plugins - maven-enforcer-plugin - - - enforce - - - - - - org.jboss.spec.javax.annotation:jboss-annotations-api_1.2_spec - org.jboss.spec.javax.annotation:jboss-annotations-api_1.3_spec - - - - - - enforce - - - - - - - + - - - jboss - http://repository.jboss.org/nexus/content/groups/public/ - - true - - - true - - - + + + + + maven-javadoc-plugin + + true + none + + + + org.apache.maven.plugins + maven-enforcer-plugin + + + enforce + + + + + + org.jboss.spec.javax.annotation:jboss-annotations-api_1.2_spec + org.jboss.spec.javax.annotation:jboss-annotations-api_1.3_spec + + + + + + 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 + + + + - - - sonatype-nexus-snapshots - https://oss.sonatype.org/content/repositories/snapshots - - - sonatype-nexus-releases - https://oss.sonatype.org/service/local/staging/deploy/maven2/ - - + + + 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 - 1.6.3 - true - - https://oss.sonatype.org/ - ossrh - 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 - - - - - - - - + + + release + + + + org.sonatype.plugins + nexus-staging-maven-plugin + ${nexus-staging-maven-plugin.version} + true + + https://oss.sonatype.org/ + ossrh + true + 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 + + + + + + + + + validate + + true + + no-format + + + + + + net.revelc.code.formatter + formatter-maven-plugin + + + process-sources + + validate + + + + + + net.revelc.code + impsort-maven-plugin + + + check-imports + + check + + + + + + + + diff --git a/independent-projects/arc/processor/pom.xml b/independent-projects/arc/processor/pom.xml index 8103f284b03cf..698eda8656ae8 100644 --- a/independent-projects/arc/processor/pom.xml +++ b/independent-projects/arc/processor/pom.xml @@ -1,3 +1,4 @@ + - - 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"> + 4.0.0 - - io.quarkus.arc - arc-parent - 999-SNAPSHOT - ../ - + + io.quarkus.arc + arc-parent + 999-SNAPSHOT + ../ + - arc-processor - ArC - Processor + arc-processor + ArC - Processor - + - - io.quarkus.arc - arc-runtime - + + io.quarkus.arc + arc + - - javax.enterprise - cdi-api - + + javax.enterprise + cdi-api + - - org.jboss.logging - jboss-logging - + + org.jboss.logging + jboss-logging + - - org.jboss - jandex - + + org.jboss + jandex + - - io.quarkus.gizmo - gizmo - + + io.quarkus.gizmo + gizmo + - - javax.annotation - javax.annotation-api - + + javax.annotation + javax.annotation-api + - - junit - junit - + + junit + junit + - + diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AbstractGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AbstractGenerator.java index 897f18c902b5f..3179ddd2089dc 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AbstractGenerator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AbstractGenerator.java @@ -16,16 +16,44 @@ package io.quarkus.arc.processor; +import io.quarkus.arc.Arc; import java.lang.reflect.Modifier; - +import org.jboss.jandex.DotName; import org.jboss.jandex.FieldInfo; import org.jboss.jandex.MethodInfo; -import io.quarkus.arc.Arc; +import org.jboss.jandex.Type.Kind; abstract class AbstractGenerator { static final String DEFAULT_PACKAGE = Arc.class.getPackage().getName() + ".generator"; + /** + * Create a generated bean name from a bean package. When bean is located + * in a default package (i.e. a classpath root), the target package name + * is empty string. This need to be taken into account when creating + * generated bean name because it is later used to build class file path + * and we do not want it to start from a slash because it will point root + * directory instead of a relative one. This method will address this + * problem.
+ *
+ * Example generated bean names (without quotes): + *
    + *
  1. a "io/quarcus/foo/FooService_Bean", when in io.quarcus.foo package,
  2. + *
  3. a "BarService_Bean", when in default package.
  4. + *
+ * + * @param baseName a bean name (class name) + * @param targetPackage a package where bean is located + * @return Generated name + */ + static String generatedNameFromTarget(String targetPackage, String baseName, String suffix) { + if (targetPackage == null || targetPackage.isEmpty()) { + return baseName + suffix; + } else { + return targetPackage.replace('.', '/') + "/" + baseName + suffix; + } + } + protected String getBaseName(BeanInfo bean, String beanClassName) { String name = Types.getSimpleName(beanClassName); return name.substring(0, name.lastIndexOf(BeanGenerator.BEAN_SUFFIX)); @@ -58,12 +86,17 @@ protected boolean isPackagePrivate(int mod) { } protected String getPackageName(BeanInfo bean) { - String packageName; + DotName providerTypeName; if (bean.isProducerMethod() || bean.isProducerField()) { - packageName = DotNames.packageName(bean.getDeclaringBean().getProviderType().name()); + providerTypeName = bean.getDeclaringBean().getProviderType().name(); } else { - packageName = DotNames.packageName(bean.getProviderType().name()); + if (bean.getProviderType().kind() == Kind.ARRAY || bean.getProviderType().kind() == Kind.PRIMITIVE) { + providerTypeName = bean.getImplClazz().name(); + } else { + providerTypeName = bean.getProviderType().name(); + } } + String packageName = DotNames.packageName(providerTypeName); if (packageName.startsWith("java.")) { // It is not possible to place a class in a JDK package packageName = DEFAULT_PACKAGE; @@ -71,5 +104,4 @@ protected String getPackageName(BeanInfo bean) { return packageName; } - } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AnnotationLiteralGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AnnotationLiteralGenerator.java index 957e16816e7f5..4c8fd622a36e3 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AnnotationLiteralGenerator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AnnotationLiteralGenerator.java @@ -20,6 +20,17 @@ import static org.objectweb.asm.Opcodes.ACC_PRIVATE; import static org.objectweb.asm.Opcodes.ACC_PUBLIC; +import io.quarkus.arc.ComputingCache; +import io.quarkus.arc.processor.AnnotationLiteralProcessor.Key; +import io.quarkus.arc.processor.AnnotationLiteralProcessor.Literal; +import io.quarkus.arc.processor.ResourceOutput.Resource; +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 java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -27,9 +38,7 @@ import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; - import javax.enterprise.util.AnnotationLiteral; - import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationTarget.Kind; import org.jboss.jandex.AnnotationValue; @@ -37,20 +46,8 @@ import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; import org.jboss.jandex.MethodInfo; -import org.jboss.jandex.PrimitiveType; import org.jboss.jandex.Type; import org.jboss.logging.Logger; -import io.quarkus.arc.ComputingCache; -import io.quarkus.arc.processor.AnnotationLiteralProcessor.Key; -import io.quarkus.arc.processor.AnnotationLiteralProcessor.Literal; -import io.quarkus.arc.processor.ResourceOutput.Resource; -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; /** * @@ -70,7 +67,8 @@ public class AnnotationLiteralGenerator extends AbstractGenerator { * @param annotationLiterals * @return a collection of resources */ - Collection generate(String name, BeanDeployment beanDeployment, ComputingCache annotationLiteralsCache) { + Collection generate(String name, BeanDeployment beanDeployment, + ComputingCache annotationLiteralsCache) { List resources = new ArrayList<>(); annotationLiteralsCache.forEachEntry((key, literal) -> { ResourceClassOutput classOutput = new ResourceClassOutput(literal.isApplicationClass); @@ -79,13 +77,15 @@ Collection generate(String name, BeanDeployment beanDeployment, Comput }); return resources; } - + static void createSharedAnnotationLiteral(ClassOutput classOutput, Key key, Literal literal) { // Ljavax/enterprise/util/AnnotationLiteral;Lcom/foo/MyQualifier; - String signature = String.format("Ljavax/enterprise/util/AnnotationLiteral;L%1$s;", key.annotationName.toString().replace('.', '/')); + String signature = String.format("Ljavax/enterprise/util/AnnotationLiteral;L%1$s;", + key.annotationName.toString().replace('.', '/')); String generatedName = literal.className.replace('.', '/'); - ClassCreator annotationLiteral = ClassCreator.builder().classOutput(classOutput).className(generatedName).superClass(AnnotationLiteral.class) + ClassCreator annotationLiteral = ClassCreator.builder().classOutput(classOutput).className(generatedName) + .superClass(AnnotationLiteral.class) .interfaces(key.annotationName.toString()).signature(signature).build(); MethodCreator constructor = annotationLiteral.getMethodCreator(Methods.INIT, "V", @@ -98,35 +98,38 @@ static void createSharedAnnotationLiteral(ClassOutput classOutput, Key key, Lite // field annotationLiteral.getFieldCreator(param.name(), returnType).setModifiers(ACC_PRIVATE | ACC_FINAL); // constructor param - constructor.writeInstanceField(FieldDescriptor.of(annotationLiteral.getClassName(), param.name(), returnType), constructor.getThis(), + constructor.writeInstanceField(FieldDescriptor.of(annotationLiteral.getClassName(), param.name(), returnType), + constructor.getThis(), constructor.getMethodParam(iterator.previousIndex())); // value method MethodCreator value = annotationLiteral.getMethodCreator(param.name(), returnType).setModifiers(ACC_PUBLIC); - value.returnValue(value.readInstanceField(FieldDescriptor.of(annotationLiteral.getClassName(), param.name(), returnType), value.getThis())); + value.returnValue(value.readInstanceField( + FieldDescriptor.of(annotationLiteral.getClassName(), param.name(), returnType), value.getThis())); } constructor.returnValue(null); - + annotationLiteral.close(); LOGGER.debugf("Shared annotation literal generated: %s", literal.className); } - static void createAnnotationLiteral(ClassOutput classOutput, ClassInfo annotationClass, AnnotationInstance annotationInstance, String literalName) { - createAnnotationLiteral(classOutput, annotationClass, annotationInstance.values(), literalName); - } + static void createAnnotationLiteral(ClassOutput classOutput, ClassInfo annotationClass, + AnnotationInstance annotationInstance, + String literalName) { - static void createAnnotationLiteral(ClassOutput classOutput, ClassInfo annotationClass, List values, String literalName) { - - Map annotationValues = values.stream().collect(Collectors.toMap(AnnotationValue::name, Function.identity())); + Map annotationValues = annotationInstance.values().stream() + .collect(Collectors.toMap(AnnotationValue::name, Function.identity())); // Ljavax/enterprise/util/AnnotationLiteral;Lcom/foo/MyQualifier; - String signature = String.format("Ljavax/enterprise/util/AnnotationLiteral;L%1$s;", annotationClass.name().toString().replace('.', '/')); + String signature = String.format("Ljavax/enterprise/util/AnnotationLiteral;L%1$s;", + annotationClass.name().toString().replace('.', '/')); String generatedName = literalName.replace('.', '/'); - ClassCreator annotationLiteral = ClassCreator.builder().classOutput(classOutput).className(generatedName).superClass(AnnotationLiteral.class) + ClassCreator annotationLiteral = ClassCreator.builder().classOutput(classOutput).className(generatedName) + .superClass(AnnotationLiteral.class) .interfaces(annotationClass.name().toString()).signature(signature).build(); for (MethodInfo method : annotationClass.methods()) { - if(method.name().equals(Methods.CLINIT) || method.name().equals(Methods.INIT)) { + if (method.name().equals(Methods.CLINIT) || method.name().equals(Methods.INIT)) { continue; } MethodCreator valueMethod = annotationLiteral.getMethodCreator(MethodDescriptor.of(method)); @@ -134,51 +137,19 @@ static void createAnnotationLiteral(ClassOutput classOutput, ClassInfo annotatio if (value == null) { value = method.defaultValue(); } - ResultHandle retValue = null; if (value == null) { - switch (method.returnType().kind()) { - case CLASS: - case ARRAY: - retValue = valueMethod.loadNull(); - break; - case PRIMITIVE: - PrimitiveType primitiveType = method.returnType().asPrimitiveType(); - switch (primitiveType.primitive()) { - case BOOLEAN: - retValue = valueMethod.load(false); - break; - case BYTE: - case SHORT: - case INT: - retValue = valueMethod.load(0); - break; - case LONG: - retValue = valueMethod.load(0L); - break; - case FLOAT: - retValue = valueMethod.load(0.0f); - break; - case DOUBLE: - retValue = valueMethod.load(0.0d); - break; - case CHAR: - retValue = valueMethod.load('\u0000'); - break; - } - break; - default: - break; - } - } else { - retValue = loadValue(valueMethod, value, annotationClass, method); + throw new IllegalStateException(String.format( + "Value is not set for %s.%s(). Most probably an older version of Jandex was used to index an application dependency. Make sure that Jandex 2.1+ is used.", + method.declaringClass().name(), method.name())); } - valueMethod.returnValue(retValue); + valueMethod.returnValue(loadValue(valueMethod, value, annotationClass, method)); } annotationLiteral.close(); LOGGER.debugf("Annotation literal generated: %s", literalName); } - - static ResultHandle loadValue(BytecodeCreator valueMethod, AnnotationValue value, ClassInfo annotationClass, MethodInfo method) { + + static ResultHandle loadValue(BytecodeCreator valueMethod, AnnotationValue value, ClassInfo annotationClass, + MethodInfo method) { ResultHandle retValue; switch (value.kind()) { case BOOLEAN: @@ -216,7 +187,8 @@ static ResultHandle loadValue(BytecodeCreator valueMethod, AnnotationValue value break; case ENUM: retValue = valueMethod - .readStaticField(FieldDescriptor.of(value.asEnumType().toString(), value.asEnum(), value.asEnumType().toString())); + .readStaticField(FieldDescriptor.of(value.asEnumType().toString(), value.asEnum(), + value.asEnumType().toString())); break; case NESTED: default: @@ -225,7 +197,8 @@ static ResultHandle loadValue(BytecodeCreator valueMethod, AnnotationValue value return retValue; } - static ResultHandle arrayValue(AnnotationValue value, BytecodeCreator valueMethod, MethodInfo method, ClassInfo annotationClass) { + static ResultHandle arrayValue(AnnotationValue value, BytecodeCreator valueMethod, MethodInfo method, + ClassInfo annotationClass) { ResultHandle retValue; switch (value.componentKind()) { case CLASS: @@ -280,7 +253,8 @@ static ResultHandle arrayValue(AnnotationValue value, BytecodeCreator valueMetho AnnotationInstance nonbinding = method.annotation(DotNames.NONBINDING); if (nonbinding == null || nonbinding.target() .kind() != Kind.METHOD) { - LOGGER.warnf("Unsupported array component type %s on %s - literal returns an empty array", method, annotationClass); + LOGGER.warnf("Unsupported array component type %s on %s - literal returns an empty array", method, + annotationClass); } } retValue = valueMethod.newArray(componentType(method), valueMethod.load(0)); diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AnnotationLiteralProcessor.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AnnotationLiteralProcessor.java index 4c9cbfacff451..8c6d0436b8803 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AnnotationLiteralProcessor.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AnnotationLiteralProcessor.java @@ -16,6 +16,11 @@ package io.quarkus.arc.processor; +import io.quarkus.arc.ComputingCache; +import io.quarkus.gizmo.BytecodeCreator; +import io.quarkus.gizmo.ClassOutput; +import io.quarkus.gizmo.MethodDescriptor; +import io.quarkus.gizmo.ResultHandle; import java.util.List; import java.util.ListIterator; import java.util.Map; @@ -23,17 +28,11 @@ import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; - import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationValue; import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; import org.jboss.jandex.MethodInfo; -import io.quarkus.arc.ComputingCache; -import io.quarkus.gizmo.BytecodeCreator; -import io.quarkus.gizmo.ClassOutput; -import io.quarkus.gizmo.MethodDescriptor; -import io.quarkus.gizmo.ResultHandle; /** * @@ -45,11 +44,12 @@ class AnnotationLiteralProcessor { AnnotationLiteralProcessor(boolean shared, Predicate applicationClassPredicate) { this.cache = shared ? new ComputingCache<>(key -> { - return new Literal(AnnotationLiteralGenerator.generatedSharedName(key.annotationName), applicationClassPredicate.test(key.annotationName), + return new Literal(AnnotationLiteralGenerator.generatedSharedName(key.annotationName), + applicationClassPredicate.test(key.annotationName), key.annotationClass.methods() - .stream() - .filter(m -> !m.name().equals(Methods.CLINIT) && !m.name().equals(Methods.INIT)) - .collect(Collectors.toList())); + .stream() + .filter(m -> !m.name().equals(Methods.CLINIT) && !m.name().equals(Methods.INIT)) + .collect(Collectors.toList())); }) : null; } @@ -70,7 +70,8 @@ ComputingCache getCache() { * @param targetPackage Target package is only used if annotation literals are not shared * @return an annotation literal result handle */ - ResultHandle process(BytecodeCreator bytecode, ClassOutput classOutput, ClassInfo annotationClass, AnnotationInstance annotationInstance, + ResultHandle process(BytecodeCreator bytecode, ClassOutput classOutput, ClassInfo annotationClass, + AnnotationInstance annotationInstance, String targetPackage) { Objects.requireNonNull(annotationClass, "Annotation class not available: " + annotationInstance); if (cache != null) { @@ -88,18 +89,23 @@ ResultHandle process(BytecodeCreator bytecode, ClassOutput classOutput, ClassInf value = method.defaultValue(); } if (value == null) { - throw new NullPointerException("Value not set for " + method); + throw new IllegalStateException(String.format( + "Value is not set for %s.%s(). Most probably an older version of Jandex was used to index an application dependency. Make sure that Jandex 2.1+ is used.", + method.declaringClass().name(), method.name())); } ResultHandle retValue = AnnotationLiteralGenerator.loadValue(bytecode, value, annotationClass, method); constructorParams[iterator.previousIndex()] = retValue; } return bytecode .newInstance(MethodDescriptor.ofConstructor(literal.className, - literal.constructorParams.stream().map(m -> m.returnType().name().toString()).toArray()), constructorParams); + literal.constructorParams.stream().map(m -> m.returnType().name().toString()).toArray()), + constructorParams); } else { - String literalClassName = AnnotationLiteralGenerator.generatedLocalName(targetPackage, DotNames.simpleName(annotationClass), + String literalClassName = AnnotationLiteralGenerator.generatedLocalName(targetPackage, + DotNames.simpleName(annotationClass), Hashes.sha1(annotationInstance.toString())); - AnnotationLiteralGenerator.createAnnotationLiteral(classOutput, annotationClass, annotationInstance, literalClassName); + AnnotationLiteralGenerator.createAnnotationLiteral(classOutput, annotationClass, annotationInstance, + literalClassName); return bytecode.newInstance(MethodDescriptor.ofConstructor(literalClassName)); } } @@ -109,7 +115,7 @@ static class Literal { final String className; final boolean isApplicationClass; - + final List constructorParams; public Literal(String className, boolean isApplicationClass, List constructorParams) { @@ -123,7 +129,7 @@ public Literal(String className, boolean isApplicationClass, List co static class Key { final DotName annotationName; - + final ClassInfo annotationClass; public Key(DotName name, ClassInfo annotationClass) { 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 27eb3939b85c0..7d4d410b11bd2 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 @@ -16,6 +16,9 @@ package io.quarkus.arc.processor; +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; @@ -23,14 +26,10 @@ import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; - import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationTarget; import org.jboss.jandex.AnnotationTarget.Kind; import org.jboss.jandex.DotName; -import io.quarkus.arc.processor.AnnotationsTransformer.TransformationContext; -import io.quarkus.arc.processor.BuildExtension.BuildContext; -import io.quarkus.arc.processor.BuildExtension.Key; /** * Applies {@link AnnotationsTransformer}s and caches the results of transformations. @@ -43,7 +42,7 @@ public class AnnotationStore { private final ConcurrentMap> transformed; private final EnumMap> transformersMap; - + private final BuildContext buildContext; AnnotationStore(Collection transformers, BuildContext buildContext) { @@ -118,7 +117,7 @@ private Collection transform(AnnotationTarget target) { } return transformationContext.getAnnotations(); } - + private Collection getOriginalAnnotations(AnnotationTarget target) { switch (target.kind()) { case CLASS: @@ -148,11 +147,11 @@ private List initTransformers(Kind kind, Collection annotations; - + TransformationContextImpl(AnnotationTarget target, Collection annotations) { this.target = target; this.annotations = annotations; @@ -177,7 +176,7 @@ public AnnotationTarget getTarget() { public Collection getAnnotations() { return annotations; } - + void setAnnotations(Collection annotations) { this.annotations = annotations; } @@ -186,7 +185,7 @@ void setAnnotations(Collection annotations) { public Transformation transform() { return new Transformation(this); } - + } } 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 74695212a847b..c9238a28372f9 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 @@ -17,9 +17,12 @@ package io.quarkus.arc.processor; import java.util.Collection; - +import java.util.HashSet; +import java.util.Set; import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationTarget.Kind; import org.jboss.jandex.DotName; +import org.jboss.jandex.MethodInfo; public final class Annotations { @@ -64,4 +67,15 @@ static boolean containsAny(Collection annotations, Iterable< return false; } + 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() + && annotation.target().asMethodParameter().position() == position) { + annotations.add(annotation); + } + } + return annotations; + } + } 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 dbef0885a378f..7f354097a4a02 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 @@ -17,7 +17,6 @@ package io.quarkus.arc.processor; import java.util.Collection; - import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationTarget; import org.jboss.jandex.AnnotationTarget.Kind; @@ -25,7 +24,8 @@ /** * 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 + * 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}. * * @author Martin Kouba diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanArchives.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanArchives.java new file mode 100644 index 0000000000000..f61c4e095c7c8 --- /dev/null +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanArchives.java @@ -0,0 +1,225 @@ +package io.quarkus.arc.processor; + +import io.quarkus.arc.ActivateRequestContextInterceptor; +import io.quarkus.arc.InjectableRequestContextController; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import javax.enterprise.context.BeforeDestroyed; +import javax.enterprise.context.Destroyed; +import javax.enterprise.context.Initialized; +import javax.enterprise.context.control.ActivateRequestContext; +import javax.enterprise.inject.Any; +import javax.enterprise.inject.Default; +import javax.inject.Named; +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.CompositeIndex; +import org.jboss.jandex.DotName; +import org.jboss.jandex.Index; +import org.jboss.jandex.IndexView; +import org.jboss.jandex.Indexer; +import org.jboss.jandex.Type; + +public final class BeanArchives { + + /** + * + * @param applicationIndexes + * @return the final bean archive index + */ + public static IndexView buildBeanArchiveIndex(IndexView... applicationIndexes) { + List indexes = new ArrayList<>(); + Collections.addAll(indexes, applicationIndexes); + indexes.add(buildCdiApiIndex()); + indexes.add(buildBuiltinIndex()); + return new IndexWrapper(CompositeIndex.create(indexes)); + } + + private static IndexView buildCdiApiIndex() { + Indexer indexer = new Indexer(); + index(indexer, ActivateRequestContext.class.getName()); + index(indexer, Default.class.getName()); + index(indexer, Any.class.getName()); + index(indexer, Named.class.getName()); + index(indexer, Initialized.class.getName()); + index(indexer, BeforeDestroyed.class.getName()); + index(indexer, Destroyed.class.getName()); + return indexer.complete(); + } + + private static IndexView buildBuiltinIndex() { + Indexer indexer = new Indexer(); + index(indexer, ActivateRequestContextInterceptor.class.getName()); + index(indexer, InjectableRequestContextController.class.getName()); + return indexer.complete(); + } + + /** + * This wrapper is used to index JDK classes on demand. + */ + static class IndexWrapper implements IndexView { + + private final Map additionalClasses; + + private final IndexView index; + + public IndexWrapper(IndexView index) { + this.index = index; + this.additionalClasses = new ConcurrentHashMap<>(); + } + + @Override + public Collection getKnownClasses() { + return index.getKnownClasses(); + } + + @Override + public ClassInfo getClassByName(DotName className) { + ClassInfo classInfo = index.getClassByName(className); + if (classInfo == null) { + return additionalClasses.computeIfAbsent(className, name -> { + BeanProcessor.LOGGER.debugf("Index: %s", className); + Indexer indexer = new Indexer(); + BeanArchives.index(indexer, className.toString()); + Index index = indexer.complete(); + return index.getClassByName(name); + }); + } + return classInfo; + } + + @Override + public Collection getKnownDirectSubclasses(DotName className) { + if (additionalClasses.isEmpty()) { + return index.getKnownDirectSubclasses(className); + } + Set directSubclasses = new HashSet(index.getKnownDirectSubclasses(className)); + for (ClassInfo additional : additionalClasses.values()) { + if (className.equals(additional.superName())) { + directSubclasses.add(additional); + } + } + return directSubclasses; + } + + @Override + public Collection getAllKnownSubclasses(DotName className) { + if (additionalClasses.isEmpty()) { + return index.getAllKnownSubclasses(className); + } + final Set allKnown = new HashSet(); + final Set processedClasses = new HashSet(); + getAllKnownSubClasses(className, allKnown, processedClasses); + return allKnown; + } + + @Override + public Collection getKnownDirectImplementors(DotName className) { + if (additionalClasses.isEmpty()) { + return index.getKnownDirectImplementors(className); + } + Set directImplementors = new HashSet(index.getKnownDirectImplementors(className)); + for (ClassInfo additional : additionalClasses.values()) { + for (Type interfaceType : additional.interfaceTypes()) { + if (className.equals(interfaceType.name())) { + directImplementors.add(additional); + break; + } + } + } + return directImplementors; + } + + @Override + public Collection getAllKnownImplementors(DotName interfaceName) { + if (additionalClasses.isEmpty()) { + return index.getAllKnownImplementors(interfaceName); + } + final Set allKnown = new HashSet(); + final Set subInterfacesToProcess = new HashSet(); + final Set processedClasses = new HashSet(); + subInterfacesToProcess.add(interfaceName); + while (!subInterfacesToProcess.isEmpty()) { + final Iterator toProcess = subInterfacesToProcess.iterator(); + DotName name = toProcess.next(); + toProcess.remove(); + processedClasses.add(name); + getKnownImplementors(name, allKnown, subInterfacesToProcess, processedClasses); + } + return allKnown; + } + + @Override + public Collection getAnnotations(DotName annotationName) { + return index.getAnnotations(annotationName); + } + + private void getAllKnownSubClasses(DotName className, Set allKnown, Set processedClasses) { + final Set subClassesToProcess = new HashSet(); + subClassesToProcess.add(className); + while (!subClassesToProcess.isEmpty()) { + final Iterator toProcess = subClassesToProcess.iterator(); + DotName name = toProcess.next(); + toProcess.remove(); + processedClasses.add(name); + getAllKnownSubClasses(name, allKnown, subClassesToProcess, processedClasses); + } + } + + private void getAllKnownSubClasses(DotName name, Set allKnown, Set subClassesToProcess, + Set processedClasses) { + final Collection directSubclasses = getKnownDirectSubclasses(name); + if (directSubclasses != null) { + for (final ClassInfo clazz : directSubclasses) { + final DotName className = clazz.name(); + if (!processedClasses.contains(className)) { + allKnown.add(clazz); + subClassesToProcess.add(className); + } + } + } + } + + private void getKnownImplementors(DotName name, Set allKnown, Set subInterfacesToProcess, + Set processedClasses) { + final Collection list = getKnownDirectImplementors(name); + if (list != null) { + for (final ClassInfo clazz : list) { + final DotName className = clazz.name(); + if (!processedClasses.contains(className)) { + if (Modifier.isInterface(clazz.flags())) { + subInterfacesToProcess.add(className); + } else { + if (!allKnown.contains(clazz)) { + allKnown.add(clazz); + processedClasses.add(className); + getAllKnownSubClasses(className, allKnown, processedClasses); + } + } + } + } + } + } + + } + + static void index(Indexer indexer, String className) { + try (InputStream stream = BeanProcessor.class.getClassLoader() + .getResourceAsStream(className.replace('.', '/') + ".class")) { + indexer.index(stream); + } catch (IOException e) { + throw new IllegalStateException("Failed to index: " + className, e); + } + } + +} 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 3e6fec5027634..9856936545aa4 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 @@ -16,6 +16,12 @@ package io.quarkus.arc.processor; +import io.quarkus.arc.BeanCreator; +import io.quarkus.arc.BeanDestroyer; +import io.quarkus.gizmo.FieldDescriptor; +import io.quarkus.gizmo.MethodCreator; +import io.quarkus.gizmo.MethodDescriptor; +import io.quarkus.gizmo.ResultHandle; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -23,21 +29,13 @@ import java.util.Objects; import java.util.Set; import java.util.function.Consumer; - import javax.enterprise.context.spi.CreationalContext; - import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationValue; import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; import org.jboss.jandex.Type; import org.jboss.jandex.Type.Kind; -import io.quarkus.arc.BeanCreator; -import io.quarkus.arc.BeanDestroyer; -import io.quarkus.gizmo.FieldDescriptor; -import io.quarkus.gizmo.MethodCreator; -import io.quarkus.gizmo.MethodDescriptor; -import io.quarkus.gizmo.ResultHandle; /** * Synthetic bean configurator. An alternative to {@link javax.enterprise.inject.spi.configurator.BeanConfigurator}. @@ -54,6 +52,8 @@ public final class BeanConfigurator { private final ClassInfo implClass; + private Type providerType; + private final Set types; private final Set qualifiers; @@ -61,7 +61,7 @@ public final class BeanConfigurator { private ScopeInfo scope; private Integer alternativePriority; - + private String name; private Consumer creatorConsumer; @@ -117,8 +117,6 @@ public BeanConfigurator param(String name, boolean value) { return this; } - // TODO other supported param types - public BeanConfigurator types(Class... types) { for (Class type : types) { this.types.add(Type.create(DotName.createSimple(type.getName()), Kind.CLASS)); @@ -130,12 +128,12 @@ public BeanConfigurator types(Type... types) { Collections.addAll(this.types, types); return this; } - + public BeanConfigurator addType(DotName className) { this.types.add(Type.create(className, Kind.CLASS)); return this; } - + public BeanConfigurator addQualifier(DotName annotationName) { this.qualifiers.add(AnnotationInstance.create(annotationName, null, new AnnotationValue[] {})); return this; @@ -150,7 +148,7 @@ public BeanConfigurator scope(ScopeInfo scope) { this.scope = scope; return this; } - + public BeanConfigurator name(String name) { this.name = name; return this; @@ -161,16 +159,22 @@ public BeanConfigurator alternativePriority(int priority) { return this; } + public BeanConfigurator providerType(Type providerType) { + this.providerType = providerType; + return this; + } + public BeanConfigurator creator(Class> creatorClazz) { return creator(mc -> { // return new FooBeanCreator().create(context, params) - // TODO verify, optimize, etc. - ResultHandle paramsHandle = mc.readInstanceField(FieldDescriptor.of(mc.getMethodDescriptor().getDeclaringClass(), "params", Map.class), + ResultHandle paramsHandle = mc.readInstanceField( + FieldDescriptor.of(mc.getMethodDescriptor().getDeclaringClass(), "params", Map.class), mc.getThis()); ResultHandle creatorHandle = mc.newInstance(MethodDescriptor.ofConstructor(creatorClazz)); ResultHandle[] params = { mc.getMethodParam(0), paramsHandle }; ResultHandle ret = mc.invokeInterfaceMethod( - MethodDescriptor.ofMethod(BeanCreator.class, "create", Object.class, CreationalContext.class, Map.class), creatorHandle, params); + MethodDescriptor.ofMethod(BeanCreator.class, "create", Object.class, CreationalContext.class, Map.class), + creatorHandle, params); mc.returnValue(ret); }); } @@ -183,12 +187,14 @@ public BeanConfigurator creator(Consumer methodC public BeanConfigurator destroyer(Class> destroyerClazz) { return destroyer(mc -> { // new FooBeanDestroyer().destroy(instance, context, params) - // TODO verify, optimize, etc. - ResultHandle paramsHandle = mc.readInstanceField(FieldDescriptor.of(mc.getMethodDescriptor().getDeclaringClass(), "params", Map.class), + ResultHandle paramsHandle = mc.readInstanceField( + FieldDescriptor.of(mc.getMethodDescriptor().getDeclaringClass(), "params", Map.class), mc.getThis()); ResultHandle destoyerHandle = mc.newInstance(MethodDescriptor.ofConstructor(destroyerClazz)); ResultHandle[] params = { mc.getMethodParam(0), mc.getMethodParam(1), paramsHandle }; - mc.invokeInterfaceMethod(MethodDescriptor.ofMethod(BeanDestroyer.class, "destroy", Void.class, Object.class, CreationalContext.class, Map.class), + mc.invokeInterfaceMethod( + MethodDescriptor.ofMethod(BeanDestroyer.class, "destroy", Void.class, Object.class, CreationalContext.class, + Map.class), destoyerHandle, params); mc.returnValue(null); }); @@ -204,8 +210,11 @@ public BeanConfigurator destroyer(Consumer metho */ public void done() { // TODO sanity checks - beanConsumer.accept(new BeanInfo.Builder().implClazz(implClass).beanDeployment(beanDeployment).scope(scope).types(types).qualifiers(qualifiers) - .alternativePriority(alternativePriority).name(name).creator(creatorConsumer).destroyer(destroyerConsumer).params(params).build()); + beanConsumer.accept(new BeanInfo.Builder().implClazz(implClass).providerType(providerType) + .beanDeployment(beanDeployment).scope(scope).types(types) + .qualifiers(qualifiers) + .alternativePriority(alternativePriority).name(name).creator(creatorConsumer).destroyer(destroyerConsumer) + .params(params).build()); } @SuppressWarnings("unchecked") @@ -213,4 +222,4 @@ private static T cast(Object obj) { return (T) obj; } -} \ No newline at end of file +} 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 ef6d8ace92fb9..3839054a0560f 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 @@ -16,6 +16,13 @@ package io.quarkus.arc.processor; +import io.quarkus.arc.processor.BeanDeploymentValidator.ValidationContext; +import io.quarkus.arc.processor.BeanProcessor.BuildContextImpl; +import io.quarkus.arc.processor.BeanRegistrar.RegistrationContext; +import io.quarkus.arc.processor.BuildExtension.BuildContext; +import io.quarkus.arc.processor.BuildExtension.Key; +import io.quarkus.gizmo.MethodCreator; +import io.quarkus.gizmo.ResultHandle; import java.lang.annotation.Annotation; import java.lang.reflect.Modifier; import java.util.ArrayList; @@ -30,11 +37,9 @@ import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; - import javax.enterprise.inject.Model; import javax.enterprise.inject.spi.DefinitionException; import javax.enterprise.inject.spi.DeploymentException; - import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationTarget; import org.jboss.jandex.AnnotationTarget.Kind; @@ -48,14 +53,6 @@ import org.jboss.logging.Logger; import org.jboss.logging.Logger.Level; -import io.quarkus.arc.processor.BeanDeploymentValidator.ValidationContext; -import io.quarkus.arc.processor.BeanProcessor.BuildContextImpl; -import io.quarkus.arc.processor.BeanRegistrar.RegistrationContext; -import io.quarkus.arc.processor.BuildExtension.BuildContext; -import io.quarkus.arc.processor.BuildExtension.Key; -import io.quarkus.gizmo.MethodCreator; -import io.quarkus.gizmo.ResultHandle; - /** * * @author Martin Kouba @@ -94,13 +91,18 @@ public class BeanDeployment { private final Map> customContexts; - BeanDeployment(IndexView index, Collection additionalBeanDefiningAnnotations, List annotationTransformers) { - this(index, additionalBeanDefiningAnnotations, annotationTransformers, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), null, false, null, Collections.emptyMap()); + BeanDeployment(IndexView index, Collection additionalBeanDefiningAnnotations, + List annotationTransformers) { + this(index, additionalBeanDefiningAnnotations, annotationTransformers, Collections.emptyList(), Collections.emptyList(), + Collections.emptyList(), null, false, null, Collections.emptyMap()); } - BeanDeployment(IndexView index, Collection additionalBeanDefiningAnnotations, List annotationTransformers, - Collection resourceAnnotations, List beanRegistrars, List contextRegistrars, - BuildContextImpl buildContext, boolean removeUnusedBeans, List> unusedExclusions, Map> additionalStereotypes) { + BeanDeployment(IndexView index, Collection additionalBeanDefiningAnnotations, + List annotationTransformers, + Collection resourceAnnotations, List beanRegistrars, + List contextRegistrars, + BuildContextImpl buildContext, boolean removeUnusedBeans, List> unusedExclusions, + Map> additionalStereotypes) { long start = System.currentTimeMillis(); Collection beanDefiningAnnotations = new HashSet<>(); if (additionalBeanDefiningAnnotations != null) { @@ -125,12 +127,14 @@ public class BeanDeployment { this.qualifiers = findQualifiers(index); // TODO interceptor bindings are transitive!!! this.interceptorBindings = findInterceptorBindings(index); - this.stereotypes = findStereotypes(index, interceptorBindings, beanDefiningAnnotations, customContexts, additionalStereotypes); + this.stereotypes = findStereotypes(index, interceptorBindings, beanDefiningAnnotations, customContexts, + additionalStereotypes); this.injectionPoints = new ArrayList<>(); this.interceptors = findInterceptors(injectionPoints); this.beanResolver = new BeanResolver(this); List observers = new ArrayList<>(); - this.beans = findBeans(initBeanDefiningAnnotations(beanDefiningAnnotations, stereotypes.keySet()), observers, injectionPoints); + this.beans = findBeans(initBeanDefiningAnnotations(beanDefiningAnnotations, stereotypes.keySet()), observers, + injectionPoints); if (buildContext != null) { buildContext.putInternal(Key.INJECTION_POINTS.asString(), Collections.unmodifiableList(injectionPoints)); @@ -202,7 +206,7 @@ AnnotationInstance getAnnotation(AnnotationTarget target, DotName name) { return annotationStore.getAnnotation(target, name); } - Map> getCustomContexts() { + Map> getCustomContexts() { return customContexts; } @@ -210,7 +214,8 @@ ScopeInfo getScope(DotName scopeAnnotationName) { return getScope(scopeAnnotationName, customContexts); } - static ScopeInfo getScope(DotName scopeAnnotationName, Map> customContexts) { + static ScopeInfo getScope(DotName scopeAnnotationName, + Map> customContexts) { BuiltinScope builtin = BuiltinScope.from(scopeAnnotationName); if (builtin != null) { return builtin.getInfo(); @@ -232,7 +237,7 @@ static ScopeInfo getValidScope(Collection stereotypeScopes, Annotatio default: throw new DefinitionException("All stereotypes must specify the same scope or the bean must declare a scope: " + target + " declares scopes " + stereotypeScopes.stream().map(ScopeInfo::getDotName) - .map(DotName::toString).collect(Collectors.joining(", "))); + .map(DotName::toString).collect(Collectors.joining(", "))); } } @@ -274,8 +279,10 @@ void init() { long removalStart = System.currentTimeMillis(); Set removable = new HashSet<>(); Set unusedProducers = new HashSet<>(); - List producers = beans.stream().filter(b -> b.isProducerMethod() || b.isProducerField()).collect(Collectors.toList()); - List instanceInjectionPoints = injectionPoints.stream().filter(ip -> BuiltinBean.resolve(ip) == BuiltinBean.INSTANCE) + List producers = beans.stream().filter(b -> b.isProducerMethod() || b.isProducerField()) + .collect(Collectors.toList()); + List instanceInjectionPoints = injectionPoints.stream() + .filter(ip -> BuiltinBean.resolve(ip) == BuiltinBean.INSTANCE) .collect(Collectors.toList()); for (BeanInfo bean : beans) { // Named beans can be used in templates and expressions @@ -299,8 +306,9 @@ void init() { continue; } // Instance - if (instanceInjectionPoints.stream().anyMatch(ip -> Beans.matchesType(bean, ip.getRequiredType().asParameterizedType().arguments().get(0)) - && ip.getRequiredQualifiers().stream().allMatch(q -> Beans.hasQualifier(bean, q)))) { + if (instanceInjectionPoints.stream() + .anyMatch(ip -> Beans.matchesType(bean, ip.getRequiredType().asParameterizedType().arguments().get(0)) + && ip.getRequiredQualifiers().stream().allMatch(q -> Beans.hasQualifier(bean, q)))) { continue; } if (bean.isProducerField() || bean.isProducerMethod()) { @@ -311,7 +319,8 @@ void init() { } if (!unusedProducers.isEmpty()) { // Second pass to find beans which themselves are unused and declare only unused producers - Map> declaringMap = producers.stream().collect(Collectors.groupingBy(BeanInfo::getDeclaringBean)); + Map> declaringMap = producers.stream() + .collect(Collectors.groupingBy(BeanInfo::getDeclaringBean)); for (Entry> entry : declaringMap.entrySet()) { if (unusedProducers.containsAll(entry.getValue())) { // All producers declared by this bean are unused @@ -346,7 +355,8 @@ static Map findInterceptorBindings(IndexView index) { } Map findStereotypes(IndexView index, Map interceptorBindings, - Collection additionalBeanDefiningAnnotations, Map> customContexts, + Collection additionalBeanDefiningAnnotations, + Map> customContexts, Map> additionalStereotypes) { Map stereotypes = new HashMap<>(); final List stereotypeAnnotations = new ArrayList<>(index.getAnnotations(DotNames.STEREOTYPE)); @@ -372,7 +382,8 @@ Map findStereotypes(IndexView index, Map findStereotypes(IndexView index, Map findStereotypes(IndexView index, Map beanRegistrars, BuildContext buildContext) { if (!beanRegistrars.isEmpty()) { RegistrationContext registrationContext = new RegistrationContext() { @@ -476,17 +487,19 @@ private void validateBeans(List errors) { if (entry.getValue() .size() > 1) { if (Beans.resolveAmbiguity(entry.getValue()) == null) { - errors.add(new DeploymentException("Unresolvable ambiguous bean name detected: " + entry.getKey() + "\nBeans:\n" + entry.getValue() - .stream() - .map(Object::toString) - .collect(Collectors.joining("\n")))); + errors.add(new DeploymentException("Unresolvable ambiguous bean name detected: " + entry.getKey() + + "\nBeans:\n" + entry.getValue() + .stream() + .map(Object::toString) + .collect(Collectors.joining("\n")))); } } } } } - private List findBeans(Collection beanDefiningAnnotations, List observers, List injectionPoints) { + private List findBeans(Collection beanDefiningAnnotations, List observers, + List injectionPoints) { Set beanClasses = new HashSet<>(); Set producerMethods = new HashSet<>(); @@ -557,7 +570,8 @@ private List findBeans(Collection beanDefiningAnnotations, Li // Producers are not inherited producerMethods.add(method); if (!hasBeanDefiningAnnotation) { - LOGGER.debugf("Producer method found but %s has no bean defining annotation - using @Dependent", beanClass); + LOGGER.debugf("Producer method found but %s has no bean defining annotation - using @Dependent", + beanClass); beanClasses.add(beanClass); } } else if (annotationStore.hasAnnotation(method, DotNames.DISPOSES)) { @@ -567,14 +581,16 @@ private List findBeans(Collection beanDefiningAnnotations, Li // TODO observers are inherited syncObserverMethods.add(method); if (!hasBeanDefiningAnnotation) { - LOGGER.debugf("Observer method found but %s has no bean defining annotation - using @Dependent", beanClass); + LOGGER.debugf("Observer method found but %s has no bean defining annotation - using @Dependent", + beanClass); beanClasses.add(beanClass); } } else if (annotationStore.hasAnnotation(method, DotNames.OBSERVES_ASYNC)) { // TODO observers are inherited asyncObserverMethods.add(method); if (!hasBeanDefiningAnnotation) { - LOGGER.debugf("Observer method found but %s has no bean defining annotation - using @Dependent", beanClass); + LOGGER.debugf("Observer method found but %s has no bean defining annotation - using @Dependent", + beanClass); beanClasses.add(beanClass); } } @@ -584,7 +600,8 @@ private List findBeans(Collection beanDefiningAnnotations, Li // Producer fields are not inherited producerFields.add(field); if (!hasBeanDefiningAnnotation) { - LOGGER.debugf("Producer field found but %s has no bean defining annotation - using @Dependent", beanClass); + LOGGER.debugf("Producer field found but %s has no bean defining annotation - using @Dependent", + beanClass); beanClasses.add(beanClass); } } @@ -624,7 +641,8 @@ private List findBeans(Collection beanDefiningAnnotations, Li for (FieldInfo producerField : producerFields) { BeanInfo declaringBean = beanClassToBean.get(producerField.declaringClass()); if (declaringBean != null) { - beans.add(Beans.createProducerField(producerField, declaringBean, this, findDisposer(declaringBean, producerField, disposers))); + beans.add(Beans.createProducerField(producerField, declaringBean, this, + findDisposer(declaringBean, producerField, disposers))); } } @@ -659,10 +677,12 @@ private DisposerInfo findDisposer(BeanInfo declaringBean, AnnotationTarget annot Set qualifiers; if (Kind.FIELD.equals(annotationTarget.kind())) { beanType = annotationTarget.asField().type(); - qualifiers = annotationTarget.asField().annotations().stream().filter(a -> getQualifier(a.name()) != null).collect(Collectors.toSet()); + qualifiers = annotationTarget.asField().annotations().stream().filter(a -> getQualifier(a.name()) != null) + .collect(Collectors.toSet()); } else if (Kind.METHOD.equals(annotationTarget.kind())) { beanType = annotationTarget.asMethod().returnType(); - qualifiers = annotationTarget.asMethod().annotations().stream().filter(a -> Kind.METHOD.equals(a.target().kind()) && getQualifier(a.name()) != null) + qualifiers = annotationTarget.asMethod().annotations().stream() + .filter(a -> Kind.METHOD.equals(a.target().kind()) && getQualifier(a.name()) != null) .collect(Collectors.toSet()); } else { throw new RuntimeException("Unsupported annotation target: " + annotationTarget); @@ -671,14 +691,14 @@ private DisposerInfo findDisposer(BeanInfo declaringBean, AnnotationTarget annot if (disposer.getDeclaringBean().equals(declaringBean)) { boolean hasQualifier = true; for (AnnotationInstance qualifier : qualifiers) { - if (!Beans.hasQualifier(getQualifier(qualifier.name()), qualifier, null)) { + if (!Beans.hasQualifier(getQualifier(qualifier.name()), qualifier, + disposer.getDisposedParameterQualifiers())) { hasQualifier = false; } } - if (hasQualifier && beanResolver.matches(beanType, disposer.getDisposerMethod().parameters().get(disposer.getDisposedParameter().position()))) { + if (hasQualifier && beanResolver.matches(beanType, disposer.getDisposedParameterType())) { found.add(disposer); } - } } if (found.size() > 1) { @@ -733,7 +753,8 @@ private void processErrors(List errors) { } } - public static Set initBeanDefiningAnnotations(Collection additionalBeanDefiningAnnotations, Set stereotypes) { + public static Set initBeanDefiningAnnotations(Collection additionalBeanDefiningAnnotations, + Set stereotypes) { Set beanDefiningAnnotations = new HashSet<>(); for (BuiltinScope scope : BuiltinScope.values()) { beanDefiningAnnotations.add(scope.getInfo().getDotName()); 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 298f1b0955538..072b13ab99a29 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 @@ -26,7 +26,8 @@ public interface BeanDeploymentValidator extends BuildExtension { /** - * At this point, all beans/observers are registered. This method should call {@link ValidationContext#addDeploymentProblem(Throwable)} if validation fails. + * At this point, all beans/observers are registered. This method should call + * {@link ValidationContext#addDeploymentProblem(Throwable)} if validation fails. * * @see Key#INJECTION_POINTS * @see Key#BEANS 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 ff157c567f30f..2d8929fe336ce 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 @@ -21,6 +21,31 @@ import static org.objectweb.asm.Opcodes.ACC_PRIVATE; import static org.objectweb.asm.Opcodes.ACC_PUBLIC; +import io.quarkus.arc.CreationalContextImpl; +import io.quarkus.arc.CurrentInjectionPointProvider; +import io.quarkus.arc.InitializedInterceptor; +import io.quarkus.arc.InjectableBean; +import io.quarkus.arc.InjectableInterceptor; +import io.quarkus.arc.InjectableReferenceProvider; +import io.quarkus.arc.LazyValue; +import io.quarkus.arc.Subclass; +import io.quarkus.arc.processor.BeanInfo.InterceptionInfo; +import io.quarkus.arc.processor.BeanProcessor.PrivateMembersCollector; +import io.quarkus.arc.processor.ResourceOutput.Resource; +import io.quarkus.arc.processor.ResourceOutput.Resource.SpecialType; +import io.quarkus.gizmo.AssignableResultHandle; +import io.quarkus.gizmo.BytecodeCreator; +import io.quarkus.gizmo.CatchBlockCreator; +import io.quarkus.gizmo.ClassCreator; +import io.quarkus.gizmo.ClassOutput; +import io.quarkus.gizmo.DescriptorUtils; +import io.quarkus.gizmo.FieldCreator; +import io.quarkus.gizmo.FieldDescriptor; +import io.quarkus.gizmo.FunctionCreator; +import io.quarkus.gizmo.MethodCreator; +import io.quarkus.gizmo.MethodDescriptor; +import io.quarkus.gizmo.ResultHandle; +import io.quarkus.gizmo.TryBlock; import java.lang.reflect.Member; import java.lang.reflect.Modifier; import java.util.ArrayList; @@ -37,12 +62,10 @@ import java.util.Set; import java.util.function.Predicate; import java.util.function.Supplier; - import javax.enterprise.context.spi.CreationalContext; import javax.enterprise.inject.literal.InjectLiteral; import javax.enterprise.inject.spi.InterceptionType; import javax.interceptor.InvocationContext; - import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationTarget; import org.jboss.jandex.AnnotationTarget.Kind; @@ -51,32 +74,6 @@ import org.jboss.jandex.FieldInfo; import org.jboss.jandex.MethodInfo; import org.jboss.jandex.Type; -import io.quarkus.gizmo.AssignableResultHandle; -import io.quarkus.gizmo.BytecodeCreator; -import io.quarkus.gizmo.CatchBlockCreator; -import io.quarkus.gizmo.ClassCreator; -import io.quarkus.gizmo.ClassOutput; -import io.quarkus.gizmo.DescriptorUtils; -import io.quarkus.gizmo.FieldCreator; -import io.quarkus.gizmo.FieldDescriptor; -import io.quarkus.gizmo.FunctionCreator; -import io.quarkus.gizmo.MethodCreator; -import io.quarkus.gizmo.MethodDescriptor; -import io.quarkus.gizmo.ResultHandle; -import io.quarkus.gizmo.TryBlock; - -import io.quarkus.arc.CreationalContextImpl; -import io.quarkus.arc.CurrentInjectionPointProvider; -import io.quarkus.arc.InitializedInterceptor; -import io.quarkus.arc.InjectableBean; -import io.quarkus.arc.InjectableInterceptor; -import io.quarkus.arc.InjectableReferenceProvider; -import io.quarkus.arc.LazyValue; -import io.quarkus.arc.Subclass; -import io.quarkus.arc.processor.BeanInfo.InterceptionInfo; -import io.quarkus.arc.processor.BeanProcessor.PrivateMembersCollector; -import io.quarkus.arc.processor.ResourceOutput.Resource; -import io.quarkus.arc.processor.ResourceOutput.Resource.SpecialType; /** * @@ -98,14 +95,15 @@ public class BeanGenerator extends AbstractGenerator { protected static final String FIELD_NAME_STEREOTYPES = "stereotypes"; protected static final String FIELD_NAME_PROXY = "proxy"; protected static final String FIELD_NAME_PARAMS = "params"; - + protected final AnnotationLiteralProcessor annotationLiterals; protected final Predicate applicationClassPredicate; protected final PrivateMembersCollector privateMembers; - - public BeanGenerator(AnnotationLiteralProcessor annotationLiterals, Predicate applicationClassPredicate, PrivateMembersCollector privateMembers) { + + public BeanGenerator(AnnotationLiteralProcessor annotationLiterals, Predicate applicationClassPredicate, + PrivateMembersCollector privateMembers) { this.annotationLiterals = annotationLiterals; this.applicationClassPredicate = applicationClassPredicate; this.privateMembers = privateMembers; @@ -137,28 +135,34 @@ Collection generate(BeanInfo bean, ReflectionRegistration reflectionRe Collection generateSyntheticBean(BeanInfo bean, ReflectionRegistration reflectionRegistration) { - String baseName; + StringBuilder baseNameBuilder = new StringBuilder(); if (bean.getImplClazz().enclosingClass() != null) { - baseName = DotNames.simpleName(bean.getImplClazz().enclosingClass()) + "_" + DotNames.simpleName(bean.getImplClazz()); + baseNameBuilder.append(DotNames.simpleName(bean.getImplClazz().enclosingClass())).append("_") + .append(DotNames.simpleName(bean.getImplClazz())); } else { - baseName = DotNames.simpleName(bean.getImplClazz()); + baseNameBuilder.append(DotNames.simpleName(bean.getImplClazz())); } - baseName += SYNTHETIC_SUFFIX; - + baseNameBuilder.append("_"); + baseNameBuilder.append(bean.getIdentifier()); + baseNameBuilder.append(SYNTHETIC_SUFFIX); + String baseName = baseNameBuilder.toString(); + Type providerType = bean.getProviderType(); - ClassInfo providerClass = bean.getDeployment().getIndex().getClassByName(providerType.name()); - String providerTypeName = providerClass.name().toString(); + String providerTypeName = providerType.name().toString(); String targetPackage = getPackageName(bean); - String generatedName = targetPackage.replace('.', '/') + "/" + baseName + BEAN_SUFFIX; - + String generatedName = generatedNameFromTarget(targetPackage, baseName, BEAN_SUFFIX); + boolean isApplicationClass = applicationClassPredicate.test(bean.getImplClazz().name()); - ResourceClassOutput classOutput = new ResourceClassOutput(isApplicationClass, name -> name.equals(generatedName) ? SpecialType.BEAN : null); + ResourceClassOutput classOutput = new ResourceClassOutput(isApplicationClass, + name -> name.equals(generatedName) ? SpecialType.BEAN : null); // Foo_Bean implements InjectableBean - ClassCreator beanCreator = ClassCreator.builder().classOutput(classOutput).className(generatedName).interfaces(InjectableBean.class).build(); + ClassCreator beanCreator = ClassCreator.builder().classOutput(classOutput).className(generatedName) + .interfaces(InjectableBean.class).build(); // Fields - FieldCreator beanTypes = beanCreator.getFieldCreator(FIELD_NAME_BEAN_TYPES, Set.class).setModifiers(ACC_PRIVATE | ACC_FINAL); + FieldCreator beanTypes = beanCreator.getFieldCreator(FIELD_NAME_BEAN_TYPES, Set.class) + .setModifiers(ACC_PRIVATE | ACC_FINAL); FieldCreator qualifiers = null; if (!bean.getQualifiers().isEmpty() && !bean.hasDefaultQualifiers()) { qualifiers = beanCreator.getFieldCreator(FIELD_NAME_QUALIFIERS, Set.class).setModifiers(ACC_PRIVATE | ACC_FINAL); @@ -174,7 +178,8 @@ Collection generateSyntheticBean(BeanInfo bean, ReflectionRegistration // If needed, store the synthetic bean parameters FieldCreator params = beanCreator.getFieldCreator(FIELD_NAME_PARAMS, Map.class).setModifiers(ACC_PRIVATE | ACC_FINAL); - MethodCreator constructor = initConstructor(classOutput, beanCreator, bean, baseName, Collections.emptyMap(), Collections.emptyMap(), + MethodCreator constructor = initConstructor(classOutput, beanCreator, bean, baseName, Collections.emptyMap(), + Collections.emptyMap(), annotationLiterals); ResultHandle paramsHandle = constructor.newInstance(MethodDescriptor.ofConstructor(HashMap.class)); for (Entry entry : bean.getParams().entrySet()) { @@ -196,16 +201,19 @@ Collection generateSyntheticBean(BeanInfo bean, ReflectionRegistration valHandle = constructor.load((Boolean) entry.getValue()); } // TODO other param types - constructor.invokeInterfaceMethod(MethodDescriptors.MAP_PUT, paramsHandle, constructor.load(entry.getKey()), valHandle); + constructor.invokeInterfaceMethod(MethodDescriptors.MAP_PUT, paramsHandle, constructor.load(entry.getKey()), + valHandle); } constructor.writeInstanceField(params.getFieldDescriptor(), constructor.getThis(), paramsHandle); constructor.returnValue(null); implementGetIdentifier(bean, beanCreator); if (!bean.hasDefaultDestroy()) { - implementDestroy(bean, beanCreator, providerTypeName, Collections.emptyMap(), reflectionRegistration, isApplicationClass); + implementDestroy(bean, beanCreator, providerTypeName, Collections.emptyMap(), reflectionRegistration, + isApplicationClass); } - implementCreate(classOutput, beanCreator, bean, providerTypeName, baseName, Collections.emptyMap(), Collections.emptyMap(), reflectionRegistration, + implementCreate(classOutput, beanCreator, bean, providerTypeName, baseName, Collections.emptyMap(), + Collections.emptyMap(), reflectionRegistration, targetPackage, isApplicationClass); implementGet(bean, beanCreator, providerTypeName); @@ -238,19 +246,21 @@ Collection generateClassBean(BeanInfo bean, ClassInfo beanClass, Refle baseName = DotNames.simpleName(beanClass); } Type providerType = bean.getProviderType(); - ClassInfo providerClass = bean.getDeployment().getIndex().getClassByName(providerType.name()); - String providerTypeName = providerClass.name().toString(); + String providerTypeName = providerType.name().toString(); String targetPackage = DotNames.packageName(providerType.name()); - String generatedName = targetPackage.replace('.', '/') + "/" + baseName + BEAN_SUFFIX; + String generatedName = generatedNameFromTarget(targetPackage, baseName, BEAN_SUFFIX); boolean isApplicationClass = applicationClassPredicate.test(beanClass.name()); - ResourceClassOutput classOutput = new ResourceClassOutput(isApplicationClass, name -> name.equals(generatedName) ? SpecialType.BEAN : null); + ResourceClassOutput classOutput = new ResourceClassOutput(isApplicationClass, + name -> name.equals(generatedName) ? SpecialType.BEAN : null); // Foo_Bean implements InjectableBean - ClassCreator beanCreator = ClassCreator.builder().classOutput(classOutput).className(generatedName).interfaces(InjectableBean.class).build(); + ClassCreator beanCreator = ClassCreator.builder().classOutput(classOutput).className(generatedName) + .interfaces(InjectableBean.class).build(); // Fields - FieldCreator beanTypes = beanCreator.getFieldCreator(FIELD_NAME_BEAN_TYPES, Set.class).setModifiers(ACC_PRIVATE | ACC_FINAL); + FieldCreator beanTypes = beanCreator.getFieldCreator(FIELD_NAME_BEAN_TYPES, Set.class) + .setModifiers(ACC_PRIVATE | ACC_FINAL); FieldCreator qualifiers = null; if (!bean.getQualifiers().isEmpty() && !bean.hasDefaultQualifiers()) { qualifiers = beanCreator.getFieldCreator(FIELD_NAME_QUALIFIERS, Set.class).setModifiers(ACC_PRIVATE | ACC_FINAL); @@ -269,13 +279,16 @@ Collection generateClassBean(BeanInfo bean, ClassInfo beanClass, Refle initMaps(bean, injectionPointToProviderField, interceptorToProviderField); createProviderFields(beanCreator, bean, injectionPointToProviderField, interceptorToProviderField); - createConstructor(classOutput, beanCreator, bean, baseName, injectionPointToProviderField, interceptorToProviderField, annotationLiterals); + createConstructor(classOutput, beanCreator, bean, baseName, injectionPointToProviderField, interceptorToProviderField, + annotationLiterals); implementGetIdentifier(bean, beanCreator); if (!bean.hasDefaultDestroy()) { - implementDestroy(bean, beanCreator, providerTypeName, injectionPointToProviderField, reflectionRegistration, isApplicationClass); + implementDestroy(bean, beanCreator, providerTypeName, injectionPointToProviderField, reflectionRegistration, + isApplicationClass); } - implementCreate(classOutput, beanCreator, bean, providerTypeName, baseName, injectionPointToProviderField, interceptorToProviderField, + implementCreate(classOutput, beanCreator, bean, providerTypeName, baseName, injectionPointToProviderField, + interceptorToProviderField, reflectionRegistration, targetPackage, isApplicationClass); implementGet(bean, beanCreator, providerTypeName); @@ -299,12 +312,14 @@ Collection generateClassBean(BeanInfo bean, ClassInfo beanClass, Refle return classOutput.getResources(); } - Collection generateProducerMethodBean(BeanInfo bean, MethodInfo producerMethod, ReflectionRegistration reflectionRegistration) { + Collection generateProducerMethodBean(BeanInfo bean, MethodInfo producerMethod, + ReflectionRegistration reflectionRegistration) { ClassInfo declaringClass = producerMethod.declaringClass(); String declaringClassBase; if (declaringClass.enclosingClass() != null) { - declaringClassBase = DotNames.simpleName(declaringClass.enclosingClass()) + "_" + DotNames.simpleName(declaringClass); + declaringClassBase = DotNames.simpleName(declaringClass.enclosingClass()) + "_" + + DotNames.simpleName(declaringClass); } else { declaringClassBase = DotNames.simpleName(declaringClass); } @@ -315,23 +330,27 @@ Collection generateProducerMethodBean(BeanInfo bean, MethodInfo produc .append("_") .append(producerMethod.returnType().name().toString()); - for(Type i : producerMethod.parameters()) { + for (Type i : producerMethod.parameters()) { sigBuilder.append(i.name().toString()); } - String baseName = declaringClassBase + PRODUCER_METHOD_SUFFIX + "_" + producerMethod.name() + "_" + Hashes.sha1(sigBuilder.toString()); + String baseName = declaringClassBase + PRODUCER_METHOD_SUFFIX + "_" + producerMethod.name() + "_" + + Hashes.sha1(sigBuilder.toString()); String providerTypeName = providerType.name().toString(); String targetPackage = DotNames.packageName(declaringClass.name()); - String generatedName = targetPackage.replace('.', '/') + "/" + baseName + BEAN_SUFFIX; + String generatedName = generatedNameFromTarget(targetPackage, baseName, BEAN_SUFFIX); boolean isApplicationClass = applicationClassPredicate.test(declaringClass.name()); - ResourceClassOutput classOutput = new ResourceClassOutput(isApplicationClass, name -> name.equals(generatedName) ? SpecialType.BEAN : null); + ResourceClassOutput classOutput = new ResourceClassOutput(isApplicationClass, + name -> name.equals(generatedName) ? SpecialType.BEAN : null); // Foo_Bean implements InjectableBean - ClassCreator beanCreator = ClassCreator.builder().classOutput(classOutput).className(generatedName).interfaces(InjectableBean.class).build(); + ClassCreator beanCreator = ClassCreator.builder().classOutput(classOutput).className(generatedName) + .interfaces(InjectableBean.class).build(); // Fields - FieldCreator beanTypes = beanCreator.getFieldCreator(FIELD_NAME_BEAN_TYPES, Set.class).setModifiers(ACC_PRIVATE | ACC_FINAL); + FieldCreator beanTypes = beanCreator.getFieldCreator(FIELD_NAME_BEAN_TYPES, Set.class) + .setModifiers(ACC_PRIVATE | ACC_FINAL); FieldCreator qualifiers = null; if (!bean.getQualifiers().isEmpty() && !bean.hasDefaultQualifiers()) { qualifiers = beanCreator.getFieldCreator(FIELD_NAME_QUALIFIERS, Set.class).setModifiers(ACC_PRIVATE | ACC_FINAL); @@ -350,13 +369,16 @@ Collection generateProducerMethodBean(BeanInfo bean, MethodInfo produc initMaps(bean, injectionPointToProviderField, null); createProviderFields(beanCreator, bean, injectionPointToProviderField, Collections.emptyMap()); - createConstructor(classOutput, beanCreator, bean, baseName, injectionPointToProviderField, Collections.emptyMap(), annotationLiterals); + createConstructor(classOutput, beanCreator, bean, baseName, injectionPointToProviderField, Collections.emptyMap(), + annotationLiterals); implementGetIdentifier(bean, beanCreator); if (!bean.hasDefaultDestroy()) { - implementDestroy(bean, beanCreator, providerTypeName, injectionPointToProviderField, reflectionRegistration, isApplicationClass); + implementDestroy(bean, beanCreator, providerTypeName, injectionPointToProviderField, reflectionRegistration, + isApplicationClass); } - implementCreate(classOutput, beanCreator, bean, providerTypeName, baseName, injectionPointToProviderField, Collections.emptyMap(), + implementCreate(classOutput, beanCreator, bean, providerTypeName, baseName, injectionPointToProviderField, + Collections.emptyMap(), reflectionRegistration, targetPackage, isApplicationClass); implementGet(bean, beanCreator, providerTypeName); @@ -378,13 +400,14 @@ Collection generateProducerMethodBean(BeanInfo bean, MethodInfo produc return classOutput.getResources(); } - - Collection generateProducerFieldBean(BeanInfo bean, FieldInfo producerField, ReflectionRegistration reflectionRegistration) { + Collection generateProducerFieldBean(BeanInfo bean, FieldInfo producerField, + ReflectionRegistration reflectionRegistration) { ClassInfo declaringClass = producerField.declaringClass(); String declaringClassBase; if (declaringClass.enclosingClass() != null) { - declaringClassBase = DotNames.simpleName(declaringClass.enclosingClass()) + "_" + DotNames.simpleName(declaringClass); + declaringClassBase = DotNames.simpleName(declaringClass.enclosingClass()) + "_" + + DotNames.simpleName(declaringClass); } else { declaringClassBase = DotNames.simpleName(declaringClass); } @@ -393,16 +416,19 @@ Collection generateProducerFieldBean(BeanInfo bean, FieldInfo producer String baseName = declaringClassBase + PRODUCER_FIELD_SUFFIX + "_" + producerField.name(); String providerTypeName = providerType.name().toString(); String targetPackage = DotNames.packageName(declaringClass.name()); - String generatedName = targetPackage.replace('.', '/') + "/" + baseName + BEAN_SUFFIX; + String generatedName = generatedNameFromTarget(targetPackage, baseName, BEAN_SUFFIX); boolean isApplicationClass = applicationClassPredicate.test(declaringClass.name()); - ResourceClassOutput classOutput = new ResourceClassOutput(isApplicationClass, name -> name.equals(generatedName) ? SpecialType.BEAN : null); + ResourceClassOutput classOutput = new ResourceClassOutput(isApplicationClass, + name -> name.equals(generatedName) ? SpecialType.BEAN : null); // Foo_Bean implements InjectableBean - ClassCreator beanCreator = ClassCreator.builder().classOutput(classOutput).className(generatedName).interfaces(InjectableBean.class).build(); + ClassCreator beanCreator = ClassCreator.builder().classOutput(classOutput).className(generatedName) + .interfaces(InjectableBean.class).build(); // Fields - FieldCreator beanTypes = beanCreator.getFieldCreator(FIELD_NAME_BEAN_TYPES, Set.class).setModifiers(ACC_PRIVATE | ACC_FINAL); + FieldCreator beanTypes = beanCreator.getFieldCreator(FIELD_NAME_BEAN_TYPES, Set.class) + .setModifiers(ACC_PRIVATE | ACC_FINAL); FieldCreator qualifiers = null; if (!bean.getQualifiers().isEmpty() && !bean.hasDefaultQualifiers()) { qualifiers = beanCreator.getFieldCreator(FIELD_NAME_QUALIFIERS, Set.class).setModifiers(ACC_PRIVATE | ACC_FINAL); @@ -417,13 +443,15 @@ Collection generateProducerFieldBean(BeanInfo bean, FieldInfo producer } createProviderFields(beanCreator, bean, Collections.emptyMap(), Collections.emptyMap()); - createConstructor(classOutput, beanCreator, bean, baseName, Collections.emptyMap(), Collections.emptyMap(), annotationLiterals); + createConstructor(classOutput, beanCreator, bean, baseName, Collections.emptyMap(), Collections.emptyMap(), + annotationLiterals); implementGetIdentifier(bean, beanCreator); if (!bean.hasDefaultDestroy()) { implementDestroy(bean, beanCreator, providerTypeName, null, reflectionRegistration, isApplicationClass); } - implementCreate(classOutput, beanCreator, bean, providerTypeName, baseName, Collections.emptyMap(), Collections.emptyMap(), reflectionRegistration, + implementCreate(classOutput, beanCreator, bean, providerTypeName, baseName, Collections.emptyMap(), + Collections.emptyMap(), reflectionRegistration, targetPackage, isApplicationClass); implementGet(bean, beanCreator, providerTypeName); @@ -445,7 +473,8 @@ Collection generateProducerFieldBean(BeanInfo bean, FieldInfo producer return classOutput.getResources(); } - protected void initMaps(BeanInfo bean, Map injectionPointToProvider, Map interceptorToProvider) { + protected void initMaps(BeanInfo bean, Map injectionPointToProvider, + Map interceptorToProvider) { int providerIdx = 1; for (InjectionPointInfo injectionPoint : bean.getAllInjectionPoints()) { injectionPointToProvider.put(injectionPoint, "injectProvider" + providerIdx++); @@ -460,11 +489,13 @@ protected void initMaps(BeanInfo bean, Map injection } } - protected void createProviderFields(ClassCreator beanCreator, BeanInfo bean, Map injectionPointToProvider, + protected void createProviderFields(ClassCreator beanCreator, BeanInfo bean, + Map injectionPointToProvider, Map interceptorToProvider) { // Declaring bean provider if (bean.isProducerMethod() || bean.isProducerField()) { - beanCreator.getFieldCreator(FIELD_NAME_DECLARING_PROVIDER, InjectableBean.class).setModifiers(ACC_PRIVATE | ACC_FINAL); + beanCreator.getFieldCreator(FIELD_NAME_DECLARING_PROVIDER, InjectableBean.class) + .setModifiers(ACC_PRIVATE | ACC_FINAL); } // Injection points for (String provider : injectionPointToProvider.values()) { @@ -477,14 +508,17 @@ protected void createProviderFields(ClassCreator beanCreator, BeanInfo bean, Map } protected void createConstructor(ClassOutput classOutput, ClassCreator beanCreator, BeanInfo bean, String baseName, - Map injectionPointToProviderField, Map interceptorToProviderField, + Map injectionPointToProviderField, + Map interceptorToProviderField, AnnotationLiteralProcessor annotationLiterals) { - initConstructor(classOutput, beanCreator, bean, baseName, injectionPointToProviderField, interceptorToProviderField, annotationLiterals) - .returnValue(null); + initConstructor(classOutput, beanCreator, bean, baseName, injectionPointToProviderField, interceptorToProviderField, + annotationLiterals) + .returnValue(null); } protected MethodCreator initConstructor(ClassOutput classOutput, ClassCreator beanCreator, BeanInfo bean, String baseName, - Map injectionPointToProviderField, Map interceptorToProviderField, + Map injectionPointToProviderField, + Map interceptorToProviderField, AnnotationLiteralProcessor annotationLiterals) { // First collect all param types @@ -515,7 +549,9 @@ protected MethodCreator initConstructor(ClassOutput classOutput, ClassCreator be // Declaring bean provider int paramIdx = 0; if (bean.isProducerMethod() || bean.isProducerField()) { - constructor.writeInstanceField(FieldDescriptor.of(beanCreator.getClassName(), FIELD_NAME_DECLARING_PROVIDER, InjectableBean.class.getName()), + constructor.writeInstanceField( + FieldDescriptor.of(beanCreator.getClassName(), FIELD_NAME_DECLARING_PROVIDER, + InjectableBean.class.getName()), constructor.getThis(), constructor.getMethodParam(0)); paramIdx++; } @@ -535,8 +571,10 @@ protected MethodCreator initConstructor(ClassOutput classOutput, ClassCreator be builtinBean.getGenerator().generate(classOutput, bean.getDeployment(), injectionPoint, beanCreator, constructor, injectionPointToProviderField.get(injectionPoint), annotationLiterals); } else { - if (injectionPoint.getResolvedBean().getAllInjectionPoints().stream() - .anyMatch(ip -> BuiltinBean.INJECTION_POINT.getRawTypeDotName().equals(ip.getRequiredType().name()))) { + if (BuiltinScope.DEPENDENT.is(injectionPoint.getResolvedBean().getScope()) && (injectionPoint.getResolvedBean() + .getAllInjectionPoints().stream() + .anyMatch(ip -> BuiltinBean.INJECTION_POINT.getRawTypeDotName().equals(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 ResultHandle wrapHandle = wrapCurrentInjectionPoint(classOutput, beanCreator, bean, constructor, @@ -555,7 +593,8 @@ protected MethodCreator initConstructor(ClassOutput classOutput, ClassCreator be } for (InterceptorInfo interceptor : bean.getBoundInterceptors()) { constructor.writeInstanceField( - FieldDescriptor.of(beanCreator.getClassName(), interceptorToProviderField.get(interceptor), InjectableInterceptor.class.getName()), + FieldDescriptor.of(beanCreator.getClassName(), interceptorToProviderField.get(interceptor), + InjectableInterceptor.class.getName()), constructor.getThis(), constructor.getMethodParam(paramIdx++)); } @@ -564,8 +603,11 @@ protected MethodCreator initConstructor(ClassOutput classOutput, ClassCreator be for (org.jboss.jandex.Type type : bean.getTypes()) { constructor.invokeInterfaceMethod(MethodDescriptors.SET_ADD, typesHandle, Types.getTypeHandle(constructor, type)); } - ResultHandle unmodifiableTypesHandle = constructor.invokeStaticMethod(MethodDescriptors.COLLECTIONS_UNMODIFIABLE_SET, typesHandle); - constructor.writeInstanceField(FieldDescriptor.of(beanCreator.getClassName(), FIELD_NAME_BEAN_TYPES, Set.class.getName()), constructor.getThis(), + ResultHandle unmodifiableTypesHandle = constructor.invokeStaticMethod(MethodDescriptors.COLLECTIONS_UNMODIFIABLE_SET, + typesHandle); + constructor.writeInstanceField( + FieldDescriptor.of(beanCreator.getClassName(), FIELD_NAME_BEAN_TYPES, Set.class.getName()), + constructor.getThis(), unmodifiableTypesHandle); // Qualifiers @@ -576,16 +618,21 @@ protected MethodCreator initConstructor(ClassOutput classOutput, ClassCreator be for (AnnotationInstance qualifierAnnotation : bean.getQualifiers()) { BuiltinQualifier qualifier = BuiltinQualifier.of(qualifierAnnotation); if (qualifier != null) { - constructor.invokeInterfaceMethod(MethodDescriptors.SET_ADD, qualifiersHandle, qualifier.getLiteralInstance(constructor)); + constructor.invokeInterfaceMethod(MethodDescriptors.SET_ADD, qualifiersHandle, + qualifier.getLiteralInstance(constructor)); } else { // Create annotation literal first ClassInfo qualifierClass = bean.getDeployment().getQualifier(qualifierAnnotation.name()); - constructor.invokeInterfaceMethod(MethodDescriptors.SET_ADD, qualifiersHandle, annotationLiterals.process(constructor, classOutput, - qualifierClass, qualifierAnnotation, Types.getPackageName(beanCreator.getClassName()))); + constructor.invokeInterfaceMethod(MethodDescriptors.SET_ADD, qualifiersHandle, + annotationLiterals.process(constructor, classOutput, + qualifierClass, qualifierAnnotation, Types.getPackageName(beanCreator.getClassName()))); } } - ResultHandle unmodifiableQualifiersHandle = constructor.invokeStaticMethod(MethodDescriptors.COLLECTIONS_UNMODIFIABLE_SET, qualifiersHandle); - constructor.writeInstanceField(FieldDescriptor.of(beanCreator.getClassName(), FIELD_NAME_QUALIFIERS, Set.class.getName()), constructor.getThis(), + ResultHandle unmodifiableQualifiersHandle = constructor + .invokeStaticMethod(MethodDescriptors.COLLECTIONS_UNMODIFIABLE_SET, qualifiersHandle); + constructor.writeInstanceField( + FieldDescriptor.of(beanCreator.getClassName(), FIELD_NAME_QUALIFIERS, Set.class.getName()), + constructor.getThis(), unmodifiableQualifiersHandle); } @@ -596,8 +643,11 @@ protected MethodCreator initConstructor(ClassOutput classOutput, ClassCreator be constructor.invokeInterfaceMethod(MethodDescriptors.SET_ADD, stereotypesHandle, constructor.loadClass(stereotype.getTarget().name().toString())); } - ResultHandle unmodifiableStereotypesHandle = constructor.invokeStaticMethod(MethodDescriptors.COLLECTIONS_UNMODIFIABLE_SET, stereotypesHandle); - constructor.writeInstanceField(FieldDescriptor.of(beanCreator.getClassName(), FIELD_NAME_STEREOTYPES, Set.class.getName()), constructor.getThis(), + ResultHandle unmodifiableStereotypesHandle = constructor + .invokeStaticMethod(MethodDescriptors.COLLECTIONS_UNMODIFIABLE_SET, stereotypesHandle); + constructor.writeInstanceField( + FieldDescriptor.of(beanCreator.getClassName(), FIELD_NAME_STEREOTYPES, Set.class.getName()), + constructor.getThis(), unmodifiableStereotypesHandle); } @@ -607,10 +657,13 @@ protected MethodCreator initConstructor(ClassOutput classOutput, ClassCreator be FunctionCreator func = constructor.createFunction(Supplier.class); BytecodeCreator funcBytecode = func.getBytecode(); funcBytecode - .returnValue(funcBytecode.newInstance(MethodDescriptor.ofConstructor(proxyTypeName, beanCreator.getClassName()), constructor.getThis())); + .returnValue(funcBytecode.newInstance( + MethodDescriptor.ofConstructor(proxyTypeName, beanCreator.getClassName()), constructor.getThis())); - ResultHandle proxyHandle = constructor.newInstance(MethodDescriptor.ofConstructor(LazyValue.class, Supplier.class), func.getInstance()); - constructor.writeInstanceField(FieldDescriptor.of(beanCreator.getClassName(), "proxy", LazyValue.class.getName()), constructor.getThis(), + ResultHandle proxyHandle = constructor.newInstance(MethodDescriptor.ofConstructor(LazyValue.class, Supplier.class), + func.getInstance()); + constructor.writeInstanceField(FieldDescriptor.of(beanCreator.getClassName(), "proxy", LazyValue.class.getName()), + constructor.getThis(), proxyHandle); } @@ -618,26 +671,33 @@ protected MethodCreator initConstructor(ClassOutput classOutput, ClassCreator be } protected void implementDestroy(BeanInfo bean, ClassCreator beanCreator, String providerTypeName, - Map injectionPointToProviderField, ReflectionRegistration reflectionRegistration, boolean isApplicationClass) { + Map injectionPointToProviderField, ReflectionRegistration reflectionRegistration, + boolean isApplicationClass) { - MethodCreator destroy = beanCreator.getMethodCreator("destroy", void.class, providerTypeName, CreationalContext.class).setModifiers(ACC_PUBLIC); + MethodCreator destroy = beanCreator.getMethodCreator("destroy", void.class, providerTypeName, CreationalContext.class) + .setModifiers(ACC_PUBLIC); if (bean.isClassBean()) { if (!bean.isInterceptor()) { // PreDestroy interceptors if (!bean.getLifecycleInterceptors(InterceptionType.PRE_DESTROY).isEmpty()) { - destroy.invokeInterfaceMethod(MethodDescriptor.ofMethod(Subclass.class, "destroy", void.class), destroy.getMethodParam(0)); + destroy.invokeInterfaceMethod(MethodDescriptor.ofMethod(Subclass.class, "destroy", void.class), + destroy.getMethodParam(0)); } // PreDestroy callbacks - List preDestroyCallbacks = Beans.getCallbacks(bean.getTarget().get().asClass(), DotNames.PRE_DESTROY, + List preDestroyCallbacks = Beans.getCallbacks(bean.getTarget().get().asClass(), + DotNames.PRE_DESTROY, bean.getDeployment().getIndex()); for (MethodInfo callback : preDestroyCallbacks) { if (Modifier.isPrivate(callback.flags())) { - privateMembers.add(isApplicationClass, String.format("@PreDestroy callback %s#%s()", callback.declaringClass().name(), callback.name())); + privateMembers.add(isApplicationClass, String.format("@PreDestroy callback %s#%s()", + callback.declaringClass().name(), callback.name())); reflectionRegistration.registerMethod(callback); - destroy.invokeStaticMethod(MethodDescriptors.REFLECTIONS_INVOKE_METHOD, destroy.loadClass(callback.declaringClass().name().toString()), - destroy.load(callback.name()), destroy.newArray(Class.class, destroy.load(0)), destroy.getMethodParam(0), + destroy.invokeStaticMethod(MethodDescriptors.REFLECTIONS_INVOKE_METHOD, + destroy.loadClass(callback.declaringClass().name().toString()), + destroy.load(callback.name()), destroy.newArray(Class.class, destroy.load(0)), + destroy.getMethodParam(0), destroy.newArray(Object.class, destroy.load(0))); } else { // instance.superCoolDestroyCallback() @@ -656,14 +716,18 @@ protected void implementDestroy(BeanInfo bean, ClassCreator beanCreator, String MethodInfo disposerMethod = bean.getDisposer().getDisposerMethod(); ResultHandle declaringProviderHandle = destroy.readInstanceField( - FieldDescriptor.of(beanCreator.getClassName(), FIELD_NAME_DECLARING_PROVIDER, InjectableBean.class.getName()), destroy.getThis()); + FieldDescriptor.of(beanCreator.getClassName(), FIELD_NAME_DECLARING_PROVIDER, + InjectableBean.class.getName()), + destroy.getThis()); ResultHandle ctxHandle = destroy.newInstance(MethodDescriptor.ofConstructor(CreationalContextImpl.class)); - ResultHandle declaringProviderInstanceHandle = destroy.invokeInterfaceMethod(MethodDescriptors.INJECTABLE_REF_PROVIDER_GET, declaringProviderHandle, + ResultHandle declaringProviderInstanceHandle = destroy.invokeInterfaceMethod( + MethodDescriptors.INJECTABLE_REF_PROVIDER_GET, declaringProviderHandle, ctxHandle); if (bean.getDeclaringBean().getScope().isNormal()) { // We need to unwrap the client proxy - declaringProviderInstanceHandle = destroy.invokeInterfaceMethod(MethodDescriptors.CLIENT_PROXY_GET_CONTEXTUAL_INSTANCE, + declaringProviderInstanceHandle = destroy.invokeInterfaceMethod( + MethodDescriptors.CLIENT_PROXY_GET_CONTEXTUAL_INSTANCE, declaringProviderInstanceHandle); } @@ -676,8 +740,10 @@ protected void implementDestroy(BeanInfo bean, ClassCreator beanCreator, String } else { ResultHandle childCtxHandle = destroy.invokeStaticMethod(MethodDescriptors.CREATIONAL_CTX_CHILD, ctxHandle); ResultHandle providerHandle = destroy.readInstanceField(FieldDescriptor.of(beanCreator.getClassName(), - injectionPointToProviderField.get(injectionPointsIterator.next()), InjectableReferenceProvider.class.getName()), destroy.getThis()); - ResultHandle referenceHandle = destroy.invokeInterfaceMethod(MethodDescriptors.INJECTABLE_REF_PROVIDER_GET, providerHandle, childCtxHandle); + injectionPointToProviderField.get(injectionPointsIterator.next()), + InjectableReferenceProvider.class.getName()), destroy.getThis()); + ResultHandle referenceHandle = destroy.invokeInterfaceMethod(MethodDescriptors.INJECTABLE_REF_PROVIDER_GET, + providerHandle, childCtxHandle); referenceHandles[i] = referenceHandle; } } @@ -688,14 +754,17 @@ protected void implementDestroy(BeanInfo bean, ClassCreator beanCreator, String ResultHandle paramTypesArray = destroy.newArray(Class.class, destroy.load(referenceHandles.length)); ResultHandle argsArray = destroy.newArray(Object.class, destroy.load(referenceHandles.length)); for (int i = 0; i < referenceHandles.length; i++) { - destroy.writeArrayValue(paramTypesArray, i, destroy.loadClass(disposerMethod.parameters().get(i).name().toString())); + destroy.writeArrayValue(paramTypesArray, i, + destroy.loadClass(disposerMethod.parameters().get(i).name().toString())); destroy.writeArrayValue(argsArray, i, referenceHandles[i]); } reflectionRegistration.registerMethod(disposerMethod); - destroy.invokeStaticMethod(MethodDescriptors.REFLECTIONS_INVOKE_METHOD, destroy.loadClass(disposerMethod.declaringClass().name().toString()), + destroy.invokeStaticMethod(MethodDescriptors.REFLECTIONS_INVOKE_METHOD, + destroy.loadClass(disposerMethod.declaringClass().name().toString()), destroy.load(disposerMethod.name()), paramTypesArray, declaringProviderInstanceHandle, argsArray); } else { - destroy.invokeVirtualMethod(MethodDescriptor.of(disposerMethod), declaringProviderInstanceHandle, referenceHandles); + destroy.invokeVirtualMethod(MethodDescriptor.of(disposerMethod), declaringProviderInstanceHandle, + referenceHandles); } // Destroy @Dependent instances injected into method parameters of a disposer method @@ -703,7 +772,8 @@ protected void implementDestroy(BeanInfo bean, ClassCreator beanCreator, String // If the declaring bean is @Dependent we must destroy the instance afterwards if (BuiltinScope.DEPENDENT.is(bean.getDisposer().getDeclaringBean().getScope())) { - destroy.invokeInterfaceMethod(MethodDescriptors.INJECTABLE_BEAN_DESTROY, declaringProviderHandle, declaringProviderInstanceHandle, ctxHandle); + destroy.invokeInterfaceMethod(MethodDescriptors.INJECTABLE_BEAN_DESTROY, declaringProviderHandle, + declaringProviderInstanceHandle, ctxHandle); } // ctx.release() destroy.invokeInterfaceMethod(MethodDescriptors.CREATIONAL_CTX_RELEASE, destroy.getMethodParam(1)); @@ -714,16 +784,21 @@ protected void implementDestroy(BeanInfo bean, ClassCreator beanCreator, String } // Bridge method needed - MethodCreator bridgeDestroy = beanCreator.getMethodCreator("destroy", void.class, Object.class, CreationalContext.class).setModifiers(ACC_PUBLIC); - bridgeDestroy.returnValue(bridgeDestroy.invokeVirtualMethod(destroy.getMethodDescriptor(), bridgeDestroy.getThis(), bridgeDestroy.getMethodParam(0), + MethodCreator bridgeDestroy = beanCreator.getMethodCreator("destroy", void.class, Object.class, CreationalContext.class) + .setModifiers(ACC_PUBLIC); + bridgeDestroy.returnValue(bridgeDestroy.invokeVirtualMethod(destroy.getMethodDescriptor(), bridgeDestroy.getThis(), + bridgeDestroy.getMethodParam(0), bridgeDestroy.getMethodParam(1))); } - protected void implementCreate(ClassOutput classOutput, ClassCreator beanCreator, BeanInfo bean, String providerTypeName, String baseName, - Map injectionPointToProviderField, Map interceptorToProviderField, + protected void implementCreate(ClassOutput classOutput, ClassCreator beanCreator, BeanInfo bean, String providerTypeName, + String baseName, + Map injectionPointToProviderField, + Map interceptorToProviderField, ReflectionRegistration reflectionRegistration, String targetPackage, boolean isApplicationClass) { - MethodCreator create = beanCreator.getMethodCreator("create", providerTypeName, CreationalContext.class).setModifiers(ACC_PUBLIC); + MethodCreator create = beanCreator.getMethodCreator("create", providerTypeName, CreationalContext.class) + .setModifiers(ACC_PUBLIC); AssignableResultHandle instanceHandle; if (bean.isClassBean()) { @@ -752,12 +827,16 @@ protected void implementCreate(ClassOutput classOutput, ClassCreator beanCreator wraps.addAll(postConstructs.interceptors); for (InterceptorInfo interceptor : wraps) { ResultHandle interceptorProvider = create.readInstanceField( - FieldDescriptor.of(beanCreator.getClassName(), interceptorToProviderField.get(interceptor), InjectableInterceptor.class.getName()), + FieldDescriptor.of(beanCreator.getClassName(), interceptorToProviderField.get(interceptor), + InjectableInterceptor.class.getName()), create.getThis()); - ResultHandle interceptorInstanceHandle = create.invokeInterfaceMethod(MethodDescriptors.INJECTABLE_REF_PROVIDER_GET, interceptorProvider, + ResultHandle interceptorInstanceHandle = create.invokeInterfaceMethod( + MethodDescriptors.INJECTABLE_REF_PROVIDER_GET, interceptorProvider, create.getMethodParam(0)); - ResultHandle wrapHandle = create.invokeStaticMethod(MethodDescriptor.ofMethod(InitializedInterceptor.class, "of", - InitializedInterceptor.class, Object.class, InjectableInterceptor.class), interceptorInstanceHandle, interceptorProvider); + ResultHandle wrapHandle = create.invokeStaticMethod( + MethodDescriptor.ofMethod(InitializedInterceptor.class, "of", + InitializedInterceptor.class, Object.class, InjectableInterceptor.class), + interceptorInstanceHandle, interceptorProvider); interceptorToWrap.put(interceptor, wrapHandle); } @@ -766,16 +845,21 @@ protected void implementCreate(ClassOutput classOutput, ClassCreator beanCreator postConstructsHandle = create.newInstance(MethodDescriptor.ofConstructor(ArrayList.class)); for (InterceptorInfo interceptor : postConstructs.interceptors) { ResultHandle interceptorHandle = create.readInstanceField(FieldDescriptor.of(beanCreator.getClassName(), - interceptorToProviderField.get(interceptor), InjectableInterceptor.class.getName()), create.getThis()); - ResultHandle childCtxHandle = create.invokeStaticMethod(MethodDescriptors.CREATIONAL_CTX_CHILD, create.getMethodParam(0)); - ResultHandle interceptorInstanceHandle = create.invokeInterfaceMethod(MethodDescriptors.INJECTABLE_REF_PROVIDER_GET, interceptorHandle, + interceptorToProviderField.get(interceptor), InjectableInterceptor.class.getName()), + create.getThis()); + ResultHandle childCtxHandle = create.invokeStaticMethod(MethodDescriptors.CREATIONAL_CTX_CHILD, + create.getMethodParam(0)); + ResultHandle interceptorInstanceHandle = create.invokeInterfaceMethod( + MethodDescriptors.INJECTABLE_REF_PROVIDER_GET, interceptorHandle, childCtxHandle); - ResultHandle interceptorInvocationHandle = create.invokeStaticMethod(MethodDescriptors.INTERCEPTOR_INVOCATION_POST_CONSTRUCT, + ResultHandle interceptorInvocationHandle = create.invokeStaticMethod( + MethodDescriptors.INTERCEPTOR_INVOCATION_POST_CONSTRUCT, interceptorHandle, interceptorInstanceHandle); // postConstructs.add(InterceptorInvocation.postConstruct(interceptor,interceptor.get(CreationalContextImpl.child(ctx)))) - create.invokeInterfaceMethod(MethodDescriptors.LIST_ADD, postConstructsHandle, interceptorInvocationHandle); + create.invokeInterfaceMethod(MethodDescriptors.LIST_ADD, postConstructsHandle, + interceptorInvocationHandle); } } if (!aroundConstructs.isEmpty()) { @@ -783,16 +867,21 @@ protected void implementCreate(ClassOutput classOutput, ClassCreator beanCreator aroundConstructsHandle = create.newInstance(MethodDescriptor.ofConstructor(ArrayList.class)); for (InterceptorInfo interceptor : aroundConstructs.interceptors) { ResultHandle interceptorHandle = create.readInstanceField(FieldDescriptor.of(beanCreator.getClassName(), - interceptorToProviderField.get(interceptor), InjectableInterceptor.class.getName()), create.getThis()); - ResultHandle childCtxHandle = create.invokeStaticMethod(MethodDescriptors.CREATIONAL_CTX_CHILD, create.getMethodParam(0)); - ResultHandle interceptorInstanceHandle = create.invokeInterfaceMethod(MethodDescriptors.INJECTABLE_REF_PROVIDER_GET, interceptorHandle, + interceptorToProviderField.get(interceptor), InjectableInterceptor.class.getName()), + create.getThis()); + ResultHandle childCtxHandle = create.invokeStaticMethod(MethodDescriptors.CREATIONAL_CTX_CHILD, + create.getMethodParam(0)); + ResultHandle interceptorInstanceHandle = create.invokeInterfaceMethod( + MethodDescriptors.INJECTABLE_REF_PROVIDER_GET, interceptorHandle, childCtxHandle); - ResultHandle interceptorInvocationHandle = create.invokeStaticMethod(MethodDescriptors.INTERCEPTOR_INVOCATION_AROUND_CONSTRUCT, + ResultHandle interceptorInvocationHandle = create.invokeStaticMethod( + MethodDescriptors.INTERCEPTOR_INVOCATION_AROUND_CONSTRUCT, interceptorHandle, interceptorInstanceHandle); // aroundConstructs.add(InterceptorInvocation.aroundConstruct(interceptor,interceptor.get(CreationalContextImpl.child(ctx)))) - create.invokeInterfaceMethod(MethodDescriptors.LIST_ADD, aroundConstructsHandle, interceptorInvocationHandle); + create.invokeInterfaceMethod(MethodDescriptors.LIST_ADD, aroundConstructsHandle, + interceptorInvocationHandle); } } } @@ -815,23 +904,27 @@ protected void implementCreate(ClassOutput classOutput, ClassCreator beanCreator create.writeArrayValue(paramsArray, iterator.nextIndex(), create.loadClass(iterator.next())); } paramsHandles[1] = paramsArray; - constructorHandle = create.invokeStaticMethod(MethodDescriptors.REFLECTIONS_FIND_CONSTRUCTOR, paramsHandles); + constructorHandle = create.invokeStaticMethod(MethodDescriptors.REFLECTIONS_FIND_CONSTRUCTOR, + paramsHandles); } else { // constructor = Reflections.findConstructor(Foo.class) ResultHandle[] paramsHandles = new ResultHandle[2]; paramsHandles[0] = create.loadClass(providerTypeName); paramsHandles[1] = create.newArray(Class.class, create.load(0)); - constructorHandle = create.invokeStaticMethod(MethodDescriptors.REFLECTIONS_FIND_CONSTRUCTOR, paramsHandles); + constructorHandle = create.invokeStaticMethod(MethodDescriptors.REFLECTIONS_FIND_CONSTRUCTOR, + paramsHandles); } - List providerHandles = newProviderHandles(bean, beanCreator, create, injectionPointToProviderField, interceptorToProviderField, + List providerHandles = newProviderHandles(bean, beanCreator, create, + injectionPointToProviderField, interceptorToProviderField, interceptorToWrap); // Forwarding function // Supplier forward = () -> new SimpleBean_Subclass(ctx,lifecycleInterceptorProvider1) FunctionCreator func = create.createFunction(Supplier.class); BytecodeCreator funcBytecode = func.getBytecode(); - ResultHandle retHandle = newInstanceHandle(bean, beanCreator, funcBytecode, create, providerTypeName, baseName, providerHandles, + ResultHandle retHandle = newInstanceHandle(bean, beanCreator, funcBytecode, create, providerTypeName, baseName, + providerHandles, reflectionRegistration, isApplicationClass); funcBytecode.returnValue(retHandle); // Interceptor bindings @@ -840,45 +933,58 @@ protected void implementCreate(ClassOutput classOutput, ClassCreator beanCreator // Create annotation literals first ClassInfo bindingClass = bean.getDeployment().getInterceptorBinding(binding.name()); create.invokeInterfaceMethod(MethodDescriptors.SET_ADD, bindingsHandle, - annotationLiterals.process(create, classOutput, bindingClass, binding, Types.getPackageName(beanCreator.getClassName()))); + annotationLiterals.process(create, classOutput, bindingClass, binding, + Types.getPackageName(beanCreator.getClassName()))); } // InvocationContextImpl.aroundConstruct(constructor,aroundConstructs,forward).proceed() - ResultHandle invocationContextHandle = create.invokeStaticMethod(MethodDescriptors.INVOCATION_CONTEXT_AROUND_CONSTRUCT, constructorHandle, + ResultHandle invocationContextHandle = create.invokeStaticMethod( + MethodDescriptors.INVOCATION_CONTEXT_AROUND_CONSTRUCT, constructorHandle, aroundConstructsHandle, func.getInstance(), bindingsHandle); TryBlock tryCatch = create.tryBlock(); CatchBlockCreator exceptionCatch = tryCatch.addCatch(Exception.class); // throw new RuntimeException(e) - exceptionCatch.throwException(RuntimeException.class, "Error invoking aroundConstructs", exceptionCatch.getCaughtException()); - tryCatch.assign(instanceHandle, tryCatch.invokeInterfaceMethod(MethodDescriptor.ofMethod(InvocationContext.class, "proceed", Object.class), - invocationContextHandle)); + exceptionCatch.throwException(RuntimeException.class, "Error invoking aroundConstructs", + exceptionCatch.getCaughtException()); + tryCatch.assign(instanceHandle, + tryCatch.invokeInterfaceMethod( + MethodDescriptor.ofMethod(InvocationContext.class, "proceed", Object.class), + invocationContextHandle)); } else { create.assign(instanceHandle, newInstanceHandle(bean, beanCreator, create, create, providerTypeName, baseName, - newProviderHandles(bean, beanCreator, create, injectionPointToProviderField, interceptorToProviderField, interceptorToWrap), + newProviderHandles(bean, beanCreator, create, injectionPointToProviderField, interceptorToProviderField, + interceptorToWrap), reflectionRegistration, isApplicationClass)); } // Perform field and initializer injections for (Injection fieldInjection : fieldInjections) { InjectionPointInfo injectionPoint = fieldInjection.injectionPoints.get(0); - ResultHandle childCtxHandle = create.invokeStaticMethod(MethodDescriptors.CREATIONAL_CTX_CHILD, create.getMethodParam(0)); + ResultHandle childCtxHandle = create.invokeStaticMethod(MethodDescriptors.CREATIONAL_CTX_CHILD, + create.getMethodParam(0)); ResultHandle providerHandle = create.readInstanceField(FieldDescriptor.of(beanCreator.getClassName(), - injectionPointToProviderField.get(injectionPoint), InjectableReferenceProvider.class.getName()), create.getThis()); - ResultHandle referenceHandle = create.invokeInterfaceMethod(MethodDescriptors.INJECTABLE_REF_PROVIDER_GET, providerHandle, childCtxHandle); + injectionPointToProviderField.get(injectionPoint), InjectableReferenceProvider.class.getName()), + create.getThis()); + ResultHandle referenceHandle = create.invokeInterfaceMethod(MethodDescriptors.INJECTABLE_REF_PROVIDER_GET, + providerHandle, childCtxHandle); FieldInfo injectedField = fieldInjection.target.asField(); if (isReflectionFallbackNeeded(injectedField, targetPackage)) { if (Modifier.isPrivate(injectedField.flags())) { - privateMembers.add(isApplicationClass, String.format("@Inject field %s#%s", fieldInjection.target.asField().declaringClass().name(), - fieldInjection.target.asField().name())); + privateMembers.add(isApplicationClass, + String.format("@Inject field %s#%s", fieldInjection.target.asField().declaringClass().name(), + fieldInjection.target.asField().name())); } reflectionRegistration.registerField(injectedField); - create.invokeStaticMethod(MethodDescriptors.REFLECTIONS_WRITE_FIELD, create.loadClass(injectedField.declaringClass().name().toString()), + create.invokeStaticMethod(MethodDescriptors.REFLECTIONS_WRITE_FIELD, + create.loadClass(injectedField.declaringClass().name().toString()), create.load(injectedField.name()), instanceHandle, referenceHandle); } else { - create.writeInstanceField(FieldDescriptor.of(providerTypeName, injectedField.name(), injectionPoint.getRequiredType().name().toString()), + create.writeInstanceField( + FieldDescriptor.of(providerTypeName, injectedField.name(), + injectionPoint.getRequiredType().name().toString()), instanceHandle, referenceHandle); } } @@ -886,10 +992,13 @@ protected void implementCreate(ClassOutput classOutput, ClassCreator beanCreator ResultHandle[] referenceHandles = new ResultHandle[methodInjection.injectionPoints.size()]; int paramIdx = 0; for (InjectionPointInfo injectionPoint : methodInjection.injectionPoints) { - ResultHandle childCtxHandle = create.invokeStaticMethod(MethodDescriptors.CREATIONAL_CTX_CHILD, create.getMethodParam(0)); + ResultHandle childCtxHandle = create.invokeStaticMethod(MethodDescriptors.CREATIONAL_CTX_CHILD, + create.getMethodParam(0)); ResultHandle providerHandle = create.readInstanceField(FieldDescriptor.of(beanCreator.getClassName(), - injectionPointToProviderField.get(injectionPoint), InjectableReferenceProvider.class.getName()), create.getThis()); - ResultHandle referenceHandle = create.invokeInterfaceMethod(MethodDescriptors.INJECTABLE_REF_PROVIDER_GET, providerHandle, childCtxHandle); + injectionPointToProviderField.get(injectionPoint), InjectableReferenceProvider.class.getName()), + create.getThis()); + ResultHandle referenceHandle = create.invokeInterfaceMethod(MethodDescriptors.INJECTABLE_REF_PROVIDER_GET, + providerHandle, childCtxHandle); referenceHandles[paramIdx++] = referenceHandle; } @@ -897,21 +1006,25 @@ protected void implementCreate(ClassOutput classOutput, ClassCreator beanCreator if (isReflectionFallbackNeeded(initializerMethod, targetPackage)) { if (Modifier.isPrivate(initializerMethod.flags())) { privateMembers.add(isApplicationClass, - String.format("@Inject initializer %s#%s()", initializerMethod.declaringClass().name(), initializerMethod.name())); + String.format("@Inject initializer %s#%s()", initializerMethod.declaringClass().name(), + initializerMethod.name())); } ResultHandle paramTypesArray = create.newArray(Class.class, create.load(referenceHandles.length)); ResultHandle argsArray = create.newArray(Object.class, create.load(referenceHandles.length)); for (int i = 0; i < referenceHandles.length; i++) { - create.writeArrayValue(paramTypesArray, i, create.loadClass(initializerMethod.parameters().get(i).name().toString())); + create.writeArrayValue(paramTypesArray, i, + create.loadClass(initializerMethod.parameters().get(i).name().toString())); create.writeArrayValue(argsArray, i, referenceHandles[i]); } reflectionRegistration.registerMethod(initializerMethod); create.invokeStaticMethod(MethodDescriptors.REFLECTIONS_INVOKE_METHOD, - create.loadClass(initializerMethod.declaringClass().name().toString()), create.load(methodInjection.target.asMethod().name()), + create.loadClass(initializerMethod.declaringClass().name().toString()), + create.load(methodInjection.target.asMethod().name()), paramTypesArray, instanceHandle, argsArray); } else { - create.invokeVirtualMethod(MethodDescriptor.of(methodInjection.target.asMethod()), instanceHandle, referenceHandles); + create.invokeVirtualMethod(MethodDescriptor.of(methodInjection.target.asMethod()), instanceHandle, + referenceHandles); } } @@ -925,30 +1038,37 @@ protected void implementCreate(ClassOutput classOutput, ClassCreator beanCreator // Create annotation literals first ClassInfo bindingClass = bean.getDeployment().getInterceptorBinding(binding.name()); create.invokeInterfaceMethod(MethodDescriptors.SET_ADD, bindingsHandle, - annotationLiterals.process(create, classOutput, bindingClass, binding, Types.getPackageName(beanCreator.getClassName()))); + annotationLiterals.process(create, classOutput, bindingClass, binding, + Types.getPackageName(beanCreator.getClassName()))); } // InvocationContextImpl.postConstruct(instance,postConstructs).proceed() - ResultHandle invocationContextHandle = create.invokeStaticMethod(MethodDescriptors.INVOCATION_CONTEXT_POST_CONSTRUCT, instanceHandle, + ResultHandle invocationContextHandle = create.invokeStaticMethod( + MethodDescriptors.INVOCATION_CONTEXT_POST_CONSTRUCT, instanceHandle, postConstructsHandle, bindingsHandle); TryBlock tryCatch = create.tryBlock(); CatchBlockCreator exceptionCatch = tryCatch.addCatch(Exception.class); // throw new RuntimeException(e) - exceptionCatch.throwException(RuntimeException.class, "Error invoking postConstructs", exceptionCatch.getCaughtException()); - tryCatch.invokeInterfaceMethod(MethodDescriptor.ofMethod(InvocationContext.class, "proceed", Object.class), invocationContextHandle); + exceptionCatch.throwException(RuntimeException.class, "Error invoking postConstructs", + exceptionCatch.getCaughtException()); + tryCatch.invokeInterfaceMethod(MethodDescriptor.ofMethod(InvocationContext.class, "proceed", Object.class), + invocationContextHandle); } // PostConstruct callbacks if (!bean.isInterceptor()) { - List postConstructCallbacks = Beans.getCallbacks(bean.getTarget().get().asClass(), DotNames.POST_CONSTRUCT, + List postConstructCallbacks = Beans.getCallbacks(bean.getTarget().get().asClass(), + DotNames.POST_CONSTRUCT, bean.getDeployment().getIndex()); for (MethodInfo callback : postConstructCallbacks) { if (Modifier.isPrivate(callback.flags())) { privateMembers.add(isApplicationClass, - String.format("@PostConstruct callback %s#%s()", callback.declaringClass().name(), callback.name())); + String.format("@PostConstruct callback %s#%s()", callback.declaringClass().name(), + callback.name())); reflectionRegistration.registerMethod(callback); - create.invokeStaticMethod(MethodDescriptors.REFLECTIONS_INVOKE_METHOD, create.loadClass(callback.declaringClass().name().toString()), + create.invokeStaticMethod(MethodDescriptors.REFLECTIONS_INVOKE_METHOD, + create.loadClass(callback.declaringClass().name().toString()), create.load(callback.name()), create.newArray(Class.class, create.load(0)), instanceHandle, create.newArray(Object.class, create.load(0))); } else { @@ -962,14 +1082,18 @@ protected void implementCreate(ClassOutput classOutput, ClassCreator beanCreator instanceHandle = create.createVariable(DescriptorUtils.extToInt(providerTypeName)); // instance = declaringProvider.get(new CreationalContextImpl<>()).produce() ResultHandle declaringProviderHandle = create.readInstanceField( - FieldDescriptor.of(beanCreator.getClassName(), FIELD_NAME_DECLARING_PROVIDER, InjectableBean.class.getName()), create.getThis()); + FieldDescriptor.of(beanCreator.getClassName(), FIELD_NAME_DECLARING_PROVIDER, + InjectableBean.class.getName()), + create.getThis()); ResultHandle ctxHandle = create.newInstance(MethodDescriptor.ofConstructor(CreationalContextImpl.class)); - ResultHandle declaringProviderInstanceHandle = create.invokeInterfaceMethod(MethodDescriptors.INJECTABLE_REF_PROVIDER_GET, declaringProviderHandle, + ResultHandle declaringProviderInstanceHandle = create.invokeInterfaceMethod( + MethodDescriptors.INJECTABLE_REF_PROVIDER_GET, declaringProviderHandle, ctxHandle); if (bean.getDeclaringBean().getScope().isNormal()) { // We need to unwrap the client proxy - declaringProviderInstanceHandle = create.invokeInterfaceMethod(MethodDescriptors.CLIENT_PROXY_GET_CONTEXTUAL_INSTANCE, + declaringProviderInstanceHandle = create.invokeInterfaceMethod( + MethodDescriptors.CLIENT_PROXY_GET_CONTEXTUAL_INSTANCE, declaringProviderInstanceHandle); } @@ -977,33 +1101,41 @@ protected void implementCreate(ClassOutput classOutput, ClassCreator beanCreator ResultHandle[] referenceHandles = new ResultHandle[injectionPoints.size()]; int paramIdx = 0; for (InjectionPointInfo injectionPoint : injectionPoints) { - ResultHandle childCtxHandle = create.invokeStaticMethod(MethodDescriptors.CREATIONAL_CTX_CHILD, create.getMethodParam(0)); + ResultHandle childCtxHandle = create.invokeStaticMethod(MethodDescriptors.CREATIONAL_CTX_CHILD, + create.getMethodParam(0)); ResultHandle providerHandle = create.readInstanceField(FieldDescriptor.of(beanCreator.getClassName(), - injectionPointToProviderField.get(injectionPoint), InjectableReferenceProvider.class.getName()), create.getThis()); - ResultHandle referenceHandle = create.invokeInterfaceMethod(MethodDescriptors.INJECTABLE_REF_PROVIDER_GET, providerHandle, childCtxHandle); + injectionPointToProviderField.get(injectionPoint), InjectableReferenceProvider.class.getName()), + create.getThis()); + ResultHandle referenceHandle = create.invokeInterfaceMethod(MethodDescriptors.INJECTABLE_REF_PROVIDER_GET, + providerHandle, childCtxHandle); referenceHandles[paramIdx++] = referenceHandle; } MethodInfo producerMethod = bean.getTarget().get().asMethod(); if (Modifier.isPrivate(producerMethod.flags())) { - privateMembers.add(isApplicationClass, String.format("Producer method %s#%s()", producerMethod.declaringClass().name(), producerMethod.name())); + privateMembers.add(isApplicationClass, String.format("Producer method %s#%s()", + producerMethod.declaringClass().name(), producerMethod.name())); ResultHandle paramTypesArray = create.newArray(Class.class, create.load(referenceHandles.length)); ResultHandle argsArray = create.newArray(Object.class, create.load(referenceHandles.length)); for (int i = 0; i < referenceHandles.length; i++) { - create.writeArrayValue(paramTypesArray, i, create.loadClass(producerMethod.parameters().get(i).name().toString())); + create.writeArrayValue(paramTypesArray, i, + create.loadClass(producerMethod.parameters().get(i).name().toString())); create.writeArrayValue(argsArray, i, referenceHandles[i]); } reflectionRegistration.registerMethod(producerMethod); create.assign(instanceHandle, create.invokeStaticMethod(MethodDescriptors.REFLECTIONS_INVOKE_METHOD, - create.loadClass(producerMethod.declaringClass().name().toString()), create.load(producerMethod.name()), paramTypesArray, + create.loadClass(producerMethod.declaringClass().name().toString()), create.load(producerMethod.name()), + paramTypesArray, declaringProviderInstanceHandle, argsArray)); } else { - create.assign(instanceHandle, create.invokeVirtualMethod(MethodDescriptor.of(producerMethod), declaringProviderInstanceHandle, referenceHandles)); + create.assign(instanceHandle, create.invokeVirtualMethod(MethodDescriptor.of(producerMethod), + declaringProviderInstanceHandle, referenceHandles)); } // If the declaring bean is @Dependent we must destroy the instance afterwards if (BuiltinScope.DEPENDENT.is(bean.getDeclaringBean().getScope())) { - create.invokeInterfaceMethod(MethodDescriptors.INJECTABLE_BEAN_DESTROY, declaringProviderHandle, declaringProviderInstanceHandle, ctxHandle); + create.invokeInterfaceMethod(MethodDescriptors.INJECTABLE_BEAN_DESTROY, declaringProviderHandle, + declaringProviderInstanceHandle, ctxHandle); } create.returnValue(instanceHandle); @@ -1014,29 +1146,37 @@ protected void implementCreate(ClassOutput classOutput, ClassCreator beanCreator FieldInfo producerField = bean.getTarget().get().asField(); ResultHandle declaringProviderHandle = create.readInstanceField( - FieldDescriptor.of(beanCreator.getClassName(), FIELD_NAME_DECLARING_PROVIDER, InjectableBean.class.getName()), create.getThis()); + FieldDescriptor.of(beanCreator.getClassName(), FIELD_NAME_DECLARING_PROVIDER, + InjectableBean.class.getName()), + create.getThis()); ResultHandle ctxHandle = create.newInstance(MethodDescriptor.ofConstructor(CreationalContextImpl.class)); - ResultHandle declaringProviderInstanceHandle = create.invokeInterfaceMethod(MethodDescriptors.INJECTABLE_REF_PROVIDER_GET, declaringProviderHandle, + ResultHandle declaringProviderInstanceHandle = create.invokeInterfaceMethod( + MethodDescriptors.INJECTABLE_REF_PROVIDER_GET, declaringProviderHandle, ctxHandle); if (bean.getDeclaringBean().getScope().isNormal()) { // We need to unwrap the client proxy - declaringProviderInstanceHandle = create.invokeInterfaceMethod(MethodDescriptors.CLIENT_PROXY_GET_CONTEXTUAL_INSTANCE, + declaringProviderInstanceHandle = create.invokeInterfaceMethod( + MethodDescriptors.CLIENT_PROXY_GET_CONTEXTUAL_INSTANCE, declaringProviderInstanceHandle); } if (Modifier.isPrivate(producerField.flags())) { - privateMembers.add(isApplicationClass, String.format("Producer field %s#%s", producerField.declaringClass().name(), producerField.name())); + privateMembers.add(isApplicationClass, + String.format("Producer field %s#%s", producerField.declaringClass().name(), producerField.name())); reflectionRegistration.registerField(producerField); create.assign(instanceHandle, create.invokeStaticMethod(MethodDescriptors.REFLECTIONS_READ_FIELD, - create.loadClass(producerField.declaringClass().name().toString()), create.load(producerField.name()), declaringProviderInstanceHandle)); + create.loadClass(producerField.declaringClass().name().toString()), create.load(producerField.name()), + declaringProviderInstanceHandle)); } else { - create.assign(instanceHandle, create.readInstanceField(FieldDescriptor.of(producerField), declaringProviderInstanceHandle)); + create.assign(instanceHandle, + create.readInstanceField(FieldDescriptor.of(producerField), declaringProviderInstanceHandle)); } // If the declaring bean is @Dependent we must destroy the instance afterwards if (BuiltinScope.DEPENDENT.is(bean.getDeclaringBean().getScope())) { - create.invokeInterfaceMethod(MethodDescriptors.INJECTABLE_BEAN_DESTROY, declaringProviderHandle, declaringProviderInstanceHandle, ctxHandle); + create.invokeInterfaceMethod(MethodDescriptors.INJECTABLE_BEAN_DESTROY, declaringProviderHandle, + declaringProviderInstanceHandle, ctxHandle); } create.returnValue(instanceHandle); @@ -1045,12 +1185,15 @@ protected void implementCreate(ClassOutput classOutput, ClassCreator beanCreator } // Bridge method needed - MethodCreator bridgeCreate = beanCreator.getMethodCreator("create", Object.class, CreationalContext.class).setModifiers(ACC_PUBLIC | ACC_BRIDGE); - bridgeCreate.returnValue(bridgeCreate.invokeVirtualMethod(create.getMethodDescriptor(), bridgeCreate.getThis(), bridgeCreate.getMethodParam(0))); + MethodCreator bridgeCreate = beanCreator.getMethodCreator("create", Object.class, CreationalContext.class) + .setModifiers(ACC_PUBLIC | ACC_BRIDGE); + bridgeCreate.returnValue(bridgeCreate.invokeVirtualMethod(create.getMethodDescriptor(), bridgeCreate.getThis(), + bridgeCreate.getMethodParam(0))); } private List newProviderHandles(BeanInfo bean, ClassCreator beanCreator, MethodCreator createMethod, - Map injectionPointToProviderField, Map interceptorToProviderField, + Map injectionPointToProviderField, + Map interceptorToProviderField, Map interceptorToWrap) { List providerHandles = new ArrayList<>(); @@ -1059,9 +1202,12 @@ private List newProviderHandles(BeanInfo bean, ClassCreator beanCr if (constructorInjection.isPresent()) { for (InjectionPointInfo injectionPoint : constructorInjection.get().injectionPoints) { ResultHandle providerHandle = createMethod.readInstanceField(FieldDescriptor.of(beanCreator.getClassName(), - injectionPointToProviderField.get(injectionPoint), InjectableReferenceProvider.class.getName()), createMethod.getThis()); - ResultHandle childCtx = createMethod.invokeStaticMethod(MethodDescriptors.CREATIONAL_CTX_CHILD, createMethod.getMethodParam(0)); - providerHandles.add(createMethod.invokeInterfaceMethod(MethodDescriptors.INJECTABLE_REF_PROVIDER_GET, providerHandle, childCtx)); + injectionPointToProviderField.get(injectionPoint), InjectableReferenceProvider.class.getName()), + createMethod.getThis()); + ResultHandle childCtx = createMethod.invokeStaticMethod(MethodDescriptors.CREATIONAL_CTX_CHILD, + createMethod.getMethodParam(0)); + providerHandles.add(createMethod.invokeInterfaceMethod(MethodDescriptors.INJECTABLE_REF_PROVIDER_GET, + providerHandle, childCtx)); } } if (bean.isSubclassRequired()) { @@ -1071,7 +1217,8 @@ private List newProviderHandles(BeanInfo bean, ClassCreator beanCr providerHandles.add(wrapped); } else { providerHandles.add(createMethod.readInstanceField( - FieldDescriptor.of(beanCreator.getClassName(), interceptorToProviderField.get(interceptor), InjectableInterceptor.class.getName()), + FieldDescriptor.of(beanCreator.getClassName(), interceptorToProviderField.get(interceptor), + InjectableInterceptor.class.getName()), createMethod.getThis())); } } @@ -1079,12 +1226,15 @@ private List newProviderHandles(BeanInfo bean, ClassCreator beanCr return providerHandles; } - private ResultHandle newInstanceHandle(BeanInfo bean, ClassCreator beanCreator, BytecodeCreator creator, MethodCreator createMethod, - String providerTypeName, String baseName, List providerHandles, ReflectionRegistration registration, boolean isApplicationClass) { + private ResultHandle newInstanceHandle(BeanInfo bean, ClassCreator beanCreator, BytecodeCreator creator, + MethodCreator createMethod, + String providerTypeName, String baseName, List providerHandles, ReflectionRegistration registration, + boolean isApplicationClass) { Optional constructorInjection = bean.getConstructorInjection(); MethodInfo constructor = constructorInjection.isPresent() ? constructorInjection.get().target.asMethod() : null; - List injectionPoints = constructorInjection.isPresent() ? constructorInjection.get().injectionPoints : Collections.emptyList(); + List injectionPoints = constructorInjection.isPresent() ? constructorInjection.get().injectionPoints + : Collections.emptyList(); if (bean.isSubclassRequired()) { // new SimpleBean_Subclass(foo,ctx,lifecycleInterceptorProvider1) @@ -1110,20 +1260,24 @@ private ResultHandle newInstanceHandle(BeanInfo bean, ClassCreator beanCreator, } return creator.newInstance( - MethodDescriptor.ofConstructor(SubclassGenerator.generatedName(bean.getProviderType().name(), baseName), paramTypes.toArray(new String[0])), + MethodDescriptor.ofConstructor(SubclassGenerator.generatedName(bean.getProviderType().name(), baseName), + paramTypes.toArray(new String[0])), paramHandles.toArray(new ResultHandle[0])); } else if (constructorInjection.isPresent()) { if (Modifier.isPrivate(constructor.flags())) { - privateMembers.add(isApplicationClass, String.format("Bean constructor %s on %s", constructor, constructor.declaringClass().name())); + privateMembers.add(isApplicationClass, + String.format("Bean constructor %s on %s", constructor, constructor.declaringClass().name())); ResultHandle paramTypesArray = creator.newArray(Class.class, creator.load(providerHandles.size())); ResultHandle argsArray = creator.newArray(Object.class, creator.load(providerHandles.size())); for (int i = 0; i < injectionPoints.size(); i++) { - creator.writeArrayValue(paramTypesArray, i, creator.loadClass(injectionPoints.get(i).getRequiredType().name().toString())); + creator.writeArrayValue(paramTypesArray, i, + creator.loadClass(injectionPoints.get(i).getRequiredType().name().toString())); creator.writeArrayValue(argsArray, i, providerHandles.get(i)); } registration.registerMethod(constructor); - return creator.invokeStaticMethod(MethodDescriptors.REFLECTIONS_NEW_INSTANCE, creator.loadClass(constructor.declaringClass().name().toString()), + return creator.invokeStaticMethod(MethodDescriptors.REFLECTIONS_NEW_INSTANCE, + creator.loadClass(constructor.declaringClass().name().toString()), paramTypesArray, argsArray); } else { // new SimpleBean(foo) @@ -1132,13 +1286,15 @@ private ResultHandle newInstanceHandle(BeanInfo bean, ClassCreator beanCreator, InjectionPointInfo injectionPoint = iterator.next(); paramTypes[iterator.previousIndex()] = injectionPoint.getRequiredType().name().toString(); } - return creator.newInstance(MethodDescriptor.ofConstructor(providerTypeName, paramTypes), providerHandles.toArray(new ResultHandle[0])); + return creator.newInstance(MethodDescriptor.ofConstructor(providerTypeName, paramTypes), + providerHandles.toArray(new ResultHandle[0])); } } else { MethodInfo noArgsConstructor = bean.getTarget().get().asClass().method(Methods.INIT); if (Modifier.isPrivate(noArgsConstructor.flags())) { privateMembers.add(isApplicationClass, - String.format("Bean constructor %s on %s", noArgsConstructor, noArgsConstructor.declaringClass().name())); + String.format("Bean constructor %s on %s", noArgsConstructor, + noArgsConstructor.declaringClass().name())); ResultHandle paramTypesArray = creator.newArray(Class.class, creator.load(0)); ResultHandle argsArray = creator.newArray(Object.class, creator.load(0)); @@ -1161,15 +1317,18 @@ private ResultHandle newInstanceHandle(BeanInfo bean, ClassCreator beanCreator, */ protected void implementGet(BeanInfo bean, ClassCreator beanCreator, String providerTypeName) { - MethodCreator get = beanCreator.getMethodCreator("get", providerTypeName, CreationalContext.class).setModifiers(ACC_PUBLIC); + MethodCreator get = beanCreator.getMethodCreator("get", providerTypeName, CreationalContext.class) + .setModifiers(ACC_PUBLIC); if (BuiltinScope.DEPENDENT.is(bean.getScope())) { // Foo instance = create(ctx) ResultHandle instance = get.invokeVirtualMethod( - MethodDescriptor.ofMethod(beanCreator.getClassName(), "create", providerTypeName, CreationalContext.class), get.getThis(), + MethodDescriptor.ofMethod(beanCreator.getClassName(), "create", providerTypeName, CreationalContext.class), + get.getThis(), get.getMethodParam(0)); // CreationalContextImpl.addDependencyToParent(this,instance,ctx) - get.invokeStaticMethod(MethodDescriptors.CREATIONAL_CTX_ADD_DEP_TO_PARENT, get.getThis(), instance, get.getMethodParam(0)); + get.invokeStaticMethod(MethodDescriptors.CREATIONAL_CTX_ADD_DEP_TO_PARENT, get.getThis(), instance, + get.getMethodParam(0)); // return instance get.returnValue(instance); } else if (BuiltinScope.SINGLETON.is(bean.getScope())) { @@ -1177,22 +1336,28 @@ protected void implementGet(BeanInfo bean, ClassCreator beanCreator, String prov ResultHandle container = get.invokeStaticMethod(MethodDescriptors.ARC_CONTAINER); ResultHandle creationalContext = get.newInstance(MethodDescriptor.ofConstructor(CreationalContextImpl.class)); ResultHandle scope = get.loadClass(bean.getScope().getDotName().toString()); - ResultHandle context = get.invokeInterfaceMethod(MethodDescriptors.ARC_CONTAINER_GET_ACTIVE_CONTEXT, container, scope); - get.returnValue(get.invokeInterfaceMethod(MethodDescriptors.CONTEXT_GET, context, get.getThis(), creationalContext)); + ResultHandle context = get.invokeInterfaceMethod(MethodDescriptors.ARC_CONTAINER_GET_ACTIVE_CONTEXT, container, + scope); + get.returnValue( + get.invokeInterfaceMethod(MethodDescriptors.CONTEXT_GET, context, get.getThis(), creationalContext)); } else if (bean.getScope().isNormal()) { // return proxy.get() - ResultHandle proxy = get.readInstanceField(FieldDescriptor.of(beanCreator.getClassName(), FIELD_NAME_PROXY, LazyValue.class.getName()), get.getThis()); + ResultHandle proxy = get.readInstanceField( + FieldDescriptor.of(beanCreator.getClassName(), FIELD_NAME_PROXY, LazyValue.class.getName()), get.getThis()); get.returnValue(get.invokeVirtualMethod(MethodDescriptors.LAZY_VALUE_GET, proxy)); } else { ResultHandle instance = get.invokeVirtualMethod( - MethodDescriptor.ofMethod(beanCreator.getClassName(), "create", providerTypeName, CreationalContext.class), get.getThis(), + MethodDescriptor.ofMethod(beanCreator.getClassName(), "create", providerTypeName, CreationalContext.class), + get.getThis(), get.getMethodParam(0)); get.returnValue(instance); } // Bridge method needed - MethodCreator bridgeGet = beanCreator.getMethodCreator("get", Object.class, CreationalContext.class).setModifiers(ACC_PUBLIC | ACC_BRIDGE); - bridgeGet.returnValue(bridgeGet.invokeVirtualMethod(get.getMethodDescriptor(), bridgeGet.getThis(), bridgeGet.getMethodParam(0))); + MethodCreator bridgeGet = beanCreator.getMethodCreator("get", Object.class, CreationalContext.class) + .setModifiers(ACC_PUBLIC | ACC_BRIDGE); + bridgeGet.returnValue( + bridgeGet.invokeVirtualMethod(get.getMethodDescriptor(), bridgeGet.getThis(), bridgeGet.getMethodParam(0))); } /** @@ -1240,15 +1405,19 @@ protected void implementGetQualifiers(BeanInfo bean, ClassCreator beanCreator, F } protected void implementGetDeclaringBean(ClassCreator beanCreator) { - MethodCreator getDeclaringBean = beanCreator.getMethodCreator("getDeclaringBean", InjectableBean.class).setModifiers(ACC_PUBLIC); + MethodCreator getDeclaringBean = beanCreator.getMethodCreator("getDeclaringBean", InjectableBean.class) + .setModifiers(ACC_PUBLIC); getDeclaringBean.returnValue(getDeclaringBean.readInstanceField( - FieldDescriptor.of(beanCreator.getClassName(), FIELD_NAME_DECLARING_PROVIDER, InjectableBean.class.getName()), getDeclaringBean.getThis())); + FieldDescriptor.of(beanCreator.getClassName(), FIELD_NAME_DECLARING_PROVIDER, InjectableBean.class.getName()), + getDeclaringBean.getThis())); } protected void implementGetAlternativePriority(BeanInfo bean, ClassCreator beanCreator) { - MethodCreator getAlternativePriority = beanCreator.getMethodCreator("getAlternativePriority", Integer.class).setModifiers(ACC_PUBLIC); - getAlternativePriority.returnValue(getAlternativePriority.newInstance(MethodDescriptor.ofConstructor(Integer.class, int.class), - getAlternativePriority.load(bean.getAlternativePriority()))); + MethodCreator getAlternativePriority = beanCreator.getMethodCreator("getAlternativePriority", Integer.class) + .setModifiers(ACC_PUBLIC); + getAlternativePriority + .returnValue(getAlternativePriority.newInstance(MethodDescriptor.ofConstructor(Integer.class, int.class), + getAlternativePriority.load(bean.getAlternativePriority()))); } protected void implementGetStereotypes(BeanInfo bean, ClassCreator beanCreator, FieldDescriptor stereotypesField) { @@ -1260,7 +1429,6 @@ protected void implementGetBeanClass(BeanInfo bean, ClassCreator beanCreator) { MethodCreator getBeanClass = beanCreator.getMethodCreator("getBeanClass", Class.class).setModifiers(ACC_PUBLIC); getBeanClass.returnValue(getBeanClass.loadClass(bean.getBeanClass().toString())); } - protected void implementGetName(BeanInfo bean, ClassCreator beanCreator) { if (bean.getName() != null) { @@ -1269,39 +1437,34 @@ protected void implementGetName(BeanInfo bean, ClassCreator beanCreator) { getName.returnValue(getName.load(bean.getName())); } } - + private ResultHandle wrapCurrentInjectionPoint(ClassOutput classOutput, ClassCreator beanCreator, BeanInfo bean, MethodCreator constructor, InjectionPointInfo injectionPoint, int paramIdx) { - // Collect qualifiers - ResultHandle requiredQualifiersHandle = constructor.newInstance(MethodDescriptor.ofConstructor(HashSet.class)); - for (AnnotationInstance qualifierAnnotation : injectionPoint.getRequiredQualifiers()) { - BuiltinQualifier qualifier = BuiltinQualifier.of(qualifierAnnotation); - if (qualifier != null) { - constructor.invokeInterfaceMethod(MethodDescriptors.SET_ADD, requiredQualifiersHandle, - qualifier.getLiteralInstance(constructor)); - } else { - // Create annotation literal if needed - ClassInfo qualifierClass = bean.getDeployment().getQualifier(qualifierAnnotation.name()); - constructor.invokeInterfaceMethod(MethodDescriptors.SET_ADD, requiredQualifiersHandle, - annotationLiterals.process(constructor, - classOutput, qualifierClass, qualifierAnnotation, - Types.getPackageName(beanCreator.getClassName()))); - } - } - // Collect all annotations - ResultHandle annotationsHandle = constructor.newInstance(MethodDescriptor.ofConstructor(HashSet.class)); + ResultHandle requiredQualifiersHandle = collectQualifiers(classOutput, beanCreator, bean.getDeployment(), constructor, + injectionPoint, + annotationLiterals); + ResultHandle annotationsHandle = collectAnnotations(classOutput, beanCreator, bean.getDeployment(), constructor, + injectionPoint, annotationLiterals); + ResultHandle javaMemberHandle = getJavaMemberHandle(constructor, injectionPoint); + return constructor.newInstance( + MethodDescriptor.ofConstructor(CurrentInjectionPointProvider.class, InjectableBean.class, + InjectableReferenceProvider.class, java.lang.reflect.Type.class, + Set.class, Set.class, Member.class, int.class), + constructor.getThis(), constructor.getMethodParam(paramIdx), + Types.getTypeHandle(constructor, injectionPoint.getRequiredType()), + requiredQualifiersHandle, annotationsHandle, javaMemberHandle, constructor.load(injectionPoint.getPosition())); + } + + static ResultHandle getJavaMemberHandle(MethodCreator constructor, + InjectionPointInfo injectionPoint) { ResultHandle javaMemberHandle; - Collection annotations; if (Kind.FIELD.equals(injectionPoint.getTarget().kind())) { FieldInfo field = injectionPoint.getTarget().asField(); - annotations = bean.getDeployment().getAnnotations(field); javaMemberHandle = constructor.invokeStaticMethod(MethodDescriptors.REFLECTIONS_FIND_FIELD, constructor.loadClass(field.declaringClass().name().toString()), constructor.load(field.name())); } else { MethodInfo method = injectionPoint.getTarget().asMethod(); - annotations = InjectionPointInfo.getParameterAnnotations(bean.getDeployment(), - method, injectionPoint.getPosition()); if (method.name().equals(Methods.INIT)) { // Reflections.findConstructor(org.foo.SimpleBean.class,java.lang.String.class) ResultHandle[] paramsHandles = new ResultHandle[2]; @@ -1328,26 +1491,57 @@ private ResultHandle wrapCurrentInjectionPoint(ClassOutput classOutput, ClassCre javaMemberHandle = constructor.invokeStaticMethod(MethodDescriptors.REFLECTIONS_FIND_METHOD, paramsHandles); } } + return javaMemberHandle; + } + + static ResultHandle collectAnnotations(ClassOutput classOutput, ClassCreator beanCreator, BeanDeployment beanDeployment, + MethodCreator constructor, + InjectionPointInfo injectionPoint, AnnotationLiteralProcessor annotationLiterals) { + ResultHandle annotationsHandle = constructor.newInstance(MethodDescriptor.ofConstructor(HashSet.class)); + Collection annotations; + if (Kind.FIELD.equals(injectionPoint.getTarget().kind())) { + FieldInfo field = injectionPoint.getTarget().asField(); + annotations = beanDeployment.getAnnotations(field); + } else { + MethodInfo method = injectionPoint.getTarget().asMethod(); + annotations = Annotations.getParameterAnnotations(beanDeployment, + method, injectionPoint.getPosition()); + } for (AnnotationInstance annotation : annotations) { if (DotNames.INJECT.equals(annotation.name())) { constructor.invokeInterfaceMethod(MethodDescriptors.SET_ADD, annotationsHandle, constructor.readStaticField(FieldDescriptor.of(InjectLiteral.class, "INSTANCE", InjectLiteral.class))); } else { // Create annotation literal if needed - ClassInfo literalClass = bean.getDeployment().getIndex().getClassByName(annotation.name()); + ClassInfo literalClass = beanDeployment.getIndex().getClassByName(annotation.name()); constructor.invokeInterfaceMethod(MethodDescriptors.SET_ADD, annotationsHandle, annotationLiterals.process(constructor, classOutput, literalClass, annotation, Types.getPackageName(beanCreator.getClassName()))); } } - return constructor.newInstance( - MethodDescriptor.ofConstructor(CurrentInjectionPointProvider.class, InjectableBean.class, - InjectableReferenceProvider.class, java.lang.reflect.Type.class, - Set.class, Set.class, Member.class, int.class), - constructor.getThis(), constructor.getMethodParam(paramIdx), - Types.getTypeHandle(constructor, injectionPoint.getRequiredType()), - requiredQualifiersHandle, annotationsHandle, javaMemberHandle, constructor.load(injectionPoint.getPosition())); + return annotationsHandle; + } + + static ResultHandle collectQualifiers(ClassOutput classOutput, ClassCreator beanCreator, BeanDeployment beanDeployment, + MethodCreator constructor, + InjectionPointInfo injectionPoint, AnnotationLiteralProcessor annotationLiterals) { + ResultHandle requiredQualifiersHandle = constructor.newInstance(MethodDescriptor.ofConstructor(HashSet.class)); + for (AnnotationInstance qualifierAnnotation : injectionPoint.getRequiredQualifiers()) { + BuiltinQualifier qualifier = BuiltinQualifier.of(qualifierAnnotation); + if (qualifier != null) { + constructor.invokeInterfaceMethod(MethodDescriptors.SET_ADD, requiredQualifiersHandle, + qualifier.getLiteralInstance(constructor)); + } else { + // Create annotation literal if needed + ClassInfo qualifierClass = beanDeployment.getQualifier(qualifierAnnotation.name()); + constructor.invokeInterfaceMethod(MethodDescriptors.SET_ADD, requiredQualifiersHandle, + annotationLiterals.process(constructor, + classOutput, qualifierClass, qualifierAnnotation, + Types.getPackageName(beanCreator.getClassName()))); + } + } + return requiredQualifiersHandle; } } 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 e239d9b46eb6d..528a842484268 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 @@ -16,6 +16,8 @@ package io.quarkus.arc.processor; +import io.quarkus.arc.processor.Methods.MethodKey; +import io.quarkus.gizmo.MethodCreator; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collection; @@ -28,11 +30,8 @@ import java.util.Optional; import java.util.Set; import java.util.function.Consumer; -import java.util.stream.Collectors; - import javax.enterprise.inject.spi.DefinitionException; import javax.enterprise.inject.spi.InterceptionType; - import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationTarget; import org.jboss.jandex.AnnotationTarget.Kind; @@ -40,19 +39,19 @@ import org.jboss.jandex.DotName; import org.jboss.jandex.MethodInfo; import org.jboss.jandex.Type; -import io.quarkus.arc.processor.Methods.MethodKey; -import io.quarkus.gizmo.MethodCreator; /** * * @author Martin Kouba */ -public class BeanInfo { +public class BeanInfo implements InjectionTargetInfo { private final String identifier; private final ClassInfo implClazz; + private final Type providerType; + private final Optional target; private final BeanDeployment beanDeployment; @@ -92,12 +91,14 @@ public class BeanInfo { List injections, BeanInfo declaringBean, DisposerInfo disposer, Integer alternativePriority, List stereotypes, String name) { - this(null, target, beanDeployment, scope, types, qualifiers, injections, declaringBean, disposer, alternativePriority, + this(null, null, target, beanDeployment, scope, types, qualifiers, injections, declaringBean, disposer, + alternativePriority, stereotypes, name, null, null, Collections.emptyMap()); } - BeanInfo(ClassInfo implClazz, AnnotationTarget target, BeanDeployment beanDeployment, ScopeInfo scope, Set types, + BeanInfo(ClassInfo implClazz, Type providerType, AnnotationTarget target, BeanDeployment beanDeployment, ScopeInfo scope, + Set types, Set qualifiers, List injections, BeanInfo declaringBean, DisposerInfo disposer, Integer alternativePriority, List stereotypes, @@ -105,27 +106,13 @@ public class BeanInfo { Map params) { this.target = Optional.ofNullable(target); if (implClazz == null && target != null) { - switch (target.kind()) { - case CLASS: - implClazz = target.asClass(); - break; - case FIELD: - Type fieldType = target.asField().type(); - if (fieldType.kind() != org.jboss.jandex.Type.Kind.PRIMITIVE) { - implClazz = beanDeployment.getIndex().getClassByName(fieldType.name()); - } - break; - case METHOD: - Type returnType = target.asMethod().returnType(); - if (returnType.kind() != org.jboss.jandex.Type.Kind.PRIMITIVE) { - implClazz = beanDeployment.getIndex().getClassByName(returnType.name()); - } - break; - default: - break; - } + implClazz = initImplClazz(target, beanDeployment); } this.implClazz = implClazz; + if (providerType == null) { + providerType = initProviderType(target, implClazz); + } + this.providerType = providerType; this.beanDeployment = beanDeployment; this.scope = scope != null ? scope : BuiltinScope.DEPENDENT.getInfo(); this.types = types; @@ -152,6 +139,16 @@ public class BeanInfo { this.identifier = Hashes.sha1(toString()); } + @Override + public TargetKind kind() { + return TargetKind.BEAN; + } + + @Override + public BeanInfo asBean() { + return this; + } + public String getIdentifier() { return identifier; } @@ -162,7 +159,7 @@ public Optional getTarget() { /** * - * @return the impl class or null in case of a producer of a primitive type + * @return the impl class or null in case of a producer of a primitive type or an array */ public ClassInfo getImplClazz() { return implClazz; @@ -204,21 +201,7 @@ BeanDeployment getDeployment() { } Type getProviderType() { - if (target.isPresent()) { - switch (target.get().kind()) { - case CLASS: - return Types.getProviderType(target.get().asClass()); - case FIELD: - return target.get().asField().type(); - case METHOD: - return target.get().asMethod().returnType(); - default: - break; - } - } else if (implClazz != null) { - return Type.create(implClazz.name(), org.jboss.jandex.Type.Kind.CLASS); - } - throw new IllegalStateException("Cannot infer the provider type"); + return providerType; } public ScopeInfo getScope() { @@ -291,15 +274,26 @@ boolean hasDefaultDestroy() { * @return an ordered list of all interceptors associated with the bean */ List getBoundInterceptors() { + if (lifecycleInterceptors.isEmpty() && interceptedMethods.isEmpty()) { + return Collections.emptyList(); + } List bound = new ArrayList<>(); for (InterceptionInfo interception : lifecycleInterceptors.values()) { - bound.addAll(interception.interceptors); + for (InterceptorInfo interceptor : interception.interceptors) { + if (!bound.contains(interceptor)) { + bound.add(interceptor); + } + } } - if (!interceptedMethods.isEmpty()) { - bound.addAll( - interceptedMethods.values().stream().flatMap(ii -> ii.interceptors.stream()).collect(Collectors.toList())); + for (InterceptionInfo interception : interceptedMethods.values()) { + for (InterceptorInfo interceptor : interception.interceptors) { + if (!bound.contains(interceptor)) { + bound.add(interceptor); + } + } } - return bound.isEmpty() ? Collections.emptyList() : bound.stream().distinct().sorted().collect(Collectors.toList()); + Collections.sort(bound); + return bound; } public DisposerInfo getDisposer() { @@ -354,7 +348,8 @@ void validate(List errors) { if (noArgsConstructor != null && Modifier.isPrivate(noArgsConstructor.flags()) && classifier != null) { errors.add( new DefinitionException( - String.format("%s bean is not proxyable because it has a private no-args constructor: %s. To fix this problem, change the constructor to be package-private", + String.format( + "%s bean is not proxyable because it has a private no-args constructor: %s. To fix this problem, change the constructor to be package-private", classifier, this))); } } @@ -469,6 +464,48 @@ public String toString() { return builder.toString(); } + private Type initProviderType(AnnotationTarget target, ClassInfo implClazz) { + if (target != null) { + switch (target.kind()) { + case CLASS: + return Types.getProviderType(target.asClass()); + case FIELD: + return target.asField().type(); + case METHOD: + return target.asMethod().returnType(); + default: + break; + } + } else if (implClazz != null) { + return Type.create(implClazz.name(), org.jboss.jandex.Type.Kind.CLASS); + } + throw new IllegalStateException("Cannot infer the provider type"); + } + + private ClassInfo initImplClazz(AnnotationTarget target, BeanDeployment beanDeployment) { + switch (target.kind()) { + case CLASS: + return target.asClass(); + case FIELD: + Type fieldType = target.asField().type(); + if (fieldType.kind() != org.jboss.jandex.Type.Kind.PRIMITIVE + && fieldType.kind() != org.jboss.jandex.Type.Kind.ARRAY) { + return beanDeployment.getIndex().getClassByName(fieldType.name()); + } + break; + case METHOD: + Type returnType = target.asMethod().returnType(); + if (returnType.kind() != org.jboss.jandex.Type.Kind.PRIMITIVE + && returnType.kind() != org.jboss.jandex.Type.Kind.ARRAY) { + return beanDeployment.getIndex().getClassByName(returnType.name()); + } + break; + default: + break; + } + return null; + } + static class InterceptionInfo { static final InterceptionInfo EMPTY = new InterceptionInfo(Collections.emptyList(), Collections.emptySet()); @@ -492,6 +529,8 @@ static class Builder { private ClassInfo implClazz; + private Type providerType; + private AnnotationTarget target; private BeanDeployment beanDeployment; @@ -530,6 +569,11 @@ Builder implClazz(ClassInfo implClazz) { return this; } + Builder providerType(Type providerType) { + this.providerType = providerType; + return this; + } + Builder beanDeployment(BeanDeployment beanDeployment) { this.beanDeployment = beanDeployment; return this; @@ -601,7 +645,8 @@ Builder params(Map params) { } BeanInfo build() { - return new BeanInfo(implClazz, target, beanDeployment, scope, types, qualifiers, injections, declaringBean, + return new BeanInfo(implClazz, providerType, target, beanDeployment, scope, types, qualifiers, injections, + declaringBean, disposer, alternativePriority, stereotypes, name, creatorConsumer, destroyerConsumer, params); } 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 e64ff7a2fee45..e663d02c69467 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 @@ -16,48 +16,30 @@ package io.quarkus.arc.processor; +import io.quarkus.arc.processor.BuildExtension.BuildContext; +import io.quarkus.arc.processor.BuildExtension.Key; +import io.quarkus.arc.processor.DeploymentEnhancer.DeploymentContext; +import io.quarkus.arc.processor.ResourceOutput.Resource; +import io.quarkus.arc.processor.ResourceOutput.Resource.SpecialType; import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Predicate; import java.util.stream.Collectors; - -import javax.enterprise.context.BeforeDestroyed; -import javax.enterprise.context.Destroyed; -import javax.enterprise.context.Initialized; -import javax.enterprise.context.control.ActivateRequestContext; -import javax.enterprise.inject.Any; -import javax.enterprise.inject.Default; -import javax.inject.Named; - import org.jboss.jandex.AnnotationInstance; -import org.jboss.jandex.ClassInfo; import org.jboss.jandex.CompositeIndex; import org.jboss.jandex.DotName; -import org.jboss.jandex.Index; import org.jboss.jandex.IndexView; import org.jboss.jandex.Indexer; -import org.jboss.jandex.Type; import org.jboss.logging.Logger; -import io.quarkus.arc.ActivateRequestContextInterceptor; -import io.quarkus.arc.processor.BuildExtension.BuildContext; -import io.quarkus.arc.processor.BuildExtension.Key; -import io.quarkus.arc.processor.DeploymentEnhancer.DeploymentContext; -import io.quarkus.arc.processor.ResourceOutput.Resource; -import io.quarkus.arc.processor.ResourceOutput.Resource.SpecialType; - /** * * @author Martin Kouba @@ -70,7 +52,7 @@ public static Builder builder() { static final String DEFAULT_NAME = "Default"; - private static final Logger LOGGER = Logger.getLogger(BeanProcessor.class); + static final Logger LOGGER = Logger.getLogger(BeanProcessor.class); private final String name; @@ -99,10 +81,14 @@ public static Builder builder() { private final boolean removeUnusedBeans; private final List> unusedExclusions; - private BeanProcessor(String name, IndexView index, Collection additionalBeanDefiningAnnotations, ResourceOutput output, - boolean sharedAnnotationLiterals, ReflectionRegistration reflectionRegistration, List annotationTransformers, - Collection resourceAnnotations, List beanRegistrars, List contextRegistrars, List deploymentEnhancers, - List beanDeploymentValidators, Predicate applicationClassPredicate, boolean unusedBeansRemovalEnabled, + private BeanProcessor(String name, IndexView index, Collection additionalBeanDefiningAnnotations, + ResourceOutput output, + boolean sharedAnnotationLiterals, ReflectionRegistration reflectionRegistration, + List annotationTransformers, + Collection resourceAnnotations, List beanRegistrars, + List contextRegistrars, List deploymentEnhancers, + List beanDeploymentValidators, Predicate applicationClassPredicate, + boolean unusedBeansRemovalEnabled, List> unusedExclusions, Map> additionalStereotypes) { this.reflectionRegistration = reflectionRegistration; this.applicationClassPredicate = applicationClassPredicate; @@ -126,12 +112,12 @@ private BeanProcessor(String name, IndexView index, Collection clazz) { - index(indexer, clazz.getName()); + BeanArchives.index(indexer, clazz.getName()); } @Override @@ -168,12 +154,15 @@ public BeanDeployment process() throws IOException { beanDeployment.validate(buildContext, beanDeploymentValidators); PrivateMembersCollector privateMembers = new PrivateMembersCollector(); - AnnotationLiteralProcessor annotationLiterals = new AnnotationLiteralProcessor(sharedAnnotationLiterals, applicationClassPredicate); + AnnotationLiteralProcessor annotationLiterals = new AnnotationLiteralProcessor(sharedAnnotationLiterals, + applicationClassPredicate); BeanGenerator beanGenerator = new BeanGenerator(annotationLiterals, applicationClassPredicate, privateMembers); ClientProxyGenerator clientProxyGenerator = new ClientProxyGenerator(applicationClassPredicate); - InterceptorGenerator interceptorGenerator = new InterceptorGenerator(annotationLiterals, applicationClassPredicate, privateMembers); + InterceptorGenerator interceptorGenerator = new InterceptorGenerator(annotationLiterals, applicationClassPredicate, + privateMembers); SubclassGenerator subclassGenerator = new SubclassGenerator(annotationLiterals, applicationClassPredicate); - ObserverGenerator observerGenerator = new ObserverGenerator(annotationLiterals, applicationClassPredicate, privateMembers); + ObserverGenerator observerGenerator = new ObserverGenerator(annotationLiterals, applicationClassPredicate, + privateMembers); AnnotationLiteralGenerator annotationLiteralsGenerator = new AnnotationLiteralGenerator(); Map beanToGeneratedName = new HashMap<>(); @@ -199,11 +188,13 @@ public BeanDeployment process() throws IOException { if (SpecialType.BEAN.equals(resource.getSpecialType())) { if (bean.getScope().isNormal()) { // Generate client proxy - resources.addAll(clientProxyGenerator.generate(bean, resource.getFullyQualifiedName(), reflectionRegistration)); + resources.addAll( + clientProxyGenerator.generate(bean, resource.getFullyQualifiedName(), reflectionRegistration)); } beanToGeneratedName.put(bean, resource.getName()); if (bean.isSubclassRequired()) { - resources.addAll(subclassGenerator.generate(bean, resource.getFullyQualifiedName(), reflectionRegistration)); + resources.addAll( + subclassGenerator.generate(bean, resource.getFullyQualifiedName(), reflectionRegistration)); } } } @@ -222,7 +213,8 @@ public BeanDeployment process() throws IOException { privateMembers.log(); // Generate _ComponentsProvider - resources.addAll(new ComponentsProviderGenerator().generate(name, beanDeployment, beanToGeneratedName, observerToGeneratedName)); + resources.addAll( + new ComponentsProviderGenerator().generate(name, beanDeployment, beanToGeneratedName, observerToGeneratedName)); // Generate AnnotationLiterals if (annotationLiterals.hasLiteralsToGenerate()) { @@ -236,31 +228,6 @@ public BeanDeployment process() throws IOException { return beanDeployment; } - public static IndexView addBuiltinClasses(IndexView index) { - Indexer indexer = new Indexer(); - // Add builtin interceptors and bindings - index(indexer, ActivateRequestContext.class.getName()); - index(indexer, ActivateRequestContextInterceptor.class.getName()); - // Add builtin qualifiers if needed - if (index.getClassByName(DotNames.ANY) == null) { - index(indexer, Default.class.getName()); - index(indexer, Any.class.getName()); - index(indexer, Named.class.getName()); - index(indexer, Initialized.class.getName()); - index(indexer, BeforeDestroyed.class.getName()); - index(indexer, Destroyed.class.getName()); - } - return new IndexWrapper(CompositeIndex.create(index, indexer.complete())); - } - - private static void index(Indexer indexer, String className) { - try (InputStream stream = BeanProcessor.class.getClassLoader().getResourceAsStream(className.replace('.', '/') + ".class")) { - indexer.index(stream); - } catch (IOException e) { - throw new IllegalStateException("Failed to index: " + className, e); - } - } - public static class Builder { private String name = DEFAULT_NAME; @@ -304,7 +271,8 @@ public Builder setIndex(IndexView index) { return this; } - public Builder setAdditionalBeanDefiningAnnotations(Collection additionalBeanDefiningAnnotations) { + public Builder setAdditionalBeanDefiningAnnotations( + Collection additionalBeanDefiningAnnotations) { Objects.requireNonNull(additionalBeanDefiningAnnotations); this.additionalBeanDefiningAnnotations = additionalBeanDefiningAnnotations; return this; @@ -345,7 +313,7 @@ public Builder addBeanRegistrar(BeanRegistrar registrar) { this.beanRegistrars.add(registrar); return this; } - + public Builder addContextRegistrar(ContextRegistrar registrar) { this.contextRegistrars.add(registrar); return this; @@ -418,153 +386,6 @@ private static List initAndSort(List extensions return extensions; } - /** - * This wrapper is used to index JDK classes on demand. - */ - public static class IndexWrapper implements IndexView { - - private final Map additionalClasses; - - private final IndexView index; - - public IndexWrapper(IndexView index) { - this.index = index; - this.additionalClasses = new ConcurrentHashMap<>(); - } - - @Override - public Collection getKnownClasses() { - return index.getKnownClasses(); - } - - @Override - public ClassInfo getClassByName(DotName className) { - ClassInfo classInfo = index.getClassByName(className); - if (classInfo == null) { - return additionalClasses.computeIfAbsent(className, name -> { - LOGGER.debugf("Index: %s", className); - Indexer indexer = new Indexer(); - index(indexer, className.toString()); - Index index = indexer.complete(); - return index.getClassByName(name); - }); - } - return classInfo; - } - - @Override - public Collection getKnownDirectSubclasses(DotName className) { - if (additionalClasses.isEmpty()) { - return index.getKnownDirectSubclasses(className); - } - Set directSubclasses = new HashSet(index.getKnownDirectSubclasses(className)); - for (ClassInfo additional : additionalClasses.values()) { - if (className.equals(additional.superName())) { - directSubclasses.add(additional); - } - } - return directSubclasses; - } - - @Override - public Collection getAllKnownSubclasses(DotName className) { - if (additionalClasses.isEmpty()) { - return index.getAllKnownSubclasses(className); - } - final Set allKnown = new HashSet(); - final Set processedClasses = new HashSet(); - getAllKnownSubClasses(className, allKnown, processedClasses); - return allKnown; - } - - @Override - public Collection getKnownDirectImplementors(DotName className) { - if (additionalClasses.isEmpty()) { - return index.getKnownDirectImplementors(className); - } - Set directImplementors = new HashSet(index.getKnownDirectImplementors(className)); - for (ClassInfo additional : additionalClasses.values()) { - for (Type interfaceType : additional.interfaceTypes()) { - if (className.equals(interfaceType.name())) { - directImplementors.add(additional); - break; - } - } - } - return directImplementors; - } - - @Override - public Collection getAllKnownImplementors(DotName interfaceName) { - if (additionalClasses.isEmpty()) { - return index.getAllKnownImplementors(interfaceName); - } - final Set allKnown = new HashSet(); - final Set subInterfacesToProcess = new HashSet(); - final Set processedClasses = new HashSet(); - subInterfacesToProcess.add(interfaceName); - while (!subInterfacesToProcess.isEmpty()) { - final Iterator toProcess = subInterfacesToProcess.iterator(); - DotName name = toProcess.next(); - toProcess.remove(); - processedClasses.add(name); - getKnownImplementors(name, allKnown, subInterfacesToProcess, processedClasses); - } - return allKnown; - } - - @Override - public Collection getAnnotations(DotName annotationName) { - return index.getAnnotations(annotationName); - } - - private void getAllKnownSubClasses(DotName className, Set allKnown, Set processedClasses) { - final Set subClassesToProcess = new HashSet(); - subClassesToProcess.add(className); - while (!subClassesToProcess.isEmpty()) { - final Iterator toProcess = subClassesToProcess.iterator(); - DotName name = toProcess.next(); - toProcess.remove(); - processedClasses.add(name); - getAllKnownSubClasses(name, allKnown, subClassesToProcess, processedClasses); - } - } - - private void getAllKnownSubClasses(DotName name, Set allKnown, Set subClassesToProcess, Set processedClasses) { - final Collection directSubclasses = getKnownDirectSubclasses(name); - if (directSubclasses != null) { - for (final ClassInfo clazz : directSubclasses) { - final DotName className = clazz.name(); - if (!processedClasses.contains(className)) { - allKnown.add(clazz); - subClassesToProcess.add(className); - } - } - } - } - - private void getKnownImplementors(DotName name, Set allKnown, Set subInterfacesToProcess, Set processedClasses) { - final Collection list = getKnownDirectImplementors(name); - if (list != null) { - for (final ClassInfo clazz : list) { - final DotName className = clazz.name(); - if (!processedClasses.contains(className)) { - if (Modifier.isInterface(clazz.flags())) { - subInterfacesToProcess.add(className); - } else { - if (!allKnown.contains(clazz)) { - allKnown.add(clazz); - processedClasses.add(className); - getAllKnownSubClasses(className, allKnown, processedClasses); - } - } - } - } - } - } - - } - static class BuildContextImpl implements BuildContext { private final Map data = new ConcurrentHashMap<>(); @@ -615,13 +436,17 @@ private void log() { int limit = LOGGER.isDebugEnabled() ? Integer.MAX_VALUE : 3; String info = appDescriptions.stream().limit(limit).map(d -> "\t- " + d).collect(Collectors.joining(",\n")); if (appDescriptions.size() > limit) { - info += "\n\t- and " + (appDescriptions.size() - limit) + " more - please enable debug logging to see the full list"; + info += "\n\t- and " + (appDescriptions.size() - limit) + + " more - please enable debug logging to see the full list"; } - LOGGER.infof("Found unrecommended usage of private members (use package-private instead) in application beans:%n%s", info); + LOGGER.infof( + "Found unrecommended usage of private members (use package-private instead) in application beans:%n%s", + info); } // Log fwk problems if (fwkDescriptions != null && !fwkDescriptions.isEmpty()) { - LOGGER.debugf("Found unrecommended usage of private members (use package-private instead) in framework beans:%n%s", + LOGGER.debugf( + "Found unrecommended usage of private members (use package-private instead) in framework beans:%n%s", fwkDescriptions.stream().map(d -> "\t- " + d).collect(Collectors.joining(",\n"))); } } 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 445130bfc7b6d..7c471056fbe56 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 @@ -16,8 +16,8 @@ package io.quarkus.arc.processor; -import org.jboss.jandex.DotName; import io.quarkus.arc.InjectableBean; +import org.jboss.jandex.DotName; /** * Allows a build-time extension to register synthetic {@link InjectableBean} implementations. diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanResolver.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanResolver.java index e1a345d8ca2ff..1103d3bc2a066 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanResolver.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanResolver.java @@ -19,8 +19,8 @@ import static java.util.Collections.singletonList; import static org.jboss.jandex.Type.Kind.*; import static org.jboss.jandex.Type.Kind.CLASS; -import static io.quarkus.arc.processor.DotNames.*; +import io.quarkus.arc.processor.InjectionPointInfo.TypeAndQualifiers; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -31,10 +31,8 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.function.Function; - import org.jboss.jandex.*; import org.jboss.jandex.Type.Kind; -import io.quarkus.arc.processor.InjectionPointInfo.TypeAndQualifiers; /** * @@ -83,17 +81,18 @@ private List findMatching(TypeAndQualifiers typeAndQualifiers) { } boolean matches(Type requiredType, Type beanType) { + return matchesNoBoxing(Types.box(requiredType), Types.box(beanType)); + } + boolean matchesNoBoxing(Type requiredType, Type beanType) { if (requiredType == beanType) { return true; } - // TODO box types - if (ARRAY.equals(requiredType.kind())) { if (ARRAY.equals(beanType.kind())) { // Array types are considered to match only if their element types are identical - return matches(requiredType.asArrayType().component(), beanType.asArrayType().component()); + return matchesNoBoxing(requiredType.asArrayType().component(), beanType.asArrayType().component()); } } else if (CLASS.equals(requiredType.kind())) { if (CLASS.equals(beanType.kind())) { @@ -134,74 +133,48 @@ boolean matches(Type requiredType, Type beanType) { } return true; } - } else if (PRIMITIVE.equals(requiredType.kind())) { - return primitiveMatch(requiredType.asPrimitiveType().primitive(), beanType); } return false; } - static boolean primitiveMatch(PrimitiveType.Primitive requiredType, Type beanType) { - switch (requiredType) { - case INT: return (beanType.kind() == CLASS && beanType.asClassType().name().equals(INTEGER)) - || (beanType.kind() == PRIMITIVE && beanType.asPrimitiveType().primitive() == PrimitiveType.Primitive.INT); - - case LONG: return (beanType.kind() == CLASS && beanType.asClassType().name().equals(LONG)) - || (beanType.kind() == PRIMITIVE && beanType.asPrimitiveType().primitive() == PrimitiveType.Primitive.LONG); - - case SHORT: return (beanType.kind() == CLASS && beanType.asClassType().name().equals(SHORT)) - || (beanType.kind() == PRIMITIVE && beanType.asPrimitiveType().primitive() == PrimitiveType.Primitive.SHORT); - - case BYTE: return (beanType.kind() == CLASS && beanType.asClassType().name().equals(BYTE)) - || (beanType.kind() == PRIMITIVE && beanType.asPrimitiveType().primitive() == PrimitiveType.Primitive.BYTE); - - case FLOAT: return (beanType.kind() == CLASS && beanType.asClassType().name().equals(FLOAT)) - || (beanType.kind() == PRIMITIVE && beanType.asPrimitiveType().primitive() == PrimitiveType.Primitive.FLOAT); - - case DOUBLE: return (beanType.kind() == CLASS && beanType.asClassType().name().equals(DOUBLE)) - || (beanType.kind() == PRIMITIVE && beanType.asPrimitiveType().primitive() == PrimitiveType.Primitive.DOUBLE); - - case CHAR: return (beanType.kind() == CLASS && beanType.asClassType().name().equals(CHARACTER)) - || (beanType.kind() == PRIMITIVE && beanType.asPrimitiveType().primitive() == PrimitiveType.Primitive.CHAR); - - case BOOLEAN: return (beanType.kind() == CLASS && beanType.asClassType().name().equals(BOOLEAN)) - || (beanType.kind() == PRIMITIVE && beanType.asPrimitiveType().primitive() == PrimitiveType.Primitive.BOOLEAN); - - default: throw new IllegalArgumentException("Not supported yet"); - } - } - boolean parametersMatch(Type requiredParameter, Type beanParameter) { if (isActualType(requiredParameter) && isActualType(beanParameter)) { /* - * the required type parameter and the bean type parameter are actual types with identical raw type, and, if the type is parameterized, the bean + * the required type parameter and the bean type parameter are actual types with identical raw type, and, if the + * type is parameterized, the bean * type parameter is assignable to the required type parameter according to these rules, or */ return matches(requiredParameter, beanParameter); } if (WILDCARD_TYPE.equals(requiredParameter.kind()) && isActualType(beanParameter)) { /* - * the required type parameter is a wildcard, the bean type parameter is an actual type and the actual type is assignable to the upper bound, if + * the required type parameter is a wildcard, the bean type parameter is an actual type and the actual type is + * assignable to the upper bound, if * any, of the wildcard and assignable from the lower bound, if any, of the wildcard, or */ return parametersMatch(requiredParameter.asWildcardType(), beanParameter); } if (WILDCARD_TYPE.equals(requiredParameter.kind()) && TYPE_VARIABLE.equals(beanParameter.kind())) { /* - * the required type parameter is a wildcard, the bean type parameter is a type variable and the upper bound of the type variable is assignable to - * or assignable from the upper bound, if any, of the wildcard and assignable from the lower bound, if any, of the wildcard, or + * the required type parameter is a wildcard, the bean type parameter is a type variable and the upper bound of the + * type variable is assignable to + * or assignable from the upper bound, if any, of the wildcard and assignable from the lower bound, if any, of the + * wildcard, or */ return parametersMatch(requiredParameter.asWildcardType(), beanParameter.asTypeVariable()); } if (isActualType(requiredParameter) && TYPE_VARIABLE.equals(beanParameter.kind())) { /* - * the required type parameter is an actual type, the bean type parameter is a type variable and the actual type is assignable to the upper bound, + * the required type parameter is an actual type, the bean type parameter is a type variable and the actual type is + * assignable to the upper bound, * if any, of the type variable, or */ return parametersMatch(requiredParameter, beanParameter.asTypeVariable()); } if (TYPE_VARIABLE.equals(requiredParameter.kind()) && TYPE_VARIABLE.equals(beanParameter.kind())) { /* - * the required type parameter and the bean type parameter are both type variables and the upper bound of the required type parameter is assignable + * the required type parameter and the bean type parameter are both type variables and the upper bound of the + * required type parameter is assignable * to the upper bound, if any, of the bean type parameter */ return parametersMatch(requiredParameter.asTypeVariable(), beanParameter.asTypeVariable()); @@ -210,7 +183,8 @@ boolean parametersMatch(Type requiredParameter, Type beanParameter) { } boolean parametersMatch(WildcardType requiredParameter, Type beanParameter) { - return (lowerBoundsOfWildcardMatch(beanParameter, requiredParameter) && upperBoundsOfWildcardMatch(requiredParameter, beanParameter)); + return (lowerBoundsOfWildcardMatch(beanParameter, requiredParameter) + && upperBoundsOfWildcardMatch(requiredParameter, beanParameter)); } boolean parametersMatch(WildcardType requiredParameter, TypeVariable beanParameter) { @@ -238,7 +212,8 @@ boolean parametersMatch(TypeVariable requiredParameter, TypeVariable beanParamet } /** - * Returns true iff for each bound T, there is at least one bound from stricterBounds assignable to T. This reflects that + * Returns true iff for each bound T, there is at least one bound from stricterBounds assignable to T. + * This reflects that * stricterBounds are at least as strict as bounds are. */ boolean boundsMatch(List bounds, List stricterBounds) { @@ -287,7 +262,8 @@ boolean upperBoundsOfWildcardMatch(WildcardType requiredParameter, Type paramete } /* - * TypeVariable bounds are treated specially - CDI assignability rules are applied. Standard Java covariant assignability rules are applied to all other + * TypeVariable bounds are treated specially - CDI assignability rules are applied. Standard Java covariant assignability + * rules are applied to all other * types of bounds. This is not explicitly mentioned in the specification but is implied. */ List getUppermostTypeVariableBounds(TypeVariable bound) { diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Beans.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Beans.java index deddc92ed194c..a4e5696053bcb 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Beans.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Beans.java @@ -16,6 +16,8 @@ package io.quarkus.arc.processor; +import io.quarkus.arc.processor.InjectionPointInfo.TypeAndQualifiers; +import io.quarkus.arc.processor.InjectionTargetInfo.TargetKind; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -24,10 +26,9 @@ import java.util.List; import java.util.Set; import java.util.stream.Collectors; - import javax.enterprise.inject.AmbiguousResolutionException; import javax.enterprise.inject.UnsatisfiedResolutionException; - +import javax.enterprise.inject.spi.DefinitionException; import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationTarget; import org.jboss.jandex.AnnotationValue; @@ -39,8 +40,6 @@ import org.jboss.jandex.Type; import org.jboss.jandex.Type.Kind; -import io.quarkus.arc.processor.InjectionPointInfo.TypeAndQualifiers; - final class Beans { private Beans() { @@ -54,8 +53,8 @@ private Beans() { */ static BeanInfo createClassBean(ClassInfo beanClass, BeanDeployment beanDeployment) { Set qualifiers = new HashSet<>(); - ScopeInfo scope = null; - Set types = Types.getTypeClosure(beanClass, Collections.emptyMap(), beanDeployment); + List scopes = new ArrayList<>(); + Set types = Types.getClassBeanTypeClosure(beanClass, Collections.emptyMap(), beanDeployment); Integer alternativePriority = null; boolean isAlternative = false; List stereotypes = new ArrayList<>(); @@ -63,6 +62,7 @@ static BeanInfo createClassBean(ClassInfo beanClass, BeanDeployment beanDeployme for (AnnotationInstance annotation : beanDeployment.getAnnotations(beanClass)) { if (beanDeployment.getQualifier(annotation.name()) != null) { + // Qualifiers qualifiers.add(annotation); if (DotNames.NAMED.equals(annotation.name())) { AnnotationValue nameValue = annotation.value(); @@ -72,26 +72,39 @@ static BeanInfo createClassBean(ClassInfo beanClass, BeanDeployment beanDeployme name = getDefaultName(beanClass); } } - } else if (annotation.name() + continue; + } + if (annotation.name() .equals(DotNames.ALTERNATIVE)) { isAlternative = true; - } else if (annotation.name() + continue; + } + if (annotation.name() .equals(DotNames.PRIORITY)) { alternativePriority = annotation.value() .asInt(); - } else { - if (scope == null) { - scope = beanDeployment.getScope(annotation.name()); - } - StereotypeInfo stereotype = beanDeployment.getStereotype(annotation.name()); - if (stereotype != null) { - stereotypes.add(stereotype); - } + continue; + } + ScopeInfo scopeAnnotation = beanDeployment.getScope(annotation.name()); + if (scopeAnnotation != null) { + scopes.add(scopeAnnotation); + continue; + } + StereotypeInfo stereotype = beanDeployment.getStereotype(annotation.name()); + if (stereotype != null) { + stereotypes.add(stereotype); + continue; } } - if (scope == null) { + if (scopes.size() > 1) { + throw multipleScopesFound("Bean class " + beanClass, scopes); + } + ScopeInfo scope; + if (scopes.isEmpty()) { scope = initStereotypeScope(stereotypes, beanClass); + } else { + scope = scopes.get(0); } if (!isAlternative) { isAlternative = initStereotypeAlternative(stereotypes); @@ -100,7 +113,8 @@ static BeanInfo createClassBean(ClassInfo beanClass, BeanDeployment beanDeployme name = initStereotypeName(stereotypes, beanClass); } - BeanInfo bean = new BeanInfo(beanClass, beanDeployment, scope, types, qualifiers, Injection.forBean(beanClass, beanDeployment), null, null, + BeanInfo bean = new BeanInfo(beanClass, beanDeployment, scope, types, qualifiers, + Injection.forBean(beanClass, beanDeployment), null, null, isAlternative ? alternativePriority : null, stereotypes, name); return bean; } @@ -113,10 +127,11 @@ static BeanInfo createClassBean(ClassInfo beanClass, BeanDeployment beanDeployme * @param disposer * @return a new bean info */ - static BeanInfo createProducerMethod(MethodInfo producerMethod, BeanInfo declaringBean, BeanDeployment beanDeployment, DisposerInfo disposer) { + static BeanInfo createProducerMethod(MethodInfo producerMethod, BeanInfo declaringBean, BeanDeployment beanDeployment, + DisposerInfo disposer) { Set qualifiers = new HashSet<>(); - ScopeInfo scope = null; - Set types = Types.getTypeClosure(producerMethod, beanDeployment); + List scopes = new ArrayList<>(); + Set types = Types.getProducerMethodTypeClosure(producerMethod, beanDeployment); Integer alternativePriority = null; boolean isAlternative = false; List stereotypes = new ArrayList<>(); @@ -125,8 +140,10 @@ static BeanInfo createProducerMethod(MethodInfo producerMethod, BeanInfo declari for (AnnotationInstance annotation : beanDeployment.getAnnotations(producerMethod)) { //only check for method annotations since at this point we will get both // method and method param annotations - if (annotation.target().kind() == AnnotationTarget.Kind.METHOD - && beanDeployment.getQualifier(annotation.name()) != null) { + if (annotation.target().kind() != AnnotationTarget.Kind.METHOD) { + continue; + } + if (beanDeployment.getQualifier(annotation.name()) != null) { qualifiers.add(annotation); if (DotNames.NAMED.equals(annotation.name())) { AnnotationValue nameValue = annotation.value(); @@ -136,21 +153,32 @@ static BeanInfo createProducerMethod(MethodInfo producerMethod, BeanInfo declari name = getDefaultName(producerMethod); } } - } else if (DotNames.ALTERNATIVE.equals(annotation.name())) { + continue; + } + if (DotNames.ALTERNATIVE.equals(annotation.name())) { isAlternative = true; - } else { - if (scope == null) { - scope = beanDeployment.getScope(annotation.name()); - } - StereotypeInfo stereotype = beanDeployment.getStereotype(annotation.name()); - if (stereotype != null) { - stereotypes.add(stereotype); - } + continue; + } + ScopeInfo scopeAnnotation = beanDeployment.getScope(annotation.name()); + if (scopeAnnotation != null) { + scopes.add(scopeAnnotation); + continue; + } + StereotypeInfo stereotype = beanDeployment.getStereotype(annotation.name()); + if (stereotype != null) { + stereotypes.add(stereotype); + continue; } } - if (scope == null) { + if (scopes.size() > 1) { + throw multipleScopesFound("Producer method " + producerMethod, scopes); + } + ScopeInfo scope; + if (scopes.isEmpty()) { scope = initStereotypeScope(stereotypes, producerMethod); + } else { + scope = scopes.get(0); } if (!isAlternative) { isAlternative = initStereotypeAlternative(stereotypes); @@ -158,17 +186,18 @@ static BeanInfo createProducerMethod(MethodInfo producerMethod, BeanInfo declari if (name == null) { name = initStereotypeName(stereotypes, producerMethod); } - if (isAlternative) { alternativePriority = declaringBean.getAlternativePriority(); if (alternativePriority == null) { // Declaring bean itself does not have to be an alternative and can only have @Priority - alternativePriority = declaringBean.getTarget().get().asClass().classAnnotations().stream().filter(a -> a.name().equals(DotNames.PRIORITY)).findAny() + alternativePriority = declaringBean.getTarget().get().asClass().classAnnotations().stream() + .filter(a -> a.name().equals(DotNames.PRIORITY)).findAny() .map(a -> a.value().asInt()).orElse(null); } } - BeanInfo bean = new BeanInfo(producerMethod, beanDeployment, scope, types, qualifiers, Injection.forBean(producerMethod, beanDeployment), declaringBean, + BeanInfo bean = new BeanInfo(producerMethod, beanDeployment, scope, types, qualifiers, + Injection.forBean(producerMethod, beanDeployment), declaringBean, disposer, alternativePriority, stereotypes, name); return bean; } @@ -181,10 +210,11 @@ static BeanInfo createProducerMethod(MethodInfo producerMethod, BeanInfo declari * @param disposer * @return a new bean info */ - static BeanInfo createProducerField(FieldInfo producerField, BeanInfo declaringBean, BeanDeployment beanDeployment, DisposerInfo disposer) { + static BeanInfo createProducerField(FieldInfo producerField, BeanInfo declaringBean, BeanDeployment beanDeployment, + DisposerInfo disposer) { Set qualifiers = new HashSet<>(); - ScopeInfo scope = null; - Set types = Types.getTypeClosure(producerField, beanDeployment); + List scopes = new ArrayList<>(); + Set types = Types.getProducerFieldTypeClosure(producerField, beanDeployment); Integer alternativePriority = null; boolean isAlternative = false; List stereotypes = new ArrayList<>(); @@ -201,19 +231,28 @@ static BeanInfo createProducerField(FieldInfo producerField, BeanInfo declaringB name = producerField.name(); } } - } else { - if (scope == null) { - scope = beanDeployment.getScope(annotation.name()); - } - StereotypeInfo stereotype = beanDeployment.getStereotype(annotation.name()); - if (stereotype != null) { - stereotypes.add(stereotype); - } + continue; + } + ScopeInfo scopeAnnotation = beanDeployment.getScope(annotation.name()); + if (scopeAnnotation != null) { + scopes.add(scopeAnnotation); + continue; + } + StereotypeInfo stereotype = beanDeployment.getStereotype(annotation.name()); + if (stereotype != null) { + stereotypes.add(stereotype); + continue; } } - if (scope == null) { + if (scopes.size() > 1) { + throw multipleScopesFound("Producer field " + producerField, scopes); + } + ScopeInfo scope; + if (scopes.isEmpty()) { scope = initStereotypeScope(stereotypes, producerField); + } else { + scope = scopes.get(0); } if (!isAlternative) { isAlternative = initStereotypeAlternative(stereotypes); @@ -221,21 +260,27 @@ static BeanInfo createProducerField(FieldInfo producerField, BeanInfo declaringB if (name == null) { name = initStereotypeName(stereotypes, producerField); } - if (isAlternative) { alternativePriority = declaringBean.getAlternativePriority(); if (alternativePriority == null) { // Declaring bean itself does not have to be an alternative and can only have @Priority - alternativePriority = declaringBean.getTarget().get().asClass().classAnnotations().stream().filter(a -> a.name().equals(DotNames.PRIORITY)).findAny() + alternativePriority = declaringBean.getTarget().get().asClass().classAnnotations().stream() + .filter(a -> a.name().equals(DotNames.PRIORITY)).findAny() .map(a -> a.value().asInt()).orElse(null); } } - BeanInfo bean = new BeanInfo(producerField, beanDeployment, scope, types, qualifiers, Collections.emptyList(), declaringBean, disposer, + BeanInfo bean = new BeanInfo(producerField, beanDeployment, scope, types, qualifiers, Collections.emptyList(), + declaringBean, disposer, alternativePriority, stereotypes, name); return bean; } + private static DefinitionException multipleScopesFound(String baseMessage, List scopes) { + return new DefinitionException(baseMessage + " declares multiple scope type annotations: " + + scopes.stream().map(s -> s.getDotName().toString()).collect(Collectors.joining(", "))); + } + private static ScopeInfo initStereotypeScope(List stereotypes, AnnotationTarget target) { if (stereotypes.isEmpty()) { return null; @@ -302,8 +347,20 @@ static boolean matchesType(BeanInfo bean, Type requiredType) { return false; } - static void resolveInjectionPoint(BeanDeployment deployment, BeanInfo bean, InjectionPointInfo injectionPoint, List errors) { - if (BuiltinBean.resolvesTo(injectionPoint)) { + static void resolveInjectionPoint(BeanDeployment deployment, InjectionTargetInfo target, InjectionPointInfo injectionPoint, + List errors) { + BuiltinBean builtinBean = BuiltinBean.resolve(injectionPoint); + if (builtinBean != null) { + if (BuiltinBean.INJECTION_POINT.equals(builtinBean) + && (target.kind() != TargetKind.BEAN || !BuiltinScope.DEPENDENT.is(target.asBean().getScope()))) { + errors.add(new DefinitionException("Only @Dependent beans can access metadata about an injection point: " + + injectionPoint.getTargetInfo())); + } + if (BuiltinBean.EVENT_METADATA.equals(builtinBean) + && target.kind() != TargetKind.OBSERVER) { + errors.add(new DefinitionException("EventMetadata can be only injected into an observer method: " + + injectionPoint.getTargetInfo())); + } // Skip built-in beans return; } @@ -317,7 +374,7 @@ static void resolveInjectionPoint(BeanDeployment deployment, BeanInfo bean, Inje message.append("\n\t- java member: "); message.append(injectionPoint.getTargetInfo()); message.append("\n\t- declared on "); - message.append(bean); + message.append(target); errors.add(new UnsatisfiedResolutionException(message.toString())); } else if (resolved.size() > 1) { // Try to resolve the ambiguity @@ -330,7 +387,7 @@ static void resolveInjectionPoint(BeanDeployment deployment, BeanInfo bean, Inje message.append("\n\t- java member: "); message.append(injectionPoint.getTargetInfo()); message.append("\n\t- declared on "); - message.append(bean); + message.append(target); message.append("\n\t- available beans:\n\t\t- "); message.append(resolved.stream().map(Object::toString).collect(Collectors.joining("\n\t\t- "))); errors.add(new AmbiguousResolutionException(message.toString())); @@ -372,13 +429,16 @@ static BeanInfo resolveAmbiguity(List resolved) { } private static Integer getAlternativePriority(BeanInfo bean) { - return bean.getDeclaringBean() != null ? bean.getDeclaringBean().getAlternativePriority() : bean.getAlternativePriority(); + return bean.getDeclaringBean() != null ? bean.getDeclaringBean().getAlternativePriority() + : bean.getAlternativePriority(); } private static int compareAlternativeBeans(BeanInfo bean1, BeanInfo bean2) { // The highest priority wins - Integer priority2 = bean2.getDeclaringBean() != null ? bean2.getDeclaringBean().getAlternativePriority() : bean2.getAlternativePriority(); - Integer priority1 = bean1.getDeclaringBean() != null ? bean1.getDeclaringBean().getAlternativePriority() : bean1.getAlternativePriority(); + Integer priority2 = bean2.getDeclaringBean() != null ? bean2.getDeclaringBean().getAlternativePriority() + : bean2.getAlternativePriority(); + Integer priority1 = bean1.getDeclaringBean() != null ? bean1.getDeclaringBean().getAlternativePriority() + : bean1.getAlternativePriority(); return priority2.compareTo(priority1); } @@ -386,7 +446,8 @@ static boolean hasQualifier(BeanInfo bean, AnnotationInstance required) { return hasQualifier(bean.getDeployment().getQualifier(required.name()), required, bean.getQualifiers()); } - static boolean hasQualifier(ClassInfo requiredInfo, AnnotationInstance required, Collection qualifiers) { + static boolean hasQualifier(ClassInfo requiredInfo, AnnotationInstance required, + Collection qualifiers) { List binding = new ArrayList<>(); for (AnnotationValue val : required.values()) { if (!requiredInfo.method(val.name()).hasAnnotation(DotNames.NONBINDING)) { @@ -418,7 +479,6 @@ static List getCallbacks(ClassInfo beanClass, DotName annotation, In return callbacks; } - static void analyzeType(Type type, BeanDeployment beanDeployment) { if (type.kind() == Type.Kind.PARAMETERIZED_TYPE) { for (Type argument : type.asParameterizedType().arguments()) { @@ -460,7 +520,6 @@ private static void collectCallbacks(ClassInfo clazz, List callbacks } } - private static String getPropertyName(String methodName) { final String get = "get"; final String is = "is"; @@ -488,7 +547,6 @@ private static String decapitalize(String name) { return decapitalized.toString(); } - private static String getDefaultName(ClassInfo beanClass) { StringBuilder defaultName = new StringBuilder(); defaultName.append(DotNames.simpleName(beanClass)); @@ -507,5 +565,4 @@ private static String getDefaultName(MethodInfo producerMethod) { } } - } 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 e8eeffd4b3216..0acc0fab06b04 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 @@ -17,7 +17,6 @@ package io.quarkus.arc.processor; import java.util.List; - import org.jboss.jandex.IndexView; /** 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 23f90afdf2752..277478ee8b35b 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 @@ -16,17 +16,10 @@ package io.quarkus.arc.processor; -import java.util.HashSet; -import java.util.Set; -import java.util.function.Predicate; - -import org.jboss.jandex.AnnotationInstance; -import org.jboss.jandex.ClassInfo; -import org.jboss.jandex.DotName; - import io.quarkus.arc.BeanManagerProvider; import io.quarkus.arc.BeanMetadataProvider; import io.quarkus.arc.EventProvider; +import io.quarkus.arc.InjectableBean; import io.quarkus.arc.InjectableReferenceProvider; import io.quarkus.arc.InjectionPointProvider; import io.quarkus.arc.InstanceProvider; @@ -38,6 +31,13 @@ import io.quarkus.gizmo.MethodCreator; import io.quarkus.gizmo.MethodDescriptor; import io.quarkus.gizmo.ResultHandle; +import java.lang.reflect.Member; +import java.util.HashSet; +import java.util.Set; +import java.util.function.Predicate; +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.DotName; /** * @@ -47,60 +47,72 @@ enum BuiltinBean { INSTANCE(DotNames.INSTANCE, new Generator() { @Override - void generate(ClassOutput classOutput, BeanDeployment beanDeployment, InjectionPointInfo injectionPoint, ClassCreator clazzCreator, + void generate(ClassOutput classOutput, BeanDeployment beanDeployment, InjectionPointInfo injectionPoint, + ClassCreator clazzCreator, MethodCreator constructor, String providerName, AnnotationLiteralProcessor annotationLiterals) { - ResultHandle qualifiers = constructor.newInstance(MethodDescriptor.ofConstructor(HashSet.class)); - if (!injectionPoint.getRequiredQualifiers().isEmpty()) { - // Set instanceProvider1Qualifiers = new HashSet<>() - // instanceProvider1Qualifiers.add(javax.enterprise.inject.Default.Literal.INSTANCE) - - for (AnnotationInstance qualifierAnnotation : injectionPoint.getRequiredQualifiers()) { - BuiltinQualifier qualifier = BuiltinQualifier.of(qualifierAnnotation); - if (qualifier != null) { - constructor.invokeInterfaceMethod(MethodDescriptors.SET_ADD, qualifiers, qualifier.getLiteralInstance(constructor)); - } else { - // Create annotation literal first - ClassInfo qualifierClass = beanDeployment.getQualifier(qualifierAnnotation.name()); - constructor.invokeInterfaceMethod(MethodDescriptors.SET_ADD, qualifiers, annotationLiterals.process(constructor, classOutput, - qualifierClass, qualifierAnnotation, Types.getPackageName(clazzCreator.getClassName()))); - } - } - } + ResultHandle qualifiers = BeanGenerator.collectQualifiers(classOutput, clazzCreator, beanDeployment, constructor, + injectionPoint, + annotationLiterals); ResultHandle parameterizedType = Types.getTypeHandle(constructor, injectionPoint.getRequiredType()); + ResultHandle annotationsHandle = BeanGenerator.collectAnnotations(classOutput, clazzCreator, beanDeployment, + constructor, + injectionPoint, annotationLiterals); + ResultHandle javaMemberHandle = BeanGenerator.getJavaMemberHandle(constructor, injectionPoint); ResultHandle instanceProvider = constructor.newInstance( - MethodDescriptor.ofConstructor(InstanceProvider.class, java.lang.reflect.Type.class, Set.class), parameterizedType, qualifiers); - constructor.writeInstanceField(FieldDescriptor.of(clazzCreator.getClassName(), providerName, InjectableReferenceProvider.class.getName()), + MethodDescriptor.ofConstructor(InstanceProvider.class, java.lang.reflect.Type.class, Set.class, + InjectableBean.class, Set.class, Member.class, int.class), + parameterizedType, qualifiers, constructor.getThis(), annotationsHandle, javaMemberHandle, + constructor.load(injectionPoint.getPosition())); + constructor.writeInstanceField( + FieldDescriptor.of(clazzCreator.getClassName(), providerName, InjectableReferenceProvider.class.getName()), constructor.getThis(), instanceProvider); } - }, ip -> ip.getKind() == InjectionPointKind.CDI && (DotNames.INSTANCE.equals(ip.getRequiredType().name()) || DotNames.PROVIDER.equals(ip.getRequiredType().name()))), - INJECTION_POINT(DotNames.INJECTION_POINT, new Generator() { - @Override - void generate(ClassOutput classOutput, BeanDeployment beanDeployment, InjectionPointInfo injectionPoint, ClassCreator clazzCreator, - MethodCreator constructor, String providerName, AnnotationLiteralProcessor annotationLiterals) { - // this.injectionPointProvider1 = new InjectionPointProvider(); - constructor.writeInstanceField(FieldDescriptor.of(clazzCreator.getClassName(), providerName, InjectableReferenceProvider.class.getName()), - constructor.getThis(), constructor.newInstance(MethodDescriptor.ofConstructor(InjectionPointProvider.class))); - } - }), BEAN(DotNames.BEAN, new Generator() { + }, BuiltinBean::isInstanceInjectionPoint), + INJECTION_POINT(DotNames.INJECTION_POINT, + new Generator() { + @Override + void generate(ClassOutput classOutput, BeanDeployment beanDeployment, InjectionPointInfo injectionPoint, + ClassCreator clazzCreator, + MethodCreator constructor, String providerName, AnnotationLiteralProcessor annotationLiterals) { + // this.injectionPointProvider1 = new InjectionPointProvider(); + constructor.writeInstanceField( + FieldDescriptor.of(clazzCreator.getClassName(), providerName, + InjectableReferenceProvider.class.getName()), + constructor.getThis(), + constructor.newInstance(MethodDescriptor.ofConstructor(InjectionPointProvider.class))); + } + }), + BEAN(DotNames.BEAN, new Generator() { @Override - void generate(ClassOutput classOutput, BeanDeployment beanDeployment, InjectionPointInfo injectionPoint, ClassCreator clazzCreator, + void generate(ClassOutput classOutput, BeanDeployment beanDeployment, InjectionPointInfo injectionPoint, + ClassCreator clazzCreator, MethodCreator constructor, String providerName, AnnotationLiteralProcessor annotationLiterals) { // this.beanProvider1 = new BeanMetadataProvider<>(); - constructor.writeInstanceField(FieldDescriptor.of(clazzCreator.getClassName(), providerName, InjectableReferenceProvider.class.getName()), - constructor.getThis(), constructor.newInstance(MethodDescriptor.ofConstructor(BeanMetadataProvider.class))); + constructor.writeInstanceField( + FieldDescriptor.of(clazzCreator.getClassName(), providerName, + InjectableReferenceProvider.class.getName()), + constructor.getThis(), + constructor.newInstance(MethodDescriptor.ofConstructor(BeanMetadataProvider.class))); } - }), BEAN_MANAGER(DotNames.BEAN_MANAGER, new Generator() { + }), + BEAN_MANAGER(DotNames.BEAN_MANAGER, new Generator() { @Override - void generate(ClassOutput classOutput, BeanDeployment beanDeployment, InjectionPointInfo injectionPoint, ClassCreator clazzCreator, + void generate(ClassOutput classOutput, BeanDeployment beanDeployment, InjectionPointInfo injectionPoint, + ClassCreator clazzCreator, MethodCreator constructor, String providerName, AnnotationLiteralProcessor annotationLiterals) { - constructor.writeInstanceField(FieldDescriptor.of(clazzCreator.getClassName(), providerName, InjectableReferenceProvider.class.getName()), - constructor.getThis(), constructor.newInstance(MethodDescriptor.ofConstructor(BeanManagerProvider.class))); + constructor.writeInstanceField( + FieldDescriptor.of(clazzCreator.getClassName(), providerName, + InjectableReferenceProvider.class.getName()), + constructor.getThis(), + constructor.newInstance(MethodDescriptor.ofConstructor(BeanManagerProvider.class))); } - }), EVENT(DotNames.EVENT, new Generator() { + }), + EVENT(DotNames.EVENT, new Generator() { @Override - void generate(ClassOutput classOutput, BeanDeployment beanDeployment, InjectionPointInfo injectionPoint, ClassCreator clazzCreator, + void generate(ClassOutput classOutput, BeanDeployment beanDeployment, InjectionPointInfo injectionPoint, + ClassCreator clazzCreator, MethodCreator constructor, String providerName, AnnotationLiteralProcessor annotationLiterals) { ResultHandle qualifiers = constructor.newInstance(MethodDescriptor.ofConstructor(HashSet.class)); @@ -111,24 +123,33 @@ void generate(ClassOutput classOutput, BeanDeployment beanDeployment, InjectionP for (AnnotationInstance qualifierAnnotation : injectionPoint.getRequiredQualifiers()) { BuiltinQualifier qualifier = BuiltinQualifier.of(qualifierAnnotation); if (qualifier != null) { - constructor.invokeInterfaceMethod(MethodDescriptors.SET_ADD, qualifiers, qualifier.getLiteralInstance(constructor)); + constructor.invokeInterfaceMethod(MethodDescriptors.SET_ADD, qualifiers, + qualifier.getLiteralInstance(constructor)); } else { // Create annotation literal first ClassInfo qualifierClass = beanDeployment.getQualifier(qualifierAnnotation.name()); - constructor.invokeInterfaceMethod(MethodDescriptors.SET_ADD, qualifiers, annotationLiterals.process(constructor, classOutput, - qualifierClass, qualifierAnnotation, Types.getPackageName(clazzCreator.getClassName()))); + constructor.invokeInterfaceMethod(MethodDescriptors.SET_ADD, qualifiers, + annotationLiterals.process(constructor, classOutput, + qualifierClass, qualifierAnnotation, + Types.getPackageName(clazzCreator.getClassName()))); } } } ResultHandle parameterizedType = Types.getTypeHandle(constructor, injectionPoint.getRequiredType()); - ResultHandle eventProvider = constructor.newInstance(MethodDescriptor.ofConstructor(EventProvider.class, java.lang.reflect.Type.class, Set.class), + ResultHandle eventProvider = constructor.newInstance( + MethodDescriptor.ofConstructor(EventProvider.class, java.lang.reflect.Type.class, + Set.class), parameterizedType, qualifiers); - constructor.writeInstanceField(FieldDescriptor.of(clazzCreator.getClassName(), providerName, InjectableReferenceProvider.class.getName()), + constructor.writeInstanceField( + FieldDescriptor.of(clazzCreator.getClassName(), providerName, + InjectableReferenceProvider.class.getName()), constructor.getThis(), eventProvider); } - }), RESOURCE(DotNames.OBJECT, new Generator() { + }), + RESOURCE(DotNames.OBJECT, new Generator() { @Override - void generate(ClassOutput classOutput, BeanDeployment beanDeployment, InjectionPointInfo injectionPoint, ClassCreator clazzCreator, + void generate(ClassOutput classOutput, BeanDeployment beanDeployment, InjectionPointInfo injectionPoint, + ClassCreator clazzCreator, MethodCreator constructor, String providerName, AnnotationLiteralProcessor annotationLiterals) { ResultHandle annotations = constructor.newInstance(MethodDescriptor.ofConstructor(HashSet.class)); @@ -137,17 +158,32 @@ void generate(ClassOutput classOutput, BeanDeployment beanDeployment, InjectionP for (AnnotationInstance annotation : injectionPoint.getRequiredQualifiers()) { // Create annotation literal first ClassInfo annotationClass = beanDeployment.getIndex().getClassByName(annotation.name()); - constructor.invokeInterfaceMethod(MethodDescriptors.SET_ADD, annotations, annotationLiterals.process(constructor, classOutput, - annotationClass, annotation, Types.getPackageName(clazzCreator.getClassName()))); + constructor.invokeInterfaceMethod(MethodDescriptors.SET_ADD, annotations, + annotationLiterals.process(constructor, classOutput, + annotationClass, annotation, + Types.getPackageName(clazzCreator.getClassName()))); } } ResultHandle parameterizedType = Types.getTypeHandle(constructor, injectionPoint.getRequiredType()); ResultHandle resourceProvider = constructor.newInstance( - MethodDescriptor.ofConstructor(ResourceProvider.class, java.lang.reflect.Type.class, Set.class), parameterizedType, annotations); - constructor.writeInstanceField(FieldDescriptor.of(clazzCreator.getClassName(), providerName, InjectableReferenceProvider.class.getName()), + MethodDescriptor.ofConstructor(ResourceProvider.class, java.lang.reflect.Type.class, + Set.class), + parameterizedType, annotations); + constructor.writeInstanceField( + FieldDescriptor.of(clazzCreator.getClassName(), providerName, + InjectableReferenceProvider.class.getName()), constructor.getThis(), resourceProvider); } - }, ip -> ip.getKind() == InjectionPointKind.RESOURCE); + }, ip -> ip.getKind() == InjectionPointKind.RESOURCE), + EVENT_METADATA(DotNames.EVENT_METADATA, new Generator() { + @Override + void generate(ClassOutput classOutput, BeanDeployment beanDeployment, InjectionPointInfo injectionPoint, + ClassCreator clazzCreator, MethodCreator constructor, String providerName, + AnnotationLiteralProcessor annotationLiterals) { + // No-op + } + }), + ; private final DotName rawTypeDotName; @@ -156,7 +192,7 @@ void generate(ClassOutput classOutput, BeanDeployment beanDeployment, InjectionP private final Predicate matcher; BuiltinBean(DotName rawTypeDotName, Generator generator) { - this(rawTypeDotName, generator, ip -> ip.getKind() == InjectionPointKind.CDI && rawTypeDotName.equals(ip.getRequiredType().name())); + this(rawTypeDotName, generator, ip -> isCdiAndRawTypeMatches(ip, rawTypeDotName)); } BuiltinBean(DotName rawTypeDotName, Generator generator, Predicate matcher) { @@ -192,9 +228,25 @@ static BuiltinBean resolve(InjectionPointInfo injectionPoint) { abstract static class Generator { - abstract void generate(ClassOutput classOutput, BeanDeployment beanDeployment, InjectionPointInfo injectionPoint, ClassCreator clazzCreator, + abstract void generate(ClassOutput classOutput, BeanDeployment beanDeployment, InjectionPointInfo injectionPoint, + ClassCreator clazzCreator, MethodCreator constructor, String providerName, AnnotationLiteralProcessor annotationLiterals); } + private static boolean isCdiAndRawTypeMatches(InjectionPointInfo injectionPoint, DotName rawTypeDotName) { + 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; + } + return DotNames.INSTANCE.equals(injectionPoint.getRequiredType().name()) + || DotNames.PROVIDER.equals(injectionPoint.getRequiredType().name()); + } + } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BuiltinQualifier.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BuiltinQualifier.java index f3832d7a3ef45..67bf1aa406a48 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BuiltinQualifier.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BuiltinQualifier.java @@ -16,20 +16,20 @@ package io.quarkus.arc.processor; +import io.quarkus.gizmo.BytecodeCreator; +import io.quarkus.gizmo.FieldDescriptor; +import io.quarkus.gizmo.ResultHandle; import java.util.Collections; - import javax.enterprise.inject.Any; import javax.enterprise.inject.Default; - import org.jboss.jandex.AnnotationInstance; -import io.quarkus.gizmo.BytecodeCreator; -import io.quarkus.gizmo.FieldDescriptor; -import io.quarkus.gizmo.ResultHandle; enum BuiltinQualifier { DEFAULT(AnnotationInstance.create(DotNames.DEFAULT, null, Collections.emptyList()), - Default.Literal.class.getName()), ANY(AnnotationInstance.create(DotNames.ANY, null, Collections.emptyList()), Any.Literal.class.getName()),; + Default.Literal.class.getName()), + ANY(AnnotationInstance.create(DotNames.ANY, null, Collections.emptyList()), + Any.Literal.class.getName()),; private final AnnotationInstance instance; diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BuiltinScope.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BuiltinScope.java index 2fabbf571d368..0346f69a03b24 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BuiltinScope.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BuiltinScope.java @@ -1,12 +1,11 @@ package io.quarkus.arc.processor; import java.lang.annotation.Annotation; - import javax.enterprise.context.ApplicationScoped; import javax.enterprise.context.Dependent; import javax.enterprise.context.RequestScoped; import javax.inject.Singleton; - +import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.DotName; public enum BuiltinScope { @@ -26,6 +25,10 @@ public ScopeInfo getInfo() { return info; } + public DotName getName() { + return info.getDotName(); + } + public static BuiltinScope from(DotName name) { for (BuiltinScope scope : BuiltinScope.values()) { if (scope.getInfo().getDotName().equals(name)) { @@ -43,5 +46,13 @@ public boolean is(ScopeInfo scope) { return getInfo().equals(scope); } + public static boolean isIn(Iterable annotations) { + for (AnnotationInstance annotation : annotations) { + if (from(annotation.name()) != null) { + return true; + } + } + return false; + } } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ClientProxyGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ClientProxyGenerator.java index aca30be87c140..b19ba123cb180 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ClientProxyGenerator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ClientProxyGenerator.java @@ -19,6 +19,19 @@ import static org.objectweb.asm.Opcodes.ACC_FINAL; import static org.objectweb.asm.Opcodes.ACC_PRIVATE; +import io.quarkus.arc.ClientProxy; +import io.quarkus.arc.CreationalContextImpl; +import io.quarkus.arc.InjectableBean; +import io.quarkus.arc.processor.ResourceOutput.Resource; +import io.quarkus.gizmo.AssignableResultHandle; +import io.quarkus.gizmo.BytecodeCreator; +import io.quarkus.gizmo.ClassCreator; +import io.quarkus.gizmo.DescriptorUtils; +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; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collection; @@ -27,9 +40,7 @@ import java.util.List; import java.util.Map; import java.util.function.Predicate; - import javax.enterprise.context.ContextNotActiveException; - import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; import org.jboss.jandex.FieldInfo; @@ -37,20 +48,6 @@ import org.jboss.jandex.Type; import org.jboss.jandex.TypeVariable; -import io.quarkus.arc.ClientProxy; -import io.quarkus.arc.CreationalContextImpl; -import io.quarkus.arc.InjectableBean; -import io.quarkus.arc.processor.ResourceOutput.Resource; -import io.quarkus.gizmo.AssignableResultHandle; -import io.quarkus.gizmo.BytecodeCreator; -import io.quarkus.gizmo.ClassCreator; -import io.quarkus.gizmo.DescriptorUtils; -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; - /** * * @author Martin Kouba @@ -80,7 +77,7 @@ Collection generate(BeanInfo bean, String beanClassName, ReflectionReg String providerTypeName = providerClass.name().toString(); String baseName = getBaseName(bean, beanClassName); String targetPackage = getPackageName(bean); - String generatedName = targetPackage.replace('.', '/') + "/" + baseName + CLIENT_PROXY_SUFFIX; + String generatedName = generatedNameFromTarget(targetPackage, baseName, CLIENT_PROXY_SUFFIX); // Foo_ClientProxy extends Foo implements ClientProxy List interfaces = new ArrayList<>(); @@ -95,9 +92,11 @@ Collection generate(BeanInfo bean, String beanClassName, ReflectionReg superClass = providerTypeName; } - ClassCreator clientProxy = ClassCreator.builder().classOutput(classOutput).className(generatedName).superClass(superClass) + ClassCreator clientProxy = ClassCreator.builder().classOutput(classOutput).className(generatedName) + .superClass(superClass) .interfaces(interfaces.toArray(new String[0])).build(); - FieldCreator beanField = clientProxy.getFieldCreator("bean", DescriptorUtils.extToInt(beanClassName)).setModifiers(ACC_PRIVATE | ACC_FINAL); + FieldCreator beanField = clientProxy.getFieldCreator("bean", DescriptorUtils.extToInt(beanClassName)) + .setModifiers(ACC_PRIVATE | ACC_FINAL); createConstructor(clientProxy, beanClassName, superClass, beanField.getFieldDescriptor()); implementDelegate(clientProxy, providerTypeName, beanField.getFieldDescriptor()); @@ -120,7 +119,9 @@ Collection generate(BeanInfo bean, String beanClassName, ReflectionReg } ResultHandle delegate = forward - .invokeVirtualMethod(MethodDescriptor.ofMethod(generatedName, "delegate", DescriptorUtils.typeToString(providerType)), forward.getThis()); + .invokeVirtualMethod( + MethodDescriptor.ofMethod(generatedName, "delegate", DescriptorUtils.typeToString(providerType)), + forward.getThis()); ResultHandle ret; /** @@ -143,15 +144,16 @@ Collection generate(BeanInfo bean, String beanClassName, ReflectionReg forward.writeArrayValue(argsArray, idx++, argHandle); } reflectionRegistration.registerMethod(method); - ret = forward.invokeStaticMethod(MethodDescriptors.REFLECTIONS_INVOKE_METHOD, forward.loadClass(method.declaringClass().name().toString()), + ret = forward.invokeStaticMethod(MethodDescriptors.REFLECTIONS_INVOKE_METHOD, + forward.loadClass(method.declaringClass().name().toString()), forward.load(method.name()), paramTypesArray, delegate, argsArray); } else { // make sure we do not use the original method descriptor as it could point to // a default interface method containing class: make sure we invoke it on the provider type. - MethodDescriptor virtualMethod = MethodDescriptor.ofMethod(providerTypeName, - originalMethodDescriptor.getName(), - originalMethodDescriptor.getReturnType(), - originalMethodDescriptor.getParameterTypes()); + MethodDescriptor virtualMethod = MethodDescriptor.ofMethod(providerTypeName, + originalMethodDescriptor.getName(), + originalMethodDescriptor.getReturnType(), + originalMethodDescriptor.getParameterTypes()); ret = forward.invokeVirtualMethod(virtualMethod, delegate, params); } // Finally write the bytecode @@ -175,39 +177,48 @@ void implementDelegate(ClassCreator clientProxy, String providerTypeName, FieldD ResultHandle container = creator.invokeStaticMethod(MethodDescriptors.ARC_CONTAINER); // bean.getScope() ResultHandle bean = creator.readInstanceField(beanField, creator.getThis()); - ResultHandle scope = creator.invokeInterfaceMethod(MethodDescriptor.ofMethod(InjectableBean.class, "getScope", Class.class), bean); + ResultHandle scope = creator + .invokeInterfaceMethod(MethodDescriptor.ofMethod(InjectableBean.class, "getScope", Class.class), bean); // getContext() ResultHandle context = creator.invokeInterfaceMethod(MethodDescriptors.ARC_CONTAINER_GET_ACTIVE_CONTEXT, container, scope); BytecodeCreator inactiveBranch = creator.ifNull(context).trueBranch(); - inactiveBranch.throwException(ContextNotActiveException.class, ""); + ResultHandle exception = inactiveBranch.newInstance( + MethodDescriptor.ofConstructor(ContextNotActiveException.class, String.class), + inactiveBranch.invokeVirtualMethod(MethodDescriptors.OBJECT_TO_STRING, scope)); + inactiveBranch.throwException(exception); AssignableResultHandle ret = creator.createVariable(Object.class); creator.assign(ret, creator.invokeInterfaceMethod(MethodDescriptors.CONTEXT_GET_IF_PRESENT, context, bean)); BytecodeCreator isNullBranch = creator.ifNull(ret).trueBranch(); // Create a new contextual instance - new CreationalContextImpl<>() ResultHandle creationContext = isNullBranch.newInstance(MethodDescriptor.ofConstructor(CreationalContextImpl.class)); - isNullBranch.assign(ret, isNullBranch.invokeInterfaceMethod(MethodDescriptors.CONTEXT_GET, context, bean, creationContext)); + isNullBranch.assign(ret, + isNullBranch.invokeInterfaceMethod(MethodDescriptors.CONTEXT_GET, context, bean, creationContext)); creator.returnValue(ret); } void implementGetContextualInstance(ClassCreator clientProxy, String providerTypeName) { - MethodCreator creator = clientProxy.getMethodCreator("getContextualInstance", Object.class).setModifiers(Modifier.PUBLIC); + MethodCreator creator = clientProxy.getMethodCreator("getContextualInstance", Object.class) + .setModifiers(Modifier.PUBLIC); creator.returnValue( - creator.invokeVirtualMethod(MethodDescriptor.ofMethod(clientProxy.getClassName(), "delegate", providerTypeName), creator.getThis())); + creator.invokeVirtualMethod(MethodDescriptor.ofMethod(clientProxy.getClassName(), "delegate", providerTypeName), + creator.getThis())); } Collection getDelegatingMethods(BeanInfo bean) { Map methods = new HashMap<>(); if (bean.isClassBean()) { - Methods.addDelegatingMethods(bean.getDeployment().getIndex(), bean.getTarget().get().asClass(), Collections.emptyMap(), methods); + Methods.addDelegatingMethods(bean.getDeployment().getIndex(), bean.getTarget().get().asClass(), + Collections.emptyMap(), methods); } else if (bean.isProducerMethod()) { MethodInfo producerMethod = bean.getTarget().get().asMethod(); Map resolved = Collections.emptyMap(); ClassInfo returnTypeClass = bean.getDeployment().getIndex().getClassByName(producerMethod.returnType().name()); if (!returnTypeClass.typeParameters().isEmpty() && !Modifier.isInterface(returnTypeClass.flags())) { // Build the resolved map iff the return type is a parameterized class - resolved = Types.buildResolvedMap(producerMethod.returnType().asParameterizedType().arguments(), returnTypeClass.typeParameters(), + resolved = Types.buildResolvedMap(producerMethod.returnType().asParameterizedType().arguments(), + returnTypeClass.typeParameters(), Collections.emptyMap()); } Methods.addDelegatingMethods(bean.getDeployment().getIndex(), returnTypeClass, resolved, methods); @@ -216,7 +227,8 @@ Collection getDelegatingMethods(BeanInfo bean) { Map resolved = Collections.emptyMap(); ClassInfo fieldClass = bean.getDeployment().getIndex().getClassByName(producerField.type().name()); if (!fieldClass.typeParameters().isEmpty()) { - resolved = Types.buildResolvedMap(producerField.type().asParameterizedType().arguments(), fieldClass.typeParameters(), Collections.emptyMap()); + resolved = Types.buildResolvedMap(producerField.type().asParameterizedType().arguments(), + fieldClass.typeParameters(), Collections.emptyMap()); } Methods.addDelegatingMethods(bean.getDeployment().getIndex(), fieldClass, resolved, methods); } else if (bean.isSynthetic()) { 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 ac85329e9eb4a..7940f31122a87 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 @@ -18,6 +18,17 @@ import static org.objectweb.asm.Opcodes.ACC_PUBLIC; +import io.quarkus.arc.Arc; +import io.quarkus.arc.Components; +import io.quarkus.arc.ComponentsProvider; +import io.quarkus.arc.InjectableBean; +import io.quarkus.arc.InjectableInterceptor; +import io.quarkus.arc.InjectableReferenceProvider; +import io.quarkus.arc.processor.ResourceOutput.Resource; +import io.quarkus.gizmo.ClassCreator; +import io.quarkus.gizmo.MethodCreator; +import io.quarkus.gizmo.MethodDescriptor; +import io.quarkus.gizmo.ResultHandle; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collection; @@ -29,21 +40,8 @@ import java.util.Map.Entry; import java.util.function.Function; import java.util.stream.Collectors; - import org.objectweb.asm.Type; -import io.quarkus.arc.Arc; -import io.quarkus.arc.Components; -import io.quarkus.arc.ComponentsProvider; -import io.quarkus.arc.InjectableBean; -import io.quarkus.arc.InjectableInterceptor; -import io.quarkus.arc.InjectableReferenceProvider; -import io.quarkus.arc.processor.ResourceOutput.Resource; -import io.quarkus.gizmo.ClassCreator; -import io.quarkus.gizmo.MethodCreator; -import io.quarkus.gizmo.MethodDescriptor; -import io.quarkus.gizmo.ResultHandle; - /** * * @author Martin Kouba @@ -68,9 +66,11 @@ Collection generate(String name, BeanDeployment beanDeployment, Map> beans = new ArrayList<>(); ResultHandle beansHandle = getComponents.newInstance(MethodDescriptor.ofConstructor(ArrayList.class)); @@ -106,7 +106,8 @@ Collection generate(String name, BeanDeployment beanDeployment, Map new ArrayList<>()).add(interceptor); + beanToInjections.computeIfAbsent(injectionPoint.getResolvedBean(), d -> new ArrayList<>()) + .add(interceptor); } } } @@ -128,7 +129,8 @@ Collection generate(String name, BeanDeployment beanDeployment, Map>> iterator = beanToInjections.entrySet().iterator(); iterator.hasNext();) { + for (Iterator>> iterator = beanToInjections.entrySet().iterator(); iterator + .hasNext();) { Entry> entry = iterator.next(); BeanInfo bean = entry.getKey(); if (!isDependency(bean, beanToInjections)) { @@ -155,7 +157,8 @@ Collection generate(String name, BeanDeployment beanDeployment, Map injectionPoints = observer.getInjection().injectionPoints.stream().filter(ip -> !BuiltinBean.resolvesTo(ip)) + List injectionPoints = observer.getInjection().injectionPoints.stream() + .filter(ip -> !BuiltinBean.resolvesTo(ip)) .collect(Collectors.toList()); List params = new ArrayList<>(); List paramTypes = new ArrayList<>(); @@ -167,7 +170,8 @@ Collection generate(String name, BeanDeployment beanDeployment, Map generate(String name, BeanDeployment beanDeployment, Map generate(String name, BeanDeployment beanDeployment, Map beanToGeneratedName, + private void addBean(MethodCreator getComponents, ResultHandle beansResultHandle, BeanInfo bean, + Map beanToGeneratedName, Map beanToResultHandle) { String beanType = beanToGeneratedName.get(bean); List injectionPoints = bean.getInjections().isEmpty() ? Collections.emptyList() - : bean.getInjections().stream().flatMap(i -> i.injectionPoints.stream()).filter(ip -> !BuiltinBean.resolvesTo(ip)).collect(Collectors.toList()); + : bean.getInjections().stream().flatMap(i -> i.injectionPoints.stream()) + .filter(ip -> !BuiltinBean.resolvesTo(ip)).collect(Collectors.toList()); List params = new ArrayList<>(); List paramTypes = new ArrayList<>(); @@ -222,15 +229,14 @@ private void addBean(MethodCreator getComponents, ResultHandle beansResultHandle paramTypes.add(Type.getDescriptor(InjectableReferenceProvider.class)); } } - if (!bean.getInterceptedMethods().isEmpty()) { - for (InterceptorInfo interceptor : bean.getBoundInterceptors()) { - ResultHandle resultHandle = beanToResultHandle.get(interceptor); - params.add(resultHandle); - paramTypes.add(Type.getDescriptor(InjectableInterceptor.class)); - } + for (InterceptorInfo interceptor : bean.getBoundInterceptors()) { + ResultHandle resultHandle = beanToResultHandle.get(interceptor); + params.add(resultHandle); + paramTypes.add(Type.getDescriptor(InjectableInterceptor.class)); } // Foo_Bean bean2 = new Foo_Bean(bean2) - ResultHandle beanInstance = getComponents.newInstance(MethodDescriptor.ofConstructor(beanType, paramTypes.toArray(new String[0])), + ResultHandle beanInstance = getComponents.newInstance( + MethodDescriptor.ofConstructor(beanType, paramTypes.toArray(new String[0])), params.toArray(new ResultHandle[0])); // beans.add(bean2) getComponents.invokeInterfaceMethod(MethodDescriptors.LIST_ADD, beansResultHandle, beanInstance); diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ContextConfigurator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ContextConfigurator.java index 1acbd112ebc3d..8a826725164a5 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ContextConfigurator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ContextConfigurator.java @@ -16,6 +16,11 @@ package io.quarkus.arc.processor; +import io.quarkus.arc.ContextCreator; +import io.quarkus.arc.InjectableContext; +import io.quarkus.gizmo.MethodCreator; +import io.quarkus.gizmo.MethodDescriptor; +import io.quarkus.gizmo.ResultHandle; import java.lang.annotation.Annotation; import java.util.HashMap; import java.util.Map; @@ -24,12 +29,6 @@ import java.util.function.Consumer; import java.util.function.Function; -import io.quarkus.arc.ContextCreator; -import io.quarkus.arc.InjectableContext; -import io.quarkus.gizmo.MethodCreator; -import io.quarkus.gizmo.MethodDescriptor; -import io.quarkus.gizmo.ResultHandle; - /** * Custom context configurator. * @@ -40,7 +39,7 @@ public final class ContextConfigurator { private final Consumer configuratorConsumer; Class scopeAnnotation; - + boolean isNormal; Function creator; @@ -82,12 +81,12 @@ public ContextConfigurator param(String name, boolean value) { params.put(name, value); return this; } - + public ContextConfigurator normal() { this.isNormal = true; return this; } - + public ContextConfigurator contextClass(Class contextClazz) { return creator(mc -> mc.newInstance(MethodDescriptor.ofConstructor(contextClazz))); } @@ -117,7 +116,8 @@ public ContextConfigurator creator(Class creatorClazz) } ResultHandle creatorHandle = mc.newInstance(MethodDescriptor.ofConstructor(creatorClazz)); ResultHandle ret = mc.invokeInterfaceMethod( - MethodDescriptor.ofMethod(ContextCreator.class, "create", InjectableContext.class, Map.class), creatorHandle, paramsHandle); + MethodDescriptor.ofMethod(ContextCreator.class, "create", InjectableContext.class, Map.class), + creatorHandle, paramsHandle); return ret; }); } @@ -132,4 +132,4 @@ public void done() { Objects.requireNonNull(configuratorConsumer).accept(this); } -} \ No newline at end of file +} diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ContextRegistrar.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ContextRegistrar.java index f7ed6544a8489..849461f74c9d9 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ContextRegistrar.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ContextRegistrar.java @@ -16,9 +16,8 @@ package io.quarkus.arc.processor; -import java.lang.annotation.Annotation; - import io.quarkus.arc.InjectableContext; +import java.lang.annotation.Annotation; /** * Use this extension point to register a custom {@link InjectableContext} implementation. diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/DisposerInfo.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/DisposerInfo.java index bebc7283e2ef3..64383b71fdc4b 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/DisposerInfo.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/DisposerInfo.java @@ -17,20 +17,21 @@ package io.quarkus.arc.processor; import java.util.ArrayList; +import java.util.Collection; import java.util.List; - +import java.util.stream.Collectors; import javax.enterprise.inject.spi.DefinitionException; - import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationTarget.Kind; import org.jboss.jandex.MethodInfo; import org.jboss.jandex.MethodParameterInfo; +import org.jboss.jandex.Type; /** * * @author Martin Kouba */ -public class DisposerInfo { +public class DisposerInfo implements InjectionTargetInfo { private final BeanInfo declaringBean; @@ -47,6 +48,16 @@ public class DisposerInfo { this.disposedParameter = initDisposedParam(disposerMethod); } + @Override + public TargetKind kind() { + return TargetKind.DISPOSER; + } + + @Override + public DisposerInfo asDisposer() { + return this; + } + public BeanInfo getDeclaringBean() { return declaringBean; } @@ -71,10 +82,20 @@ public List getAllInjectionPoints() { void init(List errors) { for (InjectionPointInfo injectionPoint : injection.injectionPoints) { - Beans.resolveInjectionPoint(declaringBean.getDeployment(), null, injectionPoint, errors); + Beans.resolveInjectionPoint(declaringBean.getDeployment(), this, injectionPoint, errors); } } + Collection getDisposedParameterQualifiers() { + return Annotations.getParameterAnnotations(declaringBean.getDeployment(), disposerMethod, disposedParameter.position()) + .stream().filter(a -> declaringBean.getDeployment().getQualifier(a.name()) != null) + .collect(Collectors.toList()); + } + + Type getDisposedParameterType() { + return disposerMethod.parameters().get(disposedParameter.position()); + } + MethodParameterInfo initDisposedParam(MethodInfo disposerMethod) { List disposedParams = new ArrayList<>(); for (AnnotationInstance annotation : disposerMethod.annotations()) { 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 c3d24e2a9741c..dedfd4ade71e6 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 @@ -16,11 +16,12 @@ package io.quarkus.arc.processor; +import io.quarkus.arc.ComputingCache; import java.util.Optional; - import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.annotation.Priority; +import javax.enterprise.context.control.ActivateRequestContext; import javax.enterprise.event.Event; import javax.enterprise.event.Observes; import javax.enterprise.event.ObservesAsync; @@ -47,10 +48,8 @@ import javax.interceptor.AroundInvoke; import javax.interceptor.Interceptor; import javax.interceptor.InterceptorBinding; - import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; -import io.quarkus.arc.ComputingCache; public final class DotNames { @@ -89,6 +88,7 @@ public final class DotNames { public static final DotName EXTENSION = create(Extension.class); public static final DotName OPTIONAL = create(Optional.class); public static final DotName NAMED = create(Named.class); + public static final DotName ACTIVATE_REQUEST_CONTEXT = create(ActivateRequestContext.class); public static final DotName BOOLEAN = create(Boolean.class); public static final DotName BYTE = create(Byte.class); @@ -98,6 +98,7 @@ public final class DotNames { public static final DotName INTEGER = create(Integer.class); public static final DotName LONG = create(Long.class); public static final DotName SHORT = create(Short.class); + public static final DotName STRING = create(String.class); private DotNames() { } @@ -134,7 +135,7 @@ public static String simpleName(ClassInfo clazz) { throw new IllegalStateException("Unsupported nesting type: " + clazz); } } - + /** * @param dotName * @see #simpleName(String) @@ -144,7 +145,8 @@ public static String simpleName(DotName dotName) { } /** - * Note that "$" is a valid character for class names so we cannot detect a nested class here. Therefore, this method would return "Foo$Bar" for the + * Note that "$" is a valid character for class names so we cannot detect a nested class here. Therefore, this method would + * return "Foo$Bar" for the * parameter "com.foo.Foo$Bar". Use {@link #simpleName(ClassInfo)} when you need to distinguish the nested classes. * * @param name diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Injection.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Injection.java index 4e298f91a8120..d0d98c36b6135 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Injection.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Injection.java @@ -20,7 +20,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; - import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationTarget; import org.jboss.jandex.AnnotationTarget.Kind; @@ -63,12 +62,14 @@ static List forBean(AnnotationTarget beanTarget, BeanDeployment beanD return Collections.emptyList(); } // All parameters are injection points - return Collections.singletonList(new Injection(beanTarget.asMethod(), InjectionPointInfo.fromMethod(beanTarget.asMethod(), beanDeployment))); + return Collections.singletonList( + new Injection(beanTarget.asMethod(), InjectionPointInfo.fromMethod(beanTarget.asMethod(), beanDeployment))); } throw new IllegalArgumentException("Unsupported annotation target"); } - private static void forClassBean(ClassInfo beanTarget, BeanDeployment beanDeployment, List injections, boolean isFirstLevel) { + private static void forClassBean(ClassInfo beanTarget, BeanDeployment beanDeployment, List injections, + boolean isFirstLevel) { List injectAnnotations = getAllInjectionPoints(beanDeployment, beanTarget, DotNames.INJECT); @@ -77,10 +78,12 @@ private static void forClassBean(ClassInfo beanTarget, BeanDeployment beanDeploy switch (injectAnnotation.target().kind()) { case FIELD: injections - .add(new Injection(injectTarget, Collections.singletonList(InjectionPointInfo.fromField(injectTarget.asField(), beanDeployment)))); + .add(new Injection(injectTarget, Collections + .singletonList(InjectionPointInfo.fromField(injectTarget.asField(), beanDeployment)))); break; case METHOD: - injections.add(new Injection(injectTarget, InjectionPointInfo.fromMethod(injectTarget.asMethod(), beanDeployment))); + injections.add(new Injection(injectTarget, + InjectionPointInfo.fromMethod(injectTarget.asMethod(), beanDeployment))); break; default: LOGGER.warn("Unsupported @Inject target ignored: " + injectAnnotation.target()); @@ -108,22 +111,26 @@ private static void forClassBean(ClassInfo beanTarget, BeanDeployment beanDeploy nonNoargConstrs.add(constr); } } - if(nonNoargConstrs.size() == 1) { + if (nonNoargConstrs.size() == 1) { final MethodInfo injectTarget = nonNoargConstrs.get(0); - injections.add(new Injection(injectTarget, InjectionPointInfo.fromMethod(injectTarget.asMethod(), beanDeployment))); + injections.add(new Injection(injectTarget, + InjectionPointInfo.fromMethod(injectTarget.asMethod(), beanDeployment))); } } } for (DotName resourceAnnotation : beanDeployment.getResourceAnnotations()) { - List resourceAnnotations = getAllInjectionPoints(beanDeployment, beanTarget, resourceAnnotation); + List resourceAnnotations = getAllInjectionPoints(beanDeployment, beanTarget, + resourceAnnotation); if (resourceAnnotations != null) { for (AnnotationInstance resourceAnnotationInstance : resourceAnnotations) { if (Kind.FIELD == resourceAnnotationInstance.target().kind() - && resourceAnnotationInstance.target().asField().annotations().stream().noneMatch(a -> DotNames.INJECT.equals(a.name()))) { + && resourceAnnotationInstance.target().asField().annotations().stream() + .noneMatch(a -> DotNames.INJECT.equals(a.name()))) { // Add special injection for a resource field injections.add(new Injection(resourceAnnotationInstance.target(), Collections - .singletonList(InjectionPointInfo.fromResourceField(resourceAnnotationInstance.target().asField(), beanDeployment)))); + .singletonList(InjectionPointInfo + .fromResourceField(resourceAnnotationInstance.target().asField(), beanDeployment)))); } // TODO setter injection } @@ -146,7 +153,8 @@ static Injection forDisposer(MethodInfo disposerMethod, BeanDeployment beanDeplo static Injection forObserver(MethodInfo observerMethod, BeanDeployment beanDeployment) { return new Injection(observerMethod, InjectionPointInfo.fromMethod(observerMethod, beanDeployment, - annotations -> annotations.stream().anyMatch(a -> a.name().equals(DotNames.OBSERVES) || a.name().equals(DotNames.OBSERVES_ASYNC)))); + annotations -> annotations.stream() + .anyMatch(a -> a.name().equals(DotNames.OBSERVES) || a.name().equals(DotNames.OBSERVES_ASYNC)))); } final AnnotationTarget target; @@ -170,7 +178,8 @@ boolean isField() { return Kind.FIELD == target.kind(); } - private static List getAllInjectionPoints(BeanDeployment beanDeployment, ClassInfo beanClass, DotName name) { + private static List getAllInjectionPoints(BeanDeployment beanDeployment, ClassInfo beanClass, + DotName name) { List injectAnnotations = new ArrayList<>(); for (FieldInfo field : beanClass.fields()) { AnnotationInstance inject = beanDeployment.getAnnotation(field, name); diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InjectionPointInfo.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InjectionPointInfo.java index c801ae3334a09..cfaca80706123 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InjectionPointInfo.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InjectionPointInfo.java @@ -24,10 +24,10 @@ import java.util.Set; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Predicate; - import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationTarget; import org.jboss.jandex.AnnotationTarget.Kind; +import org.jboss.jandex.DotName; import org.jboss.jandex.FieldInfo; import org.jboss.jandex.MethodInfo; import org.jboss.jandex.Type; @@ -63,7 +63,7 @@ static List fromMethod(MethodInfo method, BeanDeployment bea for (ListIterator iterator = method.parameters().listIterator(); iterator.hasNext();) { Type paramType = iterator.next(); int position = iterator.previousIndex(); - Set paramAnnotations = getParameterAnnotations(beanDeployment, method, position); + Set paramAnnotations = Annotations.getParameterAnnotations(beanDeployment, method, position); if (skipPredicate != null && skipPredicate.test(paramAnnotations)) { // Skip parameter, e.g. @Disposes continue; @@ -79,36 +79,27 @@ static List fromMethod(MethodInfo method, BeanDeployment bea return injectionPoints; } - static Set getParameterAnnotations(BeanDeployment beanDeployment, MethodInfo method, int position) { - Set annotations = new HashSet<>(); - for (AnnotationInstance annotation : beanDeployment.getAnnotations(method)) { - if (Kind.METHOD_PARAMETER.equals(annotation.target().kind()) - && annotation.target().asMethodParameter().position() == position) { - annotations.add(annotation); - } - } - return annotations; - } - private final TypeAndQualifiers typeAndQualifiers; private final AtomicReference resolvedBean; private final InjectionPointKind kind; - + private final boolean hasDefaultedQualifier; - + private final AnnotationTarget target; - + private final int position; InjectionPointInfo(Type requiredType, Set requiredQualifiers, AnnotationTarget target, int position) { this(requiredType, requiredQualifiers, InjectionPointKind.CDI, target, position); } - InjectionPointInfo(Type requiredType, Set requiredQualifiers, InjectionPointKind kind, AnnotationTarget target, int position) { + InjectionPointInfo(Type requiredType, Set requiredQualifiers, InjectionPointKind kind, + AnnotationTarget target, int position) { this.typeAndQualifiers = new TypeAndQualifiers(requiredType, - requiredQualifiers.isEmpty() ? Collections.singleton(AnnotationInstance.create(DotNames.DEFAULT, null, Collections.emptyList())) + requiredQualifiers.isEmpty() + ? Collections.singleton(AnnotationInstance.create(DotNames.DEFAULT, null, Collections.emptyList())) : requiredQualifiers); this.resolvedBean = new AtomicReference(null); this.kind = kind; @@ -137,14 +128,23 @@ public Set getRequiredQualifiers() { return typeAndQualifiers.qualifiers; } + public AnnotationInstance getRequiredQualifier(DotName name) { + for (AnnotationInstance qualifier : typeAndQualifiers.qualifiers) { + if (qualifier.name().equals(name)) { + return qualifier; + } + } + return null; + } + public boolean hasDefaultedQualifier() { return hasDefaultedQualifier; } - + TypeAndQualifiers getTypeAndQualifiers() { return typeAndQualifiers; } - + /** * For injected params, this method returns the corresponding method and not the param itself. * @@ -153,7 +153,15 @@ TypeAndQualifiers getTypeAndQualifiers() { public AnnotationTarget getTarget() { return target; } - + + public boolean isField() { + return target.kind() == Kind.FIELD; + } + + public boolean isParam() { + return target.kind() == Kind.METHOD; + } + /** * @return the parameter position or {@code -1} for a field injection point */ @@ -164,9 +172,9 @@ public int getPosition() { public String getTargetInfo() { switch (target.kind()) { case FIELD: - return target.asField().declaringClass().name() + "#" + target.asField().name(); + return target.asField().declaringClass().name() + "#" + target.asField().name(); case METHOD: - return target.asMethod().declaringClass().name() + "#" + target.asMethod().name() + "()"; + return target.asMethod().declaringClass().name() + "#" + target.asMethod().name() + "()"; default: return target.toString(); } @@ -174,11 +182,13 @@ public String getTargetInfo() { @Override public String toString() { - return "InjectionPointInfo [requiredType=" + typeAndQualifiers.type + ", requiredQualifiers=" + typeAndQualifiers.qualifiers + "]"; + return "InjectionPointInfo [requiredType=" + typeAndQualifiers.type + ", requiredQualifiers=" + + typeAndQualifiers.qualifiers + "]"; } enum InjectionPointKind { - CDI, RESOURCE + CDI, + RESOURCE } static class TypeAndQualifiers { diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InjectionTargetInfo.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InjectionTargetInfo.java new file mode 100644 index 0000000000000..c9458c779f0cc --- /dev/null +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InjectionTargetInfo.java @@ -0,0 +1,27 @@ +package io.quarkus.arc.processor; + +public interface InjectionTargetInfo { + + TargetKind kind(); + + default BeanInfo asBean() { + throw new IllegalStateException("Not a bean"); + } + + default ObserverInfo asObserver() { + throw new IllegalStateException("Not an observer"); + } + + default DisposerInfo asDisposer() { + throw new IllegalStateException("Not a disposer"); + } + + enum TargetKind { + + BEAN, + OBSERVER, + DISPOSER, + + } + +} diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InterceptorGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InterceptorGenerator.java index 3c3c07f55fa90..5541038b8a422 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InterceptorGenerator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InterceptorGenerator.java @@ -20,6 +20,19 @@ import static org.objectweb.asm.Opcodes.ACC_PRIVATE; import static org.objectweb.asm.Opcodes.ACC_PUBLIC; +import io.quarkus.arc.InjectableInterceptor; +import io.quarkus.arc.processor.BeanProcessor.PrivateMembersCollector; +import io.quarkus.arc.processor.ResourceOutput.Resource; +import io.quarkus.arc.processor.ResourceOutput.Resource.SpecialType; +import io.quarkus.gizmo.BranchResult; +import io.quarkus.gizmo.BytecodeCreator; +import io.quarkus.gizmo.ClassCreator; +import io.quarkus.gizmo.ClassOutput; +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; import java.lang.reflect.Modifier; import java.util.Collection; import java.util.HashMap; @@ -27,28 +40,13 @@ import java.util.Map; import java.util.Set; import java.util.function.Predicate; - import javax.enterprise.inject.spi.InterceptionType; import javax.interceptor.InvocationContext; - import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; import org.jboss.jandex.MethodInfo; import org.jboss.jandex.Type; -import io.quarkus.arc.InjectableInterceptor; -import io.quarkus.arc.processor.BeanProcessor.PrivateMembersCollector; -import io.quarkus.arc.processor.ResourceOutput.Resource; -import io.quarkus.arc.processor.ResourceOutput.Resource.SpecialType; -import io.quarkus.gizmo.BranchResult; -import io.quarkus.gizmo.BytecodeCreator; -import io.quarkus.gizmo.ClassCreator; -import io.quarkus.gizmo.ClassOutput; -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; /** * @@ -82,29 +80,35 @@ Collection generate(InterceptorInfo interceptor, ReflectionRegistratio ClassInfo providerClass = interceptor.getDeployment().getIndex().getClassByName(providerType.name()); String providerTypeName = providerClass.name().toString(); String targetPackage = DotNames.packageName(providerType.name()); - String generatedName = targetPackage.replace('.', '/') + "/" + baseName + BEAN_SUFFIX; + String generatedName = generatedNameFromTarget(targetPackage, baseName, BEAN_SUFFIX); boolean isApplicationClass = applicationClassPredicate.test(interceptor.getBeanClass()); - ResourceClassOutput classOutput = new ResourceClassOutput(isApplicationClass, name -> name.equals(generatedName) ? SpecialType.INTERCEPTOR_BEAN : null); + ResourceClassOutput classOutput = new ResourceClassOutput(isApplicationClass, + name -> name.equals(generatedName) ? SpecialType.INTERCEPTOR_BEAN : null); // MyInterceptor_Bean implements InjectableInterceptor - ClassCreator interceptorCreator = ClassCreator.builder().classOutput(classOutput).className(generatedName).interfaces(InjectableInterceptor.class) + ClassCreator interceptorCreator = ClassCreator.builder().classOutput(classOutput).className(generatedName) + .interfaces(InjectableInterceptor.class) .build(); // Fields - FieldCreator beanTypes = interceptorCreator.getFieldCreator(FIELD_NAME_BEAN_TYPES, Set.class).setModifiers(ACC_PRIVATE | ACC_FINAL); - FieldCreator bindings = interceptorCreator.getFieldCreator(FIELD_NAME_BINDINGS, Set.class).setModifiers(ACC_PRIVATE | ACC_FINAL); + FieldCreator beanTypes = interceptorCreator.getFieldCreator(FIELD_NAME_BEAN_TYPES, Set.class) + .setModifiers(ACC_PRIVATE | ACC_FINAL); + FieldCreator bindings = interceptorCreator.getFieldCreator(FIELD_NAME_BINDINGS, Set.class) + .setModifiers(ACC_PRIVATE | ACC_FINAL); Map injectionPointToProviderField = new HashMap<>(); Map interceptorToProviderField = new HashMap<>(); initMaps(interceptor, injectionPointToProviderField, interceptorToProviderField); createProviderFields(interceptorCreator, interceptor, injectionPointToProviderField, interceptorToProviderField); - createConstructor(classOutput, interceptorCreator, interceptor, baseName, injectionPointToProviderField, interceptorToProviderField, + createConstructor(classOutput, interceptorCreator, interceptor, baseName, injectionPointToProviderField, + interceptorToProviderField, bindings.getFieldDescriptor()); implementGetIdentifier(interceptor, interceptorCreator); - implementCreate(classOutput, interceptorCreator, interceptor, providerTypeName, baseName, injectionPointToProviderField, interceptorToProviderField, + implementCreate(classOutput, interceptorCreator, interceptor, providerTypeName, baseName, injectionPointToProviderField, + interceptorToProviderField, reflectionRegistration, targetPackage, isApplicationClass); implementGet(interceptor, interceptorCreator, providerTypeName); implementGetTypes(interceptorCreator, beanTypes.getFieldDescriptor()); @@ -122,10 +126,13 @@ Collection generate(InterceptorInfo interceptor, ReflectionRegistratio } - protected void createConstructor(ClassOutput classOutput, ClassCreator creator, InterceptorInfo interceptor, String baseName, - Map injectionPointToProviderField, Map interceptorToProviderField, FieldDescriptor bindings) { + protected void createConstructor(ClassOutput classOutput, ClassCreator creator, InterceptorInfo interceptor, + String baseName, + Map injectionPointToProviderField, + Map interceptorToProviderField, FieldDescriptor bindings) { - MethodCreator constructor = initConstructor(classOutput, creator, interceptor, baseName, injectionPointToProviderField, interceptorToProviderField, + MethodCreator constructor = initConstructor(classOutput, creator, interceptor, baseName, injectionPointToProviderField, + interceptorToProviderField, annotationLiterals); // Bindings @@ -136,7 +143,8 @@ protected void createConstructor(ClassOutput classOutput, ClassCreator creator, // Create annotation literal first ClassInfo bindingClass = interceptor.getDeployment().getInterceptorBinding(bindingAnnotation.name()); constructor.invokeInterfaceMethod(MethodDescriptors.SET_ADD, bindingsHandle, - annotationLiterals.process(constructor, classOutput, bindingClass, bindingAnnotation, Types.getPackageName(creator.getClassName()))); + annotationLiterals.process(constructor, classOutput, bindingClass, bindingAnnotation, + Types.getPackageName(creator.getClassName()))); } constructor.writeInstanceField(bindings, constructor.getThis(), bindingsHandle); constructor.returnValue(null); @@ -166,7 +174,8 @@ protected void implementGetPriority(ClassCreator creator, InterceptorInfo interc * @see InjectableInterceptor#intercepts(javax.enterprise.inject.spi.InterceptionType) */ protected void implementIntercepts(ClassCreator creator, InterceptorInfo interceptor) { - MethodCreator intercepts = creator.getMethodCreator("intercepts", boolean.class, InterceptionType.class).setModifiers(ACC_PUBLIC); + MethodCreator intercepts = creator.getMethodCreator("intercepts", boolean.class, InterceptionType.class) + .setModifiers(ACC_PUBLIC); addIntercepts(interceptor, InterceptionType.AROUND_INVOKE, intercepts); addIntercepts(interceptor, InterceptionType.POST_CONSTRUCT, intercepts); addIntercepts(interceptor, InterceptionType.PRE_DESTROY, intercepts); @@ -177,9 +186,11 @@ protected void implementIntercepts(ClassCreator creator, InterceptorInfo interce private void addIntercepts(InterceptorInfo interceptor, InterceptionType interceptionType, MethodCreator intercepts) { if (interceptor.intercepts(interceptionType)) { ResultHandle enumValue = intercepts - .readStaticField(FieldDescriptor.of(InterceptionType.class.getName(), interceptionType.name(), InterceptionType.class.getName())); + .readStaticField(FieldDescriptor.of(InterceptionType.class.getName(), interceptionType.name(), + InterceptionType.class.getName())); BranchResult result = intercepts - .ifNonZero(intercepts.invokeVirtualMethod(MethodDescriptors.OBJECT_EQUALS, enumValue, intercepts.getMethodParam(0))); + .ifNonZero(intercepts.invokeVirtualMethod(MethodDescriptors.OBJECT_EQUALS, enumValue, + intercepts.getMethodParam(0))); result.trueBranch().returnValue(result.trueBranch().load(true)); } } @@ -190,39 +201,58 @@ private void addIntercepts(InterceptorInfo interceptor, InterceptionType interce */ protected void implementIntercept(ClassCreator creator, InterceptorInfo interceptor, String providerTypeName, ReflectionRegistration reflectionRegistration, boolean isApplicationClass) { - MethodCreator intercept = creator.getMethodCreator("intercept", Object.class, InterceptionType.class, Object.class, InvocationContext.class) + MethodCreator intercept = creator + .getMethodCreator("intercept", Object.class, InterceptionType.class, Object.class, InvocationContext.class) .setModifiers(ACC_PUBLIC).addException(Exception.class); - addIntercept(intercept, interceptor.getAroundInvoke(), InterceptionType.AROUND_INVOKE, providerTypeName, reflectionRegistration, isApplicationClass); - addIntercept(intercept, interceptor.getPostConstruct(), InterceptionType.POST_CONSTRUCT, providerTypeName, reflectionRegistration, isApplicationClass); - addIntercept(intercept, interceptor.getPreDestroy(), InterceptionType.PRE_DESTROY, providerTypeName, reflectionRegistration, isApplicationClass); - addIntercept(intercept, interceptor.getAroundConstruct(), InterceptionType.AROUND_CONSTRUCT, providerTypeName, reflectionRegistration, isApplicationClass); + addIntercept(intercept, interceptor.getAroundInvoke(), InterceptionType.AROUND_INVOKE, providerTypeName, + reflectionRegistration, isApplicationClass); + addIntercept(intercept, interceptor.getPostConstruct(), InterceptionType.POST_CONSTRUCT, providerTypeName, + reflectionRegistration, isApplicationClass); + addIntercept(intercept, interceptor.getPreDestroy(), InterceptionType.PRE_DESTROY, providerTypeName, + reflectionRegistration, isApplicationClass); + addIntercept(intercept, interceptor.getAroundConstruct(), InterceptionType.AROUND_CONSTRUCT, providerTypeName, + reflectionRegistration, isApplicationClass); intercept.returnValue(intercept.loadNull()); } - private void addIntercept(MethodCreator intercept, MethodInfo interceptorMethod, InterceptionType interceptionType, String providerTypeName, + private void addIntercept(MethodCreator intercept, MethodInfo interceptorMethod, InterceptionType interceptionType, + String providerTypeName, ReflectionRegistration reflectionRegistration, boolean isApplicationClass) { if (interceptorMethod != null) { ResultHandle enumValue = intercept - .readStaticField(FieldDescriptor.of(InterceptionType.class.getName(), interceptionType.name(), InterceptionType.class.getName())); - BranchResult result = intercept.ifNonZero(intercept.invokeVirtualMethod(MethodDescriptors.OBJECT_EQUALS, enumValue, intercept.getMethodParam(0))); + .readStaticField(FieldDescriptor.of(InterceptionType.class.getName(), interceptionType.name(), + InterceptionType.class.getName())); + BranchResult result = intercept.ifNonZero( + intercept.invokeVirtualMethod(MethodDescriptors.OBJECT_EQUALS, enumValue, intercept.getMethodParam(0))); BytecodeCreator trueBranch = result.trueBranch(); - Class retType = InterceptionType.AROUND_INVOKE.equals(interceptionType) ? Object.class : void.class; + Class retType = null; + if (InterceptionType.AROUND_INVOKE.equals(interceptionType)) { + retType = Object.class; + } else if (InterceptionType.AROUND_CONSTRUCT.equals(interceptionType)) { + retType = interceptorMethod.returnType().kind().equals(Type.Kind.VOID) ? void.class : Object.class; + } else { + retType = void.class; + } ResultHandle ret; if (Modifier.isPrivate(interceptorMethod.flags())) { privateMembers.add(isApplicationClass, - String.format("Interceptor method %s#%s()", interceptorMethod.declaringClass().name(), interceptorMethod.name())); + String.format("Interceptor method %s#%s()", interceptorMethod.declaringClass().name(), + interceptorMethod.name())); // Use reflection fallback ResultHandle paramTypesArray = trueBranch.newArray(Class.class, trueBranch.load(1)); trueBranch.writeArrayValue(paramTypesArray, 0, trueBranch.loadClass(InvocationContext.class)); ResultHandle argsArray = trueBranch.newArray(Object.class, trueBranch.load(1)); trueBranch.writeArrayValue(argsArray, 0, intercept.getMethodParam(2)); reflectionRegistration.registerMethod(interceptorMethod); - ret = trueBranch.invokeStaticMethod(MethodDescriptors.REFLECTIONS_INVOKE_METHOD, trueBranch.loadClass(interceptorMethod.declaringClass() - .name() - .toString()), trueBranch.load(interceptorMethod.name()), paramTypesArray, intercept.getMethodParam(1), argsArray); + ret = trueBranch.invokeStaticMethod(MethodDescriptors.REFLECTIONS_INVOKE_METHOD, + trueBranch.loadClass(interceptorMethod.declaringClass() + .name() + .toString()), + trueBranch.load(interceptorMethod.name()), paramTypesArray, intercept.getMethodParam(1), argsArray); } else { - ret = trueBranch.invokeVirtualMethod(MethodDescriptor.ofMethod(providerTypeName, interceptorMethod.name(), retType, InvocationContext.class), + ret = trueBranch.invokeVirtualMethod( + MethodDescriptor.ofMethod(providerTypeName, interceptorMethod.name(), retType, InvocationContext.class), intercept.getMethodParam(1), intercept.getMethodParam(2)); } trueBranch.returnValue(InterceptionType.AROUND_INVOKE.equals(interceptionType) ? ret : trueBranch.loadNull()); diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InterceptorInfo.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InterceptorInfo.java index 491bb48f34b89..4eb6fb3459d61 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InterceptorInfo.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InterceptorInfo.java @@ -20,11 +20,10 @@ import java.util.HashSet; import java.util.List; import java.util.Set; - import javax.enterprise.inject.spi.InterceptionType; - import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationTarget; +import org.jboss.jandex.DotName; import org.jboss.jandex.MethodInfo; import org.jboss.jandex.Type; import org.jboss.jandex.Type.Kind; @@ -54,8 +53,10 @@ class InterceptorInfo extends BeanInfo implements Comparable { * @param bindings * @param injections */ - InterceptorInfo(AnnotationTarget target, BeanDeployment beanDeployment, Set bindings, List injections, int priority) { - super(target, beanDeployment, BuiltinScope.DEPENDENT.getInfo(), Collections.singleton(Type.create(target.asClass().name(), Kind.CLASS)), new HashSet<>(), injections, + InterceptorInfo(AnnotationTarget target, BeanDeployment beanDeployment, Set bindings, + List injections, int priority) { + super(target, beanDeployment, BuiltinScope.DEPENDENT.getInfo(), + Collections.singleton(Type.create(target.asClass().name(), Kind.CLASS)), new HashSet<>(), injections, null, null, null, Collections.emptyList(), null); this.bindings = bindings; this.priority = priority; @@ -66,7 +67,21 @@ class InterceptorInfo extends BeanInfo implements Comparable { for (MethodInfo method : target.asClass().methods()) { if (aroundInvoke == null && method.hasAnnotation(DotNames.AROUND_INVOKE)) { aroundInvoke = method; - } else if (aroundConstruct == null && method.hasAnnotation(DotNames.AROUND_CONSTRUCT)) { + } else if (method.hasAnnotation(DotNames.AROUND_CONSTRUCT)) { + // validate compliance with rules for AroundConstruct methods + if (!method.parameters().equals(Collections.singletonList( + Type.create(DotName.createSimple("javax.interceptor.InvocationContext"), Type.Kind.CLASS)))) { + throw new IllegalStateException( + "@AroundConstruct must have exactly one argument of type javax.interceptor.InvocationContext, but method " + + method.asMethod() + " declared by " + method.declaringClass() + + " violates this."); + } + if (!method.returnType().kind().equals(Type.Kind.VOID) && + !method.returnType().name().equals(DotNames.OBJECT)) { + throw new IllegalStateException("Return type of @AroundConstruct method must be Object or void, but method " + + method.asMethod() + " declared by " + method.declaringClass() + + " violates this."); + } aroundConstruct = method; } else if (postConstruct == null && method.hasAnnotation(DotNames.POST_CONSTRUCT)) { postConstruct = method; @@ -134,4 +149,4 @@ public int compareTo(InterceptorInfo other) { return getTarget().toString().compareTo(other.getTarget().toString()); } -} \ No newline at end of file +} diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InterceptorResolver.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InterceptorResolver.java index 46794ec063f06..bf0ae0f66d702 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InterceptorResolver.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InterceptorResolver.java @@ -20,9 +20,7 @@ import java.util.Collections; import java.util.List; import java.util.Set; - import javax.enterprise.inject.spi.InterceptionType; - import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationValue; import org.jboss.jandex.ClassInfo; diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Interceptors.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Interceptors.java index 61013e5e37851..c2500025bf551 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Interceptors.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Interceptors.java @@ -18,9 +18,7 @@ import java.util.HashSet; import java.util.Set; - import javax.enterprise.inject.spi.DefinitionException; - import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.ClassInfo; @@ -48,7 +46,8 @@ static InterceptorInfo createInterceptor(ClassInfo interceptorClass, BeanDeploym if (bindings.isEmpty()) { throw new DefinitionException("Interceptor has no bindings: " + interceptorClass); } - return new InterceptorInfo(interceptorClass, beanDeployment, bindings, Injection.forBean(interceptorClass, beanDeployment), priority); + return new InterceptorInfo(interceptorClass, beanDeployment, bindings, + Injection.forBean(interceptorClass, beanDeployment), priority); } } 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 589ae5bcdee8c..8403bde5852ab 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 @@ -16,6 +16,19 @@ package io.quarkus.arc.processor; +import io.quarkus.arc.Arc; +import io.quarkus.arc.ArcContainer; +import io.quarkus.arc.ClientProxy; +import io.quarkus.arc.CreationalContextImpl; +import io.quarkus.arc.InjectableBean; +import io.quarkus.arc.InjectableContext; +import io.quarkus.arc.InjectableInterceptor; +import io.quarkus.arc.InjectableReferenceProvider; +import io.quarkus.arc.InvocationContextImpl; +import io.quarkus.arc.InvocationContextImpl.InterceptorInvocation; +import io.quarkus.arc.LazyValue; +import io.quarkus.arc.Reflections; +import io.quarkus.gizmo.MethodDescriptor; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; @@ -25,7 +38,6 @@ import java.util.Set; import java.util.function.Function; import java.util.function.Supplier; - import javax.enterprise.context.spi.Context; import javax.enterprise.context.spi.Contextual; import javax.enterprise.context.spi.CreationalContext; @@ -33,110 +45,127 @@ import javax.enterprise.inject.spi.EventMetadata; import javax.interceptor.InvocationContext; -import io.quarkus.arc.Arc; -import io.quarkus.arc.ArcContainer; -import io.quarkus.arc.ClientProxy; -import io.quarkus.arc.CreationalContextImpl; -import io.quarkus.arc.InjectableBean; -import io.quarkus.arc.InjectableContext; -import io.quarkus.arc.InjectableInterceptor; -import io.quarkus.arc.InjectableReferenceProvider; -import io.quarkus.arc.InvocationContextImpl; -import io.quarkus.arc.InvocationContextImpl.InterceptorInvocation; -import io.quarkus.arc.LazyValue; -import io.quarkus.arc.Reflections; -import io.quarkus.gizmo.MethodDescriptor; - /** * * @author Martin Kouba */ final class MethodDescriptors { - static final MethodDescriptor CREATIONAL_CTX_CHILD = MethodDescriptor.ofMethod(CreationalContextImpl.class, "child", CreationalContextImpl.class, + static final MethodDescriptor CREATIONAL_CTX_CHILD = MethodDescriptor.ofMethod(CreationalContextImpl.class, "child", + CreationalContextImpl.class, CreationalContext.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 MAP_PUT = MethodDescriptor.ofMethod(Map.class, "put", Object.class, Object.class, + Object.class); - static final MethodDescriptor INJECTABLE_REF_PROVIDER_GET = MethodDescriptor.ofMethod(InjectableReferenceProvider.class, "get", Object.class, + static final MethodDescriptor INJECTABLE_REF_PROVIDER_GET = MethodDescriptor.ofMethod(InjectableReferenceProvider.class, + "get", Object.class, CreationalContext.class); static final MethodDescriptor SET_ADD = MethodDescriptor.ofMethod(Set.class, "add", boolean.class, Object.class); static final MethodDescriptor LIST_ADD = MethodDescriptor.ofMethod(List.class, "add", boolean.class, Object.class); - static final MethodDescriptor OBJECT_EQUALS = MethodDescriptor.ofMethod(Object.class, "equals", boolean.class, Object.class); + static final MethodDescriptor OBJECT_EQUALS = MethodDescriptor.ofMethod(Object.class, "equals", boolean.class, + Object.class); + + static final MethodDescriptor OBJECT_TO_STRING = MethodDescriptor.ofMethod(Object.class, "toString", String.class); static final MethodDescriptor OBJECT_CONSTRUCTOR = MethodDescriptor.ofConstructor(Object.class); - static final MethodDescriptor INTERCEPTOR_INVOCATION_POST_CONSTRUCT = MethodDescriptor.ofMethod(InterceptorInvocation.class, "postConstruct", + static final MethodDescriptor INTERCEPTOR_INVOCATION_POST_CONSTRUCT = MethodDescriptor.ofMethod(InterceptorInvocation.class, + "postConstruct", InterceptorInvocation.class, InjectableInterceptor.class, Object.class); - static final MethodDescriptor INTERCEPTOR_INVOCATION_AROUND_CONSTRUCT = MethodDescriptor.ofMethod(InterceptorInvocation.class, "aroundConstruct", + static final MethodDescriptor INTERCEPTOR_INVOCATION_AROUND_CONSTRUCT = MethodDescriptor.ofMethod( + InterceptorInvocation.class, "aroundConstruct", InterceptorInvocation.class, InjectableInterceptor.class, Object.class); - static final MethodDescriptor INTERCEPTOR_INVOCATION_AROUND_INVOKE = MethodDescriptor.ofMethod(InterceptorInvocation.class, "aroundInvoke", + static final MethodDescriptor INTERCEPTOR_INVOCATION_AROUND_INVOKE = MethodDescriptor.ofMethod(InterceptorInvocation.class, + "aroundInvoke", InterceptorInvocation.class, InjectableInterceptor.class, Object.class); - static final MethodDescriptor REFLECTIONS_FIND_CONSTRUCTOR = MethodDescriptor.ofMethod(Reflections.class, "findConstructor", Constructor.class, Class.class, + static final MethodDescriptor REFLECTIONS_FIND_CONSTRUCTOR = MethodDescriptor.ofMethod(Reflections.class, "findConstructor", + Constructor.class, Class.class, Class[].class); - static final MethodDescriptor REFLECTIONS_FIND_METHOD = MethodDescriptor.ofMethod(Reflections.class, "findMethod", Method.class, Class.class, String.class, + static final MethodDescriptor REFLECTIONS_FIND_METHOD = MethodDescriptor.ofMethod(Reflections.class, "findMethod", + Method.class, Class.class, String.class, Class[].class); - static final MethodDescriptor REFLECTIONS_FIND_FIELD = MethodDescriptor.ofMethod(Reflections.class, "findField", Field.class, Class.class, String.class); - - static final MethodDescriptor REFLECTIONS_WRITE_FIELD = MethodDescriptor.ofMethod(Reflections.class, "writeField", void.class, Class.class, String.class, + static final MethodDescriptor REFLECTIONS_FIND_FIELD = MethodDescriptor.ofMethod(Reflections.class, "findField", + Field.class, Class.class, String.class); + + static final MethodDescriptor REFLECTIONS_WRITE_FIELD = MethodDescriptor.ofMethod(Reflections.class, "writeField", + void.class, Class.class, String.class, Object.class, Object.class); - static final MethodDescriptor REFLECTIONS_READ_FIELD = MethodDescriptor.ofMethod(Reflections.class, "readField", Object.class, Class.class, String.class, + static final MethodDescriptor REFLECTIONS_READ_FIELD = MethodDescriptor.ofMethod(Reflections.class, "readField", + Object.class, Class.class, String.class, Object.class); - static final MethodDescriptor REFLECTIONS_INVOKE_METHOD = MethodDescriptor.ofMethod(Reflections.class, "invokeMethod", Object.class, Class.class, + static final MethodDescriptor REFLECTIONS_INVOKE_METHOD = MethodDescriptor.ofMethod(Reflections.class, "invokeMethod", + Object.class, Class.class, String.class, Class[].class, Object.class, Object[].class); - static final MethodDescriptor REFLECTIONS_NEW_INSTANCE = MethodDescriptor.ofMethod(Reflections.class, "newInstance", Object.class, Class.class, + static final MethodDescriptor REFLECTIONS_NEW_INSTANCE = MethodDescriptor.ofMethod(Reflections.class, "newInstance", + Object.class, Class.class, Class[].class, Object[].class); - static final MethodDescriptor CLIENT_PROXY_GET_CONTEXTUAL_INSTANCE = MethodDescriptor.ofMethod(ClientProxy.class, "getContextualInstance", Object.class); + static final MethodDescriptor CLIENT_PROXY_GET_CONTEXTUAL_INSTANCE = MethodDescriptor.ofMethod(ClientProxy.class, + "getContextualInstance", Object.class); - static final MethodDescriptor INJECTABLE_BEAN_DESTROY = MethodDescriptor.ofMethod(InjectableBean.class, "destroy", void.class, Object.class, + static final MethodDescriptor INJECTABLE_BEAN_DESTROY = MethodDescriptor.ofMethod(InjectableBean.class, "destroy", + void.class, Object.class, CreationalContext.class); - static final MethodDescriptor CREATIONAL_CTX_RELEASE = MethodDescriptor.ofMethod(CreationalContext.class, "release", void.class); + static final MethodDescriptor CREATIONAL_CTX_RELEASE = MethodDescriptor.ofMethod(CreationalContext.class, "release", + void.class); - static final MethodDescriptor EVENT_CONTEXT_GET_EVENT = MethodDescriptor.ofMethod(EventContext.class, "getEvent", Object.class); + static final MethodDescriptor EVENT_CONTEXT_GET_EVENT = MethodDescriptor.ofMethod(EventContext.class, "getEvent", + Object.class); - static final MethodDescriptor EVENT_CONTEXT_GET_METADATA = MethodDescriptor.ofMethod(EventContext.class, "getMetadata", EventMetadata.class); + static final MethodDescriptor EVENT_CONTEXT_GET_METADATA = MethodDescriptor.ofMethod(EventContext.class, "getMetadata", + EventMetadata.class); - static final MethodDescriptor INVOCATION_CONTEXT_AROUND_INVOKE = MethodDescriptor.ofMethod(InvocationContextImpl.class, "aroundInvoke", + static final MethodDescriptor INVOCATION_CONTEXT_AROUND_INVOKE = MethodDescriptor.ofMethod(InvocationContextImpl.class, + "aroundInvoke", InvocationContextImpl.class, Object.class, Method.class, Object[].class, List.class, Function.class, Set.class); - static final MethodDescriptor INVOCATION_CONTEXT_AROUND_CONSTRUCT = MethodDescriptor.ofMethod(InvocationContextImpl.class, "aroundConstruct", + static final MethodDescriptor INVOCATION_CONTEXT_AROUND_CONSTRUCT = MethodDescriptor.ofMethod(InvocationContextImpl.class, + "aroundConstruct", InvocationContextImpl.class, Constructor.class, List.class, Supplier.class, Set.class); - static final MethodDescriptor INVOCATION_CONTEXT_POST_CONSTRUCT = MethodDescriptor.ofMethod(InvocationContextImpl.class, "postConstruct", + static final MethodDescriptor INVOCATION_CONTEXT_POST_CONSTRUCT = MethodDescriptor.ofMethod(InvocationContextImpl.class, + "postConstruct", InvocationContextImpl.class, Object.class, List.class, Set.class); - static final MethodDescriptor INVOCATION_CONTEXT_PRE_DESTROY = MethodDescriptor.ofMethod(InvocationContextImpl.class, "preDestroy", + static final MethodDescriptor INVOCATION_CONTEXT_PRE_DESTROY = MethodDescriptor.ofMethod(InvocationContextImpl.class, + "preDestroy", InvocationContextImpl.class, Object.class, List.class, Set.class); - static final MethodDescriptor INVOCATION_CONTEXT_PROCEED = MethodDescriptor.ofMethod(InvocationContext.class, "proceed", Object.class); + static final MethodDescriptor INVOCATION_CONTEXT_PROCEED = MethodDescriptor.ofMethod(InvocationContext.class, "proceed", + Object.class); - static final MethodDescriptor CREATIONAL_CTX_ADD_DEP_TO_PARENT = MethodDescriptor.ofMethod(CreationalContextImpl.class, "addDependencyToParent", void.class, + static final MethodDescriptor CREATIONAL_CTX_ADD_DEP_TO_PARENT = MethodDescriptor.ofMethod(CreationalContextImpl.class, + "addDependencyToParent", void.class, InjectableBean.class, Object.class, CreationalContext.class); - static final MethodDescriptor COLLECTIONS_UNMODIFIABLE_SET = MethodDescriptor.ofMethod(Collections.class, "unmodifiableSet", Set.class, Set.class); + static final MethodDescriptor COLLECTIONS_UNMODIFIABLE_SET = MethodDescriptor.ofMethod(Collections.class, "unmodifiableSet", + Set.class, Set.class); static final MethodDescriptor ARC_CONTAINER = MethodDescriptor.ofMethod(Arc.class, "container", ArcContainer.class); - static final MethodDescriptor ARC_CONTAINER_GET_ACTIVE_CONTEXT = MethodDescriptor.ofMethod(ArcContainer.class, "getActiveContext", InjectableContext.class, Class.class); + static final MethodDescriptor ARC_CONTAINER_GET_ACTIVE_CONTEXT = MethodDescriptor.ofMethod(ArcContainer.class, + "getActiveContext", InjectableContext.class, Class.class); - static final MethodDescriptor CONTEXT_GET = MethodDescriptor.ofMethod(Context.class, "get", Object.class, Contextual.class, CreationalContext.class); + static final MethodDescriptor CONTEXT_GET = MethodDescriptor.ofMethod(Context.class, "get", Object.class, Contextual.class, + CreationalContext.class); - static final MethodDescriptor CONTEXT_GET_IF_PRESENT = MethodDescriptor.ofMethod(Context.class, "get", Object.class, Contextual.class); + static final MethodDescriptor CONTEXT_GET_IF_PRESENT = MethodDescriptor.ofMethod(Context.class, "get", Object.class, + Contextual.class); static final MethodDescriptor LAZY_VALUE_GET = MethodDescriptor.ofMethod(LazyValue.class, "get", Object.class); diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Methods.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Methods.java index ca4b2864ba6f2..c9564e9bbd8de 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Methods.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Methods.java @@ -25,7 +25,6 @@ import java.util.Map; import java.util.Set; import java.util.stream.Collectors; - import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.ArrayType; import org.jboss.jandex.ClassInfo; @@ -72,7 +71,8 @@ static void addDelegatingMethods(IndexView index, ClassInfo classInfo, Map typeVariables = key.method.typeParameters(); - return MethodInfo.create(classInfo, key.method.name(), params, returnType, key.method.flags(), typeVariables.toArray(new TypeVariable[] {}), + return MethodInfo.create(classInfo, key.method.name(), params, returnType, key.method.flags(), + typeVariables.toArray(new TypeVariable[] {}), key.method.exceptions().toArray(Type.EMPTY_ARRAY)); }); } @@ -82,7 +82,8 @@ static void addDelegatingMethods(IndexView index, ClassInfo classInfo, Map resolved = Collections.emptyMap(); if (org.jboss.jandex.Type.Kind.PARAMETERIZED_TYPE.equals(interfaceType.kind())) { - resolved = Types.buildResolvedMap(interfaceType.asParameterizedType().arguments(), interfaceClassInfo.typeParameters(), + resolved = Types.buildResolvedMap(interfaceType.asParameterizedType().arguments(), + interfaceClassInfo.typeParameters(), resolvedTypeParameters); } addDelegatingMethods(index, interfaceClassInfo, resolved, methods); @@ -93,7 +94,8 @@ static void addDelegatingMethods(IndexView index, ClassInfo classInfo, Map resolved = Collections.emptyMap(); if (org.jboss.jandex.Type.Kind.PARAMETERIZED_TYPE.equals(classInfo.superClassType().kind())) { - resolved = Types.buildResolvedMap(classInfo.superClassType().asParameterizedType().arguments(), superClassInfo.typeParameters(), + resolved = Types.buildResolvedMap(classInfo.superClassType().asParameterizedType().arguments(), + superClassInfo.typeParameters(), resolvedTypeParameters); } addDelegatingMethods(index, superClassInfo, resolved, methods); @@ -115,7 +117,8 @@ private static boolean skipForClientProxy(MethodInfo method) { return false; } - static void addInterceptedMethodCandidates(BeanDeployment beanDeployment, ClassInfo classInfo, Map> candidates, + static void addInterceptedMethodCandidates(BeanDeployment beanDeployment, ClassInfo classInfo, + Map> candidates, List classLevelBindings) { for (MethodInfo method : classInfo.methods()) { if (skipForSubclass(method)) { @@ -176,7 +179,8 @@ static Type resolveType(Type type, Map resolvedTypeParameter case WILDCARD_TYPE: WildcardType wildcardType = type.asWildcardType(); return WildcardType.create( - resolveType(wildcardType.superBound() != null ? wildcardType.superBound() : wildcardType.extendsBound(), resolvedTypeParameters), + resolveType(wildcardType.superBound() != null ? wildcardType.superBound() : wildcardType.extendsBound(), + resolvedTypeParameters), wildcardType.superBound() == null); case ARRAY: ArrayType arrayType = type.asArrayType(); @@ -196,7 +200,7 @@ public MethodKey(MethodInfo method) { this.method = method; this.name = method.name(); this.params = new ArrayList<>(); - for(Type i : method.parameters()) { + for (Type i : method.parameters()) { params.add(i.name()); } } 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 574e752d76f1f..a4516378e6fbd 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 @@ -20,6 +20,22 @@ import static org.objectweb.asm.Opcodes.ACC_PRIVATE; import static org.objectweb.asm.Opcodes.ACC_PUBLIC; +import io.quarkus.arc.CreationalContextImpl; +import io.quarkus.arc.CurrentInjectionPointProvider; +import io.quarkus.arc.InjectableBean; +import io.quarkus.arc.InjectableObserverMethod; +import io.quarkus.arc.InjectableReferenceProvider; +import io.quarkus.arc.processor.BeanProcessor.PrivateMembersCollector; +import io.quarkus.arc.processor.ResourceOutput.Resource; +import io.quarkus.arc.processor.ResourceOutput.Resource.SpecialType; +import io.quarkus.gizmo.ClassCreator; +import io.quarkus.gizmo.ClassOutput; +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; +import java.lang.reflect.Member; import java.lang.reflect.Modifier; import java.lang.reflect.Type; import java.util.ArrayList; @@ -32,28 +48,11 @@ import java.util.Map; import java.util.Set; import java.util.function.Predicate; - import javax.enterprise.inject.spi.EventContext; import javax.enterprise.inject.spi.ObserverMethod; - import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; -import io.quarkus.arc.CreationalContextImpl; -import io.quarkus.arc.CurrentInjectionPointProvider; -import io.quarkus.arc.InjectableBean; -import io.quarkus.arc.InjectableObserverMethod; -import io.quarkus.arc.InjectableReferenceProvider; -import io.quarkus.arc.processor.BeanProcessor.PrivateMembersCollector; -import io.quarkus.arc.processor.ResourceOutput.Resource; -import io.quarkus.arc.processor.ResourceOutput.Resource.SpecialType; -import io.quarkus.gizmo.ClassCreator; -import io.quarkus.gizmo.ClassOutput; -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; /** * @@ -66,9 +65,9 @@ public class ObserverGenerator extends AbstractGenerator { private final AnnotationLiteralProcessor annotationLiterals; private final Predicate applicationClassPredicate; - + private final PrivateMembersCollector privateMembers; - + public ObserverGenerator(AnnotationLiteralProcessor annotationLiterals, Predicate applicationClassPredicate, PrivateMembersCollector privateMembers) { this.annotationLiterals = annotationLiterals; @@ -86,7 +85,8 @@ Collection generate(ObserverInfo observer, ReflectionRegistration refl ClassInfo declaringClass = observer.getObserverMethod().declaringClass(); String declaringClassBase; if (declaringClass.enclosingClass() != null) { - declaringClassBase = DotNames.simpleName(declaringClass.enclosingClass()) + "_" + DotNames.simpleName(declaringClass); + declaringClassBase = DotNames.simpleName(declaringClass.enclosingClass()) + "_" + + DotNames.simpleName(declaringClass); } else { declaringClassBase = DotNames.simpleName(declaringClass); } @@ -95,21 +95,32 @@ Collection generate(ObserverInfo observer, ReflectionRegistration refl sigBuilder.append(observer.getObserverMethod().name()) .append("_") .append(observer.getObserverMethod().returnType().name().toString()); - for(org.jboss.jandex.Type paramType : observer.getObserverMethod().parameters()) { + for (org.jboss.jandex.Type paramType : observer.getObserverMethod().parameters()) { sigBuilder.append(paramType.name().toString()); } - String baseName = declaringClassBase + OBSERVER_SUFFIX + "_" + observer.getObserverMethod().name() + "_" + Hashes.sha1(sigBuilder.toString()); - String generatedName = DotNames.packageName(declaringClass.name()).replace('.', '/') + "/" + baseName; + + String baseName = declaringClassBase + OBSERVER_SUFFIX + + "_" + observer.getObserverMethod().name() + + "_" + Hashes.sha1(sigBuilder.toString()); + + // No suffix added at the end of generated name because it's already + // included in a baseName, e.g. Foo_Observer_fooMethod_hash + + String targetPackage = DotNames.packageName(declaringClass.name()); + String generatedName = generatedNameFromTarget(targetPackage, baseName, ""); boolean isApplicationClass = applicationClassPredicate.test(observer.getObserverMethod().declaringClass().name()); - ResourceClassOutput classOutput = new ResourceClassOutput(isApplicationClass, name -> name.equals(generatedName) ? SpecialType.OBSERVER : null); + ResourceClassOutput classOutput = new ResourceClassOutput(isApplicationClass, + name -> name.equals(generatedName) ? SpecialType.OBSERVER : null); // Foo_Observer_fooMethod_hash implements ObserverMethod - ClassCreator observerCreator = ClassCreator.builder().classOutput(classOutput).className(generatedName).interfaces(InjectableObserverMethod.class) + ClassCreator observerCreator = ClassCreator.builder().classOutput(classOutput).className(generatedName) + .interfaces(InjectableObserverMethod.class) .build(); // Fields - FieldCreator observedType = observerCreator.getFieldCreator("observedType", Type.class).setModifiers(ACC_PRIVATE | ACC_FINAL); + FieldCreator observedType = observerCreator.getFieldCreator("observedType", Type.class) + .setModifiers(ACC_PRIVATE | ACC_FINAL); FieldCreator observedQualifiers = null; if (!observer.getQualifiers().isEmpty()) { observedQualifiers = observerCreator.getFieldCreator("qualifiers", Set.class).setModifiers(ACC_PRIVATE | ACC_FINAL); @@ -141,18 +152,25 @@ Collection generate(ObserverInfo observer, ReflectionRegistration refl protected void initMaps(ObserverInfo observer, Map injectionPointToProvider) { int providerIdx = 1; for (InjectionPointInfo injectionPoint : observer.getInjection().injectionPoints) { + if (injectionPoint.getRequiredType().name().equals(DotNames.EVENT_METADATA)) { + // We do not need a provider for event metadata + continue; + } injectionPointToProvider.put(injectionPoint, "observerProvider" + providerIdx++); } } protected void implementGetObservedType(ClassCreator observerCreator, FieldDescriptor observedTypeField) { - MethodCreator getObservedType = observerCreator.getMethodCreator("getObservedType", Type.class).setModifiers(ACC_PUBLIC); + MethodCreator getObservedType = observerCreator.getMethodCreator("getObservedType", Type.class) + .setModifiers(ACC_PUBLIC); getObservedType.returnValue(getObservedType.readInstanceField(observedTypeField, getObservedType.getThis())); } protected void implementGetObservedQualifiers(ClassCreator observerCreator, FieldDescriptor observedQualifiersField) { - MethodCreator getObservedQualifiers = observerCreator.getMethodCreator("getObservedQualifiers", Set.class).setModifiers(ACC_PUBLIC); - getObservedQualifiers.returnValue(getObservedQualifiers.readInstanceField(observedQualifiersField, getObservedQualifiers.getThis())); + MethodCreator getObservedQualifiers = observerCreator.getMethodCreator("getObservedQualifiers", Set.class) + .setModifiers(ACC_PUBLIC); + getObservedQualifiers + .returnValue(getObservedQualifiers.readInstanceField(observedQualifiersField, getObservedQualifiers.getThis())); } protected void implementGetBeanClass(ClassCreator observerCreator, DotName beanClass) { @@ -170,22 +188,30 @@ protected void implementIsAsync(ClassCreator observerCreator) { isAsync.returnValue(isAsync.load(true)); } - protected void implementNotify(ObserverInfo observer, ClassCreator observerCreator, Map injectionPointToProviderField, + protected void implementNotify(ObserverInfo observer, ClassCreator observerCreator, + Map injectionPointToProviderField, ReflectionRegistration reflectionRegistration, boolean isApplicationClass) { - MethodCreator notify = observerCreator.getMethodCreator("notify", void.class, EventContext.class).setModifiers(ACC_PUBLIC); + MethodCreator notify = observerCreator.getMethodCreator("notify", void.class, EventContext.class) + .setModifiers(ACC_PUBLIC); ResultHandle declaringProviderHandle = notify - .readInstanceField(FieldDescriptor.of(observerCreator.getClassName(), "declaringProvider", InjectableBean.class.getName()), notify.getThis()); - + .readInstanceField( + FieldDescriptor.of(observerCreator.getClassName(), "declaringProvider", InjectableBean.class.getName()), + notify.getThis()); + // It is safe to skip CreationalContext.release() for normal scoped declaring provider and no injection points - boolean skipRelease = observer.getDeclaringBean().getScope().isNormal() && observer.getInjection().injectionPoints.isEmpty(); - ResultHandle ctxHandle = skipRelease ? notify.loadNull() : notify.newInstance(MethodDescriptor.ofConstructor(CreationalContextImpl.class)); - ResultHandle declaringProviderInstanceHandle = notify.invokeInterfaceMethod(MethodDescriptors.INJECTABLE_REF_PROVIDER_GET, declaringProviderHandle, + boolean skipRelease = observer.getDeclaringBean().getScope().isNormal() + && observer.getInjection().injectionPoints.isEmpty(); + ResultHandle ctxHandle = skipRelease ? notify.loadNull() + : notify.newInstance(MethodDescriptor.ofConstructor(CreationalContextImpl.class)); + ResultHandle declaringProviderInstanceHandle = notify.invokeInterfaceMethod( + MethodDescriptors.INJECTABLE_REF_PROVIDER_GET, declaringProviderHandle, ctxHandle); if (observer.getDeclaringBean().getScope().isNormal()) { // We need to unwrap the client proxy - declaringProviderInstanceHandle = notify.invokeInterfaceMethod(MethodDescriptors.CLIENT_PROXY_GET_CONTEXTUAL_INSTANCE, + declaringProviderInstanceHandle = notify.invokeInterfaceMethod( + MethodDescriptors.CLIENT_PROXY_GET_CONTEXTUAL_INSTANCE, declaringProviderInstanceHandle); } @@ -194,33 +220,41 @@ protected void implementNotify(ObserverInfo observer, ClassCreator observerCreat Iterator injectionPointsIterator = observer.getInjection().injectionPoints.iterator(); for (int i = 0; i < observer.getObserverMethod().parameters().size(); i++) { if (i == eventParamPosition) { - referenceHandles[i] = notify.invokeInterfaceMethod(MethodDescriptors.EVENT_CONTEXT_GET_EVENT, notify.getMethodParam(0)); + referenceHandles[i] = notify.invokeInterfaceMethod(MethodDescriptors.EVENT_CONTEXT_GET_EVENT, + notify.getMethodParam(0)); } else if (i == observer.getEventMetadataParameterPosition()) { - referenceHandles[i] = notify.invokeInterfaceMethod(MethodDescriptors.EVENT_CONTEXT_GET_METADATA, notify.getMethodParam(0)); + referenceHandles[i] = notify.invokeInterfaceMethod(MethodDescriptors.EVENT_CONTEXT_GET_METADATA, + notify.getMethodParam(0)); } else { ResultHandle childCtxHandle = notify.invokeStaticMethod(MethodDescriptors.CREATIONAL_CTX_CHILD, ctxHandle); ResultHandle providerHandle = notify.readInstanceField(FieldDescriptor.of(observerCreator.getClassName(), - injectionPointToProviderField.get(injectionPointsIterator.next()), InjectableReferenceProvider.class.getName()), notify.getThis()); - ResultHandle referenceHandle = notify.invokeInterfaceMethod(MethodDescriptors.INJECTABLE_REF_PROVIDER_GET, providerHandle, childCtxHandle); + injectionPointToProviderField.get(injectionPointsIterator.next()), + InjectableReferenceProvider.class.getName()), notify.getThis()); + ResultHandle referenceHandle = notify.invokeInterfaceMethod(MethodDescriptors.INJECTABLE_REF_PROVIDER_GET, + providerHandle, childCtxHandle); referenceHandles[i] = referenceHandle; } } if (Modifier.isPrivate(observer.getObserverMethod().flags())) { privateMembers.add(isApplicationClass, - String.format("Observer method %s#%s()", observer.getObserverMethod().declaringClass().name(), observer.getObserverMethod().name())); + String.format("Observer method %s#%s()", observer.getObserverMethod().declaringClass().name(), + observer.getObserverMethod().name())); ResultHandle paramTypesArray = notify.newArray(Class.class, notify.load(referenceHandles.length)); ResultHandle argsArray = notify.newArray(Object.class, notify.load(referenceHandles.length)); for (int i = 0; i < referenceHandles.length; i++) { - notify.writeArrayValue(paramTypesArray, i, notify.loadClass(observer.getObserverMethod().parameters().get(i).name().toString())); + notify.writeArrayValue(paramTypesArray, i, + notify.loadClass(observer.getObserverMethod().parameters().get(i).name().toString())); notify.writeArrayValue(argsArray, i, referenceHandles[i]); } reflectionRegistration.registerMethod(observer.getObserverMethod()); notify.invokeStaticMethod(MethodDescriptors.REFLECTIONS_INVOKE_METHOD, - notify.loadClass(observer.getObserverMethod().declaringClass().name().toString()), notify.load(observer.getObserverMethod().name()), + notify.loadClass(observer.getObserverMethod().declaringClass().name().toString()), + notify.load(observer.getObserverMethod().name()), paramTypesArray, declaringProviderInstanceHandle, argsArray); } else { - notify.invokeVirtualMethod(MethodDescriptor.of(observer.getObserverMethod()), declaringProviderInstanceHandle, referenceHandles); + notify.invokeVirtualMethod(MethodDescriptor.of(observer.getObserverMethod()), declaringProviderInstanceHandle, + referenceHandles); } // Destroy @Dependent instances injected into method parameters of an observer method @@ -230,13 +264,15 @@ protected void implementNotify(ObserverInfo observer, ClassCreator observerCreat // If the declaring bean is @Dependent we must destroy the instance afterwards if (BuiltinScope.DEPENDENT.is(observer.getDeclaringBean().getScope())) { - notify.invokeInterfaceMethod(MethodDescriptors.INJECTABLE_BEAN_DESTROY, declaringProviderHandle, declaringProviderInstanceHandle, ctxHandle); + notify.invokeInterfaceMethod(MethodDescriptors.INJECTABLE_BEAN_DESTROY, declaringProviderHandle, + declaringProviderInstanceHandle, ctxHandle); } notify.returnValue(null); } - protected void createProviderFields(ClassCreator observerCreator, ObserverInfo observer, Map injectionPointToProvider) { + protected void createProviderFields(ClassCreator observerCreator, ObserverInfo observer, + Map injectionPointToProvider) { // Declaring bean provider observerCreator.getFieldCreator("declaringProvider", InjectableBean.class).setModifiers(ACC_PRIVATE | ACC_FINAL); // Injection points @@ -245,7 +281,8 @@ protected void createProviderFields(ClassCreator observerCreator, ObserverInfo o } } - protected void createConstructor(ClassOutput classOutput, ClassCreator observerCreator, ObserverInfo observer, String baseName, + protected void createConstructor(ClassOutput classOutput, ClassCreator observerCreator, ObserverInfo observer, + String baseName, Map injectionPointToProviderField, AnnotationLiteralProcessor annotationLiterals) { // First collect all param types @@ -262,7 +299,8 @@ protected void createConstructor(ClassOutput classOutput, ClassCreator observerC constructor.invokeSpecialMethod(MethodDescriptors.OBJECT_CONSTRUCTOR, constructor.getThis()); int paramIdx = 0; - constructor.writeInstanceField(FieldDescriptor.of(observerCreator.getClassName(), "declaringProvider", InjectableBean.class.getName()), + constructor.writeInstanceField( + FieldDescriptor.of(observerCreator.getClassName(), "declaringProvider", InjectableBean.class.getName()), constructor.getThis(), constructor.getMethodParam(0)); paramIdx++; for (InjectionPointInfo injectionPoint : observer.getInjection().injectionPoints) { @@ -272,40 +310,47 @@ protected void createConstructor(ClassOutput classOutput, ClassCreator observerC builtinBean = BuiltinBean.resolve(injectionPoint); } if (builtinBean != null) { - builtinBean.getGenerator().generate(classOutput, observer.getDeclaringBean().getDeployment(), injectionPoint, observerCreator, constructor, + builtinBean.getGenerator().generate(classOutput, observer.getDeclaringBean().getDeployment(), injectionPoint, + observerCreator, constructor, injectionPointToProviderField.get(injectionPoint), annotationLiterals); } else { if (injectionPoint.getResolvedBean().getAllInjectionPoints().stream() .anyMatch(ip -> BuiltinBean.INJECTION_POINT.getRawTypeDotName().equals(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 = constructor.newInstance(MethodDescriptor.ofConstructor(HashSet.class)); - for (AnnotationInstance qualifierAnnotation : injectionPoint.getRequiredQualifiers()) { - BuiltinQualifier qualifier = BuiltinQualifier.of(qualifierAnnotation); - if (qualifier != null) { - constructor.invokeInterfaceMethod(MethodDescriptors.SET_ADD, requiredQualifiersHandle, qualifier.getLiteralInstance(constructor)); - } else { - // Create annotation literal first - ClassInfo qualifierClass = observer.getDeclaringBean().getDeployment().getQualifier(qualifierAnnotation.name()); - constructor.invokeInterfaceMethod(MethodDescriptors.SET_ADD, requiredQualifiersHandle, annotationLiterals.process(constructor, - classOutput, qualifierClass, qualifierAnnotation, Types.getPackageName(observerCreator.getClassName()))); - } - } + ResultHandle requiredQualifiersHandle = BeanGenerator.collectQualifiers(classOutput, observerCreator, + observer.getDeclaringBean().getDeployment(), constructor, + injectionPoint, + annotationLiterals); + ResultHandle annotationsHandle = BeanGenerator.collectAnnotations(classOutput, observerCreator, + observer.getDeclaringBean().getDeployment(), constructor, + injectionPoint, annotationLiterals); + ResultHandle javaMemberHandle = BeanGenerator.getJavaMemberHandle(constructor, injectionPoint); ResultHandle wrapHandle = constructor.newInstance( - MethodDescriptor.ofConstructor(CurrentInjectionPointProvider.class, InjectableReferenceProvider.class, java.lang.reflect.Type.class, - Set.class), - constructor.getMethodParam(paramIdx++), Types.getTypeHandle(constructor, injectionPoint.getRequiredType()), requiredQualifiersHandle); - constructor.writeInstanceField(FieldDescriptor.of(observerCreator.getClassName(), injectionPointToProviderField.get(injectionPoint), + MethodDescriptor.ofConstructor(CurrentInjectionPointProvider.class, InjectableBean.class, + InjectableReferenceProvider.class, java.lang.reflect.Type.class, + Set.class, Set.class, Member.class, int.class), + constructor.getThis(), constructor.getMethodParam(paramIdx++), + Types.getTypeHandle(constructor, injectionPoint.getRequiredType()), + requiredQualifiersHandle, annotationsHandle, javaMemberHandle, + constructor.load(injectionPoint.getPosition())); + + constructor.writeInstanceField(FieldDescriptor.of(observerCreator.getClassName(), + injectionPointToProviderField.get(injectionPoint), InjectableReferenceProvider.class.getName()), constructor.getThis(), wrapHandle); } else { - constructor.writeInstanceField(FieldDescriptor.of(observerCreator.getClassName(), injectionPointToProviderField.get(injectionPoint), - InjectableReferenceProvider.class.getName()), constructor.getThis(), constructor.getMethodParam(paramIdx++)); + constructor.writeInstanceField( + FieldDescriptor.of(observerCreator.getClassName(), + injectionPointToProviderField.get(injectionPoint), + InjectableReferenceProvider.class.getName()), + constructor.getThis(), constructor.getMethodParam(paramIdx++)); } } } // Observed type - constructor.writeInstanceField(FieldDescriptor.of(observerCreator.getClassName(), "observedType", Type.class.getName()), constructor.getThis(), + constructor.writeInstanceField(FieldDescriptor.of(observerCreator.getClassName(), "observedType", Type.class.getName()), + constructor.getThis(), Types.getTypeHandle(constructor, observer.getObservedType())); // Qualifiers @@ -317,17 +362,23 @@ protected void createConstructor(ClassOutput classOutput, ClassCreator observerC for (AnnotationInstance qualifierAnnotation : qualifiers) { BuiltinQualifier qualifier = BuiltinQualifier.of(qualifierAnnotation); if (qualifier != null) { - constructor.invokeInterfaceMethod(MethodDescriptors.SET_ADD, qualifiersHandle, qualifier.getLiteralInstance(constructor)); + constructor.invokeInterfaceMethod(MethodDescriptors.SET_ADD, qualifiersHandle, + qualifier.getLiteralInstance(constructor)); } else { // Create annotation literal first - ClassInfo qualifierClass = observer.getDeclaringBean().getDeployment().getQualifier(qualifierAnnotation.name()); - constructor.invokeInterfaceMethod(MethodDescriptors.SET_ADD, qualifiersHandle, annotationLiterals.process(constructor, classOutput, - qualifierClass, qualifierAnnotation, Types.getPackageName(observerCreator.getClassName()))); + ClassInfo qualifierClass = observer.getDeclaringBean().getDeployment() + .getQualifier(qualifierAnnotation.name()); + constructor.invokeInterfaceMethod(MethodDescriptors.SET_ADD, qualifiersHandle, + annotationLiterals.process(constructor, classOutput, + qualifierClass, qualifierAnnotation, Types.getPackageName(observerCreator.getClassName()))); } } ResultHandle unmodifiableQualifiersHandle = constructor - .invokeStaticMethod(MethodDescriptor.ofMethod(Collections.class, "unmodifiableSet", Set.class, Set.class), qualifiersHandle); - constructor.writeInstanceField(FieldDescriptor.of(observerCreator.getClassName(), "qualifiers", Set.class.getName()), constructor.getThis(), + .invokeStaticMethod(MethodDescriptor.ofMethod(Collections.class, "unmodifiableSet", Set.class, Set.class), + qualifiersHandle); + constructor.writeInstanceField( + FieldDescriptor.of(observerCreator.getClassName(), "qualifiers", Set.class.getName()), + constructor.getThis(), unmodifiableQualifiersHandle); } 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 dc872a7d6def1..9e1469c54e803 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 @@ -21,10 +21,8 @@ import java.util.List; import java.util.ListIterator; import java.util.Set; - import javax.enterprise.inject.spi.DefinitionException; import javax.enterprise.inject.spi.ObserverMethod; - import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationTarget.Kind; import org.jboss.jandex.MethodInfo; @@ -36,7 +34,7 @@ * * @author Martin Kouba */ -public class ObserverInfo { +public class ObserverInfo implements InjectionTargetInfo { private final BeanInfo declaringBean; @@ -67,6 +65,16 @@ public class ObserverInfo { this.isAsync = isAsync; } + @Override + public TargetKind kind() { + return TargetKind.OBSERVER; + } + + @Override + public ObserverInfo asObserver() { + return this; + } + public BeanInfo getDeclaringBean() { return declaringBean; } @@ -93,7 +101,7 @@ public boolean isAsync() { void init(List errors) { for (InjectionPointInfo injectionPoint : injection.injectionPoints) { - Beans.resolveInjectionPoint(declaringBean.getDeployment(), getDeclaringBean(), injectionPoint, errors); + Beans.resolveInjectionPoint(declaringBean.getDeployment(), this, injectionPoint, errors); } } @@ -104,7 +112,8 @@ public Type getObservedType() { public Set getQualifiers() { Set qualifiers = new HashSet<>(); for (AnnotationInstance annotation : declaringBean.getDeployment().getAnnotations(observerMethod)) { - if (annotation.target().equals(eventParameter) && declaringBean.getDeployment().getQualifier(annotation.name()) != null) { + if (annotation.target().equals(eventParameter) + && declaringBean.getDeployment().getQualifier(annotation.name()) != null) { qualifiers.add(annotation); } } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ResourceClassOutput.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ResourceClassOutput.java index 8b2251aa77b2b..39c3869259020 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ResourceClassOutput.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ResourceClassOutput.java @@ -16,13 +16,12 @@ package io.quarkus.arc.processor; -import java.util.ArrayList; -import java.util.List; -import java.util.function.Function; - import io.quarkus.arc.processor.ResourceOutput.Resource; import io.quarkus.arc.processor.ResourceOutput.Resource.SpecialType; import io.quarkus.gizmo.ClassOutput; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; /** * @@ -47,7 +46,8 @@ public ResourceClassOutput(boolean applicationClass, Function getResources() { diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ResourceImpl.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ResourceImpl.java index 44fa3445936de..431f2b8f79947 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ResourceImpl.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ResourceImpl.java @@ -16,14 +16,13 @@ package io.quarkus.arc.processor; +import io.quarkus.arc.processor.ResourceOutput.Resource; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import io.quarkus.arc.processor.ResourceOutput.Resource; - /** * * @author Martin Kouba diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ResourceOutput.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ResourceOutput.java index 7f2ae5ad592b4..7afd1cef3a32b 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ResourceOutput.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ResourceOutput.java @@ -76,11 +76,15 @@ default String getFullyQualifiedName() { SpecialType getSpecialType(); enum Type { - JAVA_CLASS, JAVA_SOURCE, SERVICE_PROVIDER, + JAVA_CLASS, + JAVA_SOURCE, + SERVICE_PROVIDER, } enum SpecialType { - BEAN, INTERCEPTOR_BEAN, OBSERVER; + BEAN, + INTERCEPTOR_BEAN, + OBSERVER; } } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ScopeInfo.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ScopeInfo.java index 16f17ff134115..0a4bd8785d1d2 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ScopeInfo.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ScopeInfo.java @@ -18,7 +18,6 @@ import java.lang.annotation.Annotation; import java.util.Objects; - import org.jboss.jandex.DotName; public class ScopeInfo { diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/StereotypeInfo.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/StereotypeInfo.java index 85f3d8d3a3aab..b85f9232013af 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/StereotypeInfo.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/StereotypeInfo.java @@ -17,7 +17,6 @@ package io.quarkus.arc.processor; import java.util.List; - import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.ClassInfo; @@ -28,12 +27,13 @@ public class StereotypeInfo { private final List interceptorBindings; private final boolean isAlternative; - + private final boolean isNamed; private final ClassInfo target; - public StereotypeInfo(ScopeInfo defaultScope, List interceptorBindings, boolean isAlternative, boolean isNamed, ClassInfo target) { + public StereotypeInfo(ScopeInfo defaultScope, List interceptorBindings, boolean isAlternative, + boolean isNamed, ClassInfo target) { this.defaultScope = defaultScope; this.interceptorBindings = interceptorBindings; this.isAlternative = isAlternative; @@ -52,7 +52,7 @@ public List getInterceptorBindings() { public boolean isAlternative() { return isAlternative; } - + public boolean isNamed() { return isNamed; } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/SubclassGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/SubclassGenerator.java index 282510a64acdd..98d2a2aeb87b8 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/SubclassGenerator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/SubclassGenerator.java @@ -19,6 +19,23 @@ import static org.objectweb.asm.Opcodes.ACC_FINAL; import static org.objectweb.asm.Opcodes.ACC_PRIVATE; +import io.quarkus.arc.InjectableInterceptor; +import io.quarkus.arc.InvocationContextImpl.InterceptorInvocation; +import io.quarkus.arc.Subclass; +import io.quarkus.arc.processor.BeanInfo.InterceptionInfo; +import io.quarkus.arc.processor.ResourceOutput.Resource; +import io.quarkus.gizmo.BytecodeCreator; +import io.quarkus.gizmo.CatchBlockCreator; +import io.quarkus.gizmo.ClassCreator; +import io.quarkus.gizmo.ClassOutput; +import io.quarkus.gizmo.DescriptorUtils; +import io.quarkus.gizmo.FieldCreator; +import io.quarkus.gizmo.FieldDescriptor; +import io.quarkus.gizmo.FunctionCreator; +import io.quarkus.gizmo.MethodCreator; +import io.quarkus.gizmo.MethodDescriptor; +import io.quarkus.gizmo.ResultHandle; +import io.quarkus.gizmo.TryBlock; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -31,33 +48,14 @@ import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; - import javax.enterprise.context.spi.CreationalContext; import javax.enterprise.inject.spi.InterceptionType; import javax.interceptor.InvocationContext; - import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; import org.jboss.jandex.MethodInfo; import org.jboss.jandex.Type; -import io.quarkus.arc.InjectableInterceptor; -import io.quarkus.arc.InvocationContextImpl.InterceptorInvocation; -import io.quarkus.arc.Subclass; -import io.quarkus.arc.processor.BeanInfo.InterceptionInfo; -import io.quarkus.arc.processor.ResourceOutput.Resource; -import io.quarkus.gizmo.BytecodeCreator; -import io.quarkus.gizmo.CatchBlockCreator; -import io.quarkus.gizmo.ClassCreator; -import io.quarkus.gizmo.ClassOutput; -import io.quarkus.gizmo.DescriptorUtils; -import io.quarkus.gizmo.FieldCreator; -import io.quarkus.gizmo.FieldDescriptor; -import io.quarkus.gizmo.FunctionCreator; -import io.quarkus.gizmo.MethodCreator; -import io.quarkus.gizmo.MethodDescriptor; -import io.quarkus.gizmo.ResultHandle; -import io.quarkus.gizmo.TryBlock; /** * @@ -72,6 +70,7 @@ public class SubclassGenerator extends AbstractGenerator { static final String SUBCLASS_SUFFIX = "_Subclass"; private final Predicate applicationClassPredicate; + static String generatedName(DotName providerTypeName, String baseName) { return DotNames.packageName(providerTypeName).replace('.', '/') + "/" + baseName + SUBCLASS_SUFFIX; } @@ -105,17 +104,20 @@ Collection generate(BeanInfo bean, String beanClassName, ReflectionReg String generatedName = generatedName(providerType.name(), baseName); // Foo_Subclass extends Foo implements Subclass - ClassCreator subclass = ClassCreator.builder().classOutput(classOutput).className(generatedName).superClass(providerTypeName).interfaces(Subclass.class) + ClassCreator subclass = ClassCreator.builder().classOutput(classOutput).className(generatedName) + .superClass(providerTypeName).interfaces(Subclass.class) .build(); - FieldDescriptor preDestroyField = createConstructor(classOutput, bean, subclass, providerTypeName, reflectionRegistration); + FieldDescriptor preDestroyField = createConstructor(classOutput, bean, subclass, providerTypeName, + reflectionRegistration); createDestroy(classOutput, bean, subclass, preDestroyField); subclass.close(); return classOutput.getResources(); } - protected FieldDescriptor createConstructor(ClassOutput classOutput, BeanInfo bean, ClassCreator subclass, String providerTypeName, + protected FieldDescriptor createConstructor(ClassOutput classOutput, BeanInfo bean, ClassCreator subclass, + String providerTypeName, ReflectionRegistration reflectionRegistration) { List parameterTypes = new ArrayList<>(); @@ -146,7 +148,9 @@ protected FieldDescriptor createConstructor(ClassOutput classOutput, BeanInfo be superParams[j] = constructor.getMethodParam(j); } // super(fooProvider) - constructor.invokeSpecialMethod(MethodDescriptor.ofConstructor(providerTypeName, parameterTypes.subList(0, superParamsSize).toArray(new String[0])), + constructor.invokeSpecialMethod( + MethodDescriptor.ofConstructor(providerTypeName, + parameterTypes.subList(0, superParamsSize).toArray(new String[0])), constructor.getThis(), superParams); Map interceptorToResultHandle = new HashMap<>(); @@ -166,26 +170,34 @@ protected FieldDescriptor createConstructor(ClassOutput classOutput, BeanInfo be constructor.newInstance(MethodDescriptor.ofConstructor(ArrayList.class))); for (InterceptorInfo interceptor : preDestroys.interceptors) { // preDestroys.add(InvocationContextImpl.InterceptorInvocation.preDestroy(provider1,provider1.get(CreationalContextImpl.child(ctx)))) - ResultHandle creationalContext = constructor.invokeStaticMethod(MethodDescriptors.CREATIONAL_CTX_CHILD, creationalContextHandle); - ResultHandle interceptorInstance = constructor.invokeInterfaceMethod(MethodDescriptors.INJECTABLE_REF_PROVIDER_GET, + ResultHandle creationalContext = constructor.invokeStaticMethod(MethodDescriptors.CREATIONAL_CTX_CHILD, + creationalContextHandle); + ResultHandle interceptorInstance = constructor.invokeInterfaceMethod( + MethodDescriptors.INJECTABLE_REF_PROVIDER_GET, interceptorToResultHandle.get(interceptor), creationalContext); - ResultHandle interceptionInvocation = constructor.invokeStaticMethod(MethodDescriptor.ofMethod(InterceptorInvocation.class, "preDestroy", - InterceptorInvocation.class, InjectableInterceptor.class, Object.class), interceptorToResultHandle.get(interceptor), + ResultHandle interceptionInvocation = constructor.invokeStaticMethod( + MethodDescriptor.ofMethod(InterceptorInvocation.class, "preDestroy", + InterceptorInvocation.class, InjectableInterceptor.class, Object.class), + interceptorToResultHandle.get(interceptor), interceptorInstance); constructor.invokeInterfaceMethod(MethodDescriptors.LIST_ADD, - constructor.readInstanceField(preDestroysField.getFieldDescriptor(), constructor.getThis()), interceptionInvocation); + constructor.readInstanceField(preDestroysField.getFieldDescriptor(), constructor.getThis()), + interceptionInvocation); } } // Init intercepted methods and interceptor chains // private final Map> interceptorChains - FieldCreator interceptorChainsField = subclass.getFieldCreator("interceptorChains", Map.class.getName()).setModifiers(ACC_PRIVATE | ACC_FINAL); + FieldCreator interceptorChainsField = subclass.getFieldCreator("interceptorChains", Map.class.getName()) + .setModifiers(ACC_PRIVATE | ACC_FINAL); // interceptorChains = new HashMap<>() constructor.writeInstanceField(interceptorChainsField.getFieldDescriptor(), constructor.getThis(), constructor.newInstance(MethodDescriptor.ofConstructor(HashMap.class))); - ResultHandle interceptorChainsHandle = constructor.readInstanceField(interceptorChainsField.getFieldDescriptor(), constructor.getThis()); + ResultHandle interceptorChainsHandle = constructor.readInstanceField(interceptorChainsField.getFieldDescriptor(), + constructor.getThis()); // private final Map methods - FieldCreator methodsField = subclass.getFieldCreator("methods", DescriptorUtils.extToInt(Map.class.getName())).setModifiers(ACC_PRIVATE | ACC_FINAL); + FieldCreator methodsField = subclass.getFieldCreator("methods", DescriptorUtils.extToInt(Map.class.getName())) + .setModifiers(ACC_PRIVATE | ACC_FINAL); constructor.writeInstanceField(methodsField.getFieldDescriptor(), constructor.getThis(), constructor.newInstance(MethodDescriptor.ofConstructor(HashMap.class))); ResultHandle methodsHandle = constructor.readInstanceField(methodsField.getFieldDescriptor(), constructor.getThis()); @@ -202,10 +214,13 @@ protected FieldDescriptor createConstructor(ClassOutput classOutput, BeanInfo be InterceptionInfo interceptedMethod = entry.getValue(); for (InterceptorInfo interceptor : interceptedMethod.interceptors) { // m1Chain.add(InvocationContextImpl.InterceptorInvocation.aroundInvoke(p3,p3.get(CreationalContextImpl.child(ctx)))) - ResultHandle creationalContext = constructor.invokeStaticMethod(MethodDescriptors.CREATIONAL_CTX_CHILD, creationalContextHandle); - ResultHandle interceptorInstance = constructor.invokeInterfaceMethod(MethodDescriptors.INJECTABLE_REF_PROVIDER_GET, + ResultHandle creationalContext = constructor.invokeStaticMethod(MethodDescriptors.CREATIONAL_CTX_CHILD, + creationalContextHandle); + ResultHandle interceptorInstance = constructor.invokeInterfaceMethod( + MethodDescriptors.INJECTABLE_REF_PROVIDER_GET, interceptorToResultHandle.get(interceptor), creationalContext); - ResultHandle interceptionInvocation = constructor.invokeStaticMethod(MethodDescriptors.INTERCEPTOR_INVOCATION_AROUND_INVOKE, + ResultHandle interceptionInvocation = constructor.invokeStaticMethod( + MethodDescriptors.INTERCEPTOR_INVOCATION_AROUND_INVOKE, interceptorToResultHandle.get(interceptor), interceptorInstance); constructor.invokeInterfaceMethod(MethodDescriptors.LIST_ADD, chainHandle, interceptionInvocation); } @@ -218,20 +233,23 @@ protected FieldDescriptor createConstructor(ClassOutput classOutput, BeanInfo be if (!method.parameters().isEmpty()) { ResultHandle paramsArray = constructor.newArray(Class.class, constructor.load(method.parameters().size())); for (ListIterator iterator = method.parameters().listIterator(); iterator.hasNext();) { - constructor.writeArrayValue(paramsArray, iterator.nextIndex(), constructor.loadClass(iterator.next().name().toString())); + constructor.writeArrayValue(paramsArray, iterator.nextIndex(), + constructor.loadClass(iterator.next().name().toString())); } paramsHandles[2] = paramsArray; } else { paramsHandles[2] = constructor.newArray(Class.class, constructor.load(0)); } - ResultHandle methodHandle = constructor.invokeStaticMethod(MethodDescriptors.REFLECTIONS_FIND_METHOD, paramsHandles); + ResultHandle methodHandle = constructor.invokeStaticMethod(MethodDescriptors.REFLECTIONS_FIND_METHOD, + paramsHandles); constructor.invokeInterfaceMethod(MethodDescriptors.MAP_PUT, methodsHandle, methodIdHandle, methodHandle); // Needed when running on substrate VM reflectionRegistration.registerMethod(method); // Finally create the forwarding method - createForwardingMethod(classOutput, bean, method, methodId, subclass, providerTypeName, interceptorChainsField.getFieldDescriptor(), + createForwardingMethod(classOutput, bean, method, methodId, subclass, providerTypeName, + interceptorChainsField.getFieldDescriptor(), methodsField.getFieldDescriptor(), interceptedMethod); } @@ -239,8 +257,10 @@ protected FieldDescriptor createConstructor(ClassOutput classOutput, BeanInfo be return preDestroysField != null ? preDestroysField.getFieldDescriptor() : null; } - private void createForwardingMethod(ClassOutput classOutput, BeanInfo bean, MethodInfo method, String methodId, ClassCreator subclass, - String providerTypeName, FieldDescriptor interceptorChainsField, FieldDescriptor methodsField, InterceptionInfo interceptedMethod) { + private void createForwardingMethod(ClassOutput classOutput, BeanInfo bean, MethodInfo method, String methodId, + ClassCreator subclass, + String providerTypeName, FieldDescriptor interceptorChainsField, FieldDescriptor methodsField, + InterceptionInfo interceptedMethod) { MethodCreator forwardMethod = subclass.getMethodCreator(MethodDescriptor.of(method)); @@ -257,7 +277,8 @@ private void createForwardingMethod(ClassOutput classOutput, BeanInfo bean, Meth BytecodeCreator funcBytecode = func.getBytecode(); ResultHandle ctxHandle = funcBytecode.getMethodParam(0); ResultHandle[] superParamHandles = new ResultHandle[method.parameters().size()]; - ResultHandle ctxParamsHandle = funcBytecode.invokeInterfaceMethod(MethodDescriptor.ofMethod(InvocationContext.class, "getParameters", Object[].class), + ResultHandle ctxParamsHandle = funcBytecode.invokeInterfaceMethod( + MethodDescriptor.ofMethod(InvocationContext.class, "getParameters", Object[].class), ctxHandle); // TODO autoboxing? for (int i = 0; i < superParamHandles.length; i++) { @@ -265,7 +286,8 @@ private void createForwardingMethod(ClassOutput classOutput, BeanInfo bean, Meth } ResultHandle superResult = funcBytecode.invokeSpecialMethod( MethodDescriptor.ofMethod(providerTypeName, method.name(), method.returnType().name().toString(), - method.parameters().stream().map(p -> p.name().toString()).collect(Collectors.toList()).toArray(new String[0])), + method.parameters().stream().map(p -> p.name().toString()).collect(Collectors.toList()) + .toArray(new String[0])), forwardMethod.getThis(), superParamHandles); funcBytecode.returnValue(superResult != null ? superResult : funcBytecode.loadNull()); for (Type declaredException : method.exceptions()) { @@ -300,7 +322,8 @@ private void createForwardingMethod(ClassOutput classOutput, BeanInfo bean, Meth if (addCatchException) { CatchBlockCreator catchOtherExceptions = tryCatch.addCatch(Exception.class); // and wrap them in a new RuntimeException(e) - catchOtherExceptions.throwException(RuntimeException.class, "Error invoking subclass method", catchOtherExceptions.getCaughtException()); + catchOtherExceptions.throwException(RuntimeException.class, "Error invoking subclass method", + catchOtherExceptions.getCaughtException()); } // InvocationContextImpl.aroundInvoke(this, methods.get("m1"), params, interceptorChains.get("m1"), forward) ResultHandle methodIdHandle = tryCatch.load(methodId); @@ -314,10 +337,12 @@ private void createForwardingMethod(ClassOutput classOutput, BeanInfo bean, Meth // Create annotation literals first ClassInfo bindingClass = bean.getDeployment().getInterceptorBinding(binding.name()); tryCatch.invokeInterfaceMethod(MethodDescriptors.SET_ADD, bindingsHandle, - annotationLiterals.process(tryCatch, classOutput, bindingClass, binding, Types.getPackageName(subclass.getClassName()))); + annotationLiterals.process(tryCatch, classOutput, bindingClass, binding, + Types.getPackageName(subclass.getClassName()))); } - ResultHandle invocationContext = tryCatch.invokeStaticMethod(MethodDescriptors.INVOCATION_CONTEXT_AROUND_INVOKE, tryCatch.getThis(), + ResultHandle invocationContext = tryCatch.invokeStaticMethod(MethodDescriptors.INVOCATION_CONTEXT_AROUND_INVOKE, + tryCatch.getThis(), interceptedMethodHandle, paramsHandle, interceptedChainHandle, func.getInstance(), bindingsHandle); // InvocationContext.proceed() ResultHandle ret = tryCatch.invokeInterfaceMethod(MethodDescriptors.INVOCATION_CONTEXT_PROCEED, invocationContext); @@ -332,7 +357,8 @@ private void createForwardingMethod(ClassOutput classOutput, BeanInfo bean, Meth * @param preDestroysField * @see Subclass#destroy() */ - protected void createDestroy(ClassOutput classOutput, BeanInfo bean, ClassCreator subclass, FieldDescriptor preDestroysField) { + protected void createDestroy(ClassOutput classOutput, BeanInfo bean, ClassCreator subclass, + FieldDescriptor preDestroysField) { if (preDestroysField != null) { MethodCreator destroy = subclass.getMethodCreator(MethodDescriptor.ofMethod(Subclass.class, "destroy", void.class)); ResultHandle predestroysHandle = destroy.readInstanceField(preDestroysField, destroy.getThis()); @@ -343,7 +369,8 @@ protected void createDestroy(ClassOutput classOutput, BeanInfo bean, ClassCreato // Create annotation literals first ClassInfo bindingClass = bean.getDeployment().getInterceptorBinding(binding.name()); destroy.invokeInterfaceMethod(MethodDescriptors.SET_ADD, bindingsHandle, - annotationLiterals.process(destroy, classOutput, bindingClass, binding, Types.getPackageName(subclass.getClassName()))); + annotationLiterals.process(destroy, classOutput, bindingClass, binding, + Types.getPackageName(subclass.getClassName()))); } // try @@ -354,7 +381,8 @@ protected void createDestroy(ClassOutput classOutput, BeanInfo bean, ClassCreato exception.throwException(RuntimeException.class, "Error destroying subclass", exception.getCaughtException()); // InvocationContextImpl.preDestroy(this,predestroys) - ResultHandle invocationContext = tryCatch.invokeStaticMethod(MethodDescriptors.INVOCATION_CONTEXT_PRE_DESTROY, tryCatch.getThis(), predestroysHandle, + ResultHandle invocationContext = tryCatch.invokeStaticMethod(MethodDescriptors.INVOCATION_CONTEXT_PRE_DESTROY, + tryCatch.getThis(), predestroysHandle, bindingsHandle); // InvocationContext.proceed() 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 b67a449afa2ce..7a7ebd461c190 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 @@ -16,19 +16,18 @@ 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 org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationValue; import org.jboss.jandex.DotName; import org.jboss.jandex.MethodParameterInfo; -import io.quarkus.arc.processor.AnnotationStore.TransformationContextImpl; -import io.quarkus.arc.processor.AnnotationsTransformer.TransformationContext; /** * Convenient helper class. @@ -75,7 +74,8 @@ public Transformation addAll(AnnotationInstance... annotations) { } /** - * NOTE: The annotation target is derived from the {@link TransformationContext}. If you need to add an annotation instance to a method parameter use + * 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 @@ -88,7 +88,8 @@ public Transformation add(Class annotationType, Annotation } /** - * NOTE: The annotation target is derived from the {@link TransformationContext}. If you need to add an annotation instance to a method parameter use + * 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 @@ -128,4 +129,4 @@ public void done() { transformationContext.setAnnotations(modified); } -} \ No newline at end of file +} diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Types.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Types.java index 266d9ad9a46e4..391d800b86578 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Types.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Types.java @@ -16,6 +16,14 @@ package io.quarkus.arc.processor; +import io.quarkus.arc.GenericArrayTypeImpl; +import io.quarkus.arc.ParameterizedTypeImpl; +import io.quarkus.arc.TypeVariableImpl; +import io.quarkus.arc.WildcardTypeImpl; +import io.quarkus.gizmo.BytecodeCreator; +import io.quarkus.gizmo.MethodDescriptor; +import io.quarkus.gizmo.ResultHandle; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -23,24 +31,18 @@ import java.util.List; import java.util.Map; import java.util.Set; - import org.jboss.jandex.AnnotationInstance; +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 org.jboss.jandex.ParameterizedType; +import org.jboss.jandex.PrimitiveType.Primitive; import org.jboss.jandex.Type; import org.jboss.jandex.Type.Kind; import org.jboss.jandex.TypeVariable; import org.jboss.jandex.WildcardType; -import io.quarkus.arc.GenericArrayTypeImpl; -import io.quarkus.arc.ParameterizedTypeImpl; -import io.quarkus.arc.TypeVariableImpl; -import io.quarkus.arc.WildcardTypeImpl; -import io.quarkus.gizmo.BytecodeCreator; -import io.quarkus.gizmo.MethodDescriptor; -import io.quarkus.gizmo.ResultHandle; /** * @@ -69,7 +71,8 @@ static ResultHandle getTypeHandle(BytecodeCreator creator, Type type) { creator.writeArrayValue(boundsHandle, i, getTypeHandle(creator, bounds.get(i))); } } - return creator.newInstance(MethodDescriptor.ofConstructor(TypeVariableImpl.class, String.class, java.lang.reflect.Type[].class), + return creator.newInstance( + MethodDescriptor.ofConstructor(TypeVariableImpl.class, String.class, java.lang.reflect.Type[].class), creator.load(typeVariable.identifier()), boundsHandle); } else if (Kind.PARAMETERIZED_TYPE.equals(type.kind())) { @@ -82,7 +85,8 @@ static ResultHandle getTypeHandle(BytecodeCreator creator, Type type) { creator.writeArrayValue(typeArgsHandle, i, getTypeHandle(creator, arguments.get(i))); } return creator.newInstance( - MethodDescriptor.ofConstructor(ParameterizedTypeImpl.class, java.lang.reflect.Type.class, java.lang.reflect.Type[].class), + MethodDescriptor.ofConstructor(ParameterizedTypeImpl.class, java.lang.reflect.Type.class, + java.lang.reflect.Type[].class), creator.loadClass(parameterizedType.name().toString()), typeArgsHandle); } else if (Kind.ARRAY.equals(type.kind())) { @@ -97,11 +101,13 @@ static ResultHandle getTypeHandle(BytecodeCreator creator, Type type) { if (wildcardType.superBound() == null) { return creator.invokeStaticMethod( - MethodDescriptor.ofMethod(WildcardTypeImpl.class, "withUpperBound", java.lang.reflect.WildcardType.class, java.lang.reflect.Type.class), + MethodDescriptor.ofMethod(WildcardTypeImpl.class, "withUpperBound", + java.lang.reflect.WildcardType.class, java.lang.reflect.Type.class), getTypeHandle(creator, wildcardType.extendsBound())); } else { return creator.invokeStaticMethod( - MethodDescriptor.ofMethod(WildcardTypeImpl.class, "withLowerBound", java.lang.reflect.WildcardType.class, java.lang.reflect.Type.class), + MethodDescriptor.ofMethod(WildcardTypeImpl.class, "withLowerBound", + java.lang.reflect.WildcardType.class, java.lang.reflect.Type.class), getTypeHandle(creator, wildcardType.superBound())); } } else if (Kind.PRIMITIVE.equals(type.kind())) { @@ -131,7 +137,6 @@ static ResultHandle getTypeHandle(BytecodeCreator creator, Type type) { } static Type getProviderType(ClassInfo classInfo) { - // TODO hack List typeParameters = classInfo.typeParameters(); if (!typeParameters.isEmpty()) { return ParameterizedType.create(classInfo.name(), typeParameters.toArray(new Type[] {}), null); @@ -140,52 +145,68 @@ static Type getProviderType(ClassInfo classInfo) { } } - static Set getTypeClosure(MethodInfo producerMethod, BeanDeployment beanDeployment) { + static Set getProducerMethodTypeClosure(MethodInfo producerMethod, BeanDeployment beanDeployment) { + Set types; Type returnType = producerMethod.returnType(); - if (returnType.kind() == Kind.PRIMITIVE) { - Set types = new HashSet<>(); + if (returnType.kind() == Kind.PRIMITIVE || returnType.kind() == Kind.ARRAY) { + types = new HashSet<>(); types.add(returnType); types.add(OBJECT_TYPE); return types; - } - ClassInfo returnTypeClassInfo = beanDeployment.getIndex().getClassByName(returnType.name()); - if (returnTypeClassInfo == null) { - throw new IllegalArgumentException("Producer method return type not found in index: " + producerMethod.returnType().name()); - } - if (Kind.CLASS.equals(returnType.kind())) { - return getTypeClosure(returnTypeClassInfo, Collections.emptyMap(), beanDeployment); - } else if (Kind.PARAMETERIZED_TYPE.equals(returnType.kind())) { - return getTypeClosure(returnTypeClassInfo, - buildResolvedMap(returnType.asParameterizedType().arguments(), returnTypeClassInfo.typeParameters(), Collections.emptyMap()), - beanDeployment); } else { - throw new IllegalArgumentException("Unsupported return type"); + ClassInfo returnTypeClassInfo = beanDeployment.getIndex().getClassByName(returnType.name()); + if (returnTypeClassInfo == null) { + throw new IllegalArgumentException( + "Producer method return type not found in index: " + producerMethod.returnType().name()); + } + if (Kind.CLASS.equals(returnType.kind())) { + types = getTypeClosure(returnTypeClassInfo, Collections.emptyMap(), beanDeployment); + } else if (Kind.PARAMETERIZED_TYPE.equals(returnType.kind())) { + types = getTypeClosure(returnTypeClassInfo, + buildResolvedMap(returnType.asParameterizedType().arguments(), returnTypeClassInfo.typeParameters(), + Collections.emptyMap()), + beanDeployment); + } else { + throw new IllegalArgumentException("Unsupported return type"); + } } + return restrictBeanTypes(types, beanDeployment.getAnnotations(producerMethod)); } - static Set getTypeClosure(FieldInfo producerField, BeanDeployment beanDeployment) { + static Set getProducerFieldTypeClosure(FieldInfo producerField, BeanDeployment beanDeployment) { + Set types; Type fieldType = producerField.type(); - if (fieldType.kind() == Kind.PRIMITIVE) { - Set types = new HashSet<>(); + if (fieldType.kind() == Kind.PRIMITIVE || fieldType.kind() == Kind.ARRAY) { + types = new HashSet<>(); types.add(fieldType); types.add(OBJECT_TYPE); - return types; - } - ClassInfo fieldClassInfo = beanDeployment.getIndex().getClassByName(producerField.type().name()); - if (fieldClassInfo == null) { - throw new IllegalArgumentException("Producer field type not found in index: " + producerField.type().name()); - } - if (Kind.CLASS.equals(fieldType.kind())) { - return getTypeClosure(fieldClassInfo, Collections.emptyMap(), beanDeployment); - } else if (Kind.PARAMETERIZED_TYPE.equals(fieldType.kind())) { - return getTypeClosure(fieldClassInfo, - buildResolvedMap(fieldType.asParameterizedType().arguments(), fieldClassInfo.typeParameters(), Collections.emptyMap()), beanDeployment); } else { - throw new IllegalArgumentException("Unsupported return type"); + ClassInfo fieldClassInfo = beanDeployment.getIndex().getClassByName(producerField.type().name()); + if (fieldClassInfo == null) { + throw new IllegalArgumentException("Producer field type not found in index: " + producerField.type().name()); + } + if (Kind.CLASS.equals(fieldType.kind())) { + types = getTypeClosure(fieldClassInfo, Collections.emptyMap(), beanDeployment); + } else if (Kind.PARAMETERIZED_TYPE.equals(fieldType.kind())) { + types = getTypeClosure(fieldClassInfo, + buildResolvedMap(fieldType.asParameterizedType().arguments(), fieldClassInfo.typeParameters(), + Collections.emptyMap()), + beanDeployment); + } else { + throw new IllegalArgumentException("Unsupported return type"); + } } + return restrictBeanTypes(types, beanDeployment.getAnnotations(producerField)); } - static Set getTypeClosure(ClassInfo classInfo, Map resolvedTypeParameters, BeanDeployment beanDeployment) { + static Set getClassBeanTypeClosure(ClassInfo classInfo, Map resolvedTypeParameters, + BeanDeployment beanDeployment) { + return restrictBeanTypes(getTypeClosure(classInfo, resolvedTypeParameters, beanDeployment), + beanDeployment.getAnnotations(classInfo)); + } + + private static Set getTypeClosure(ClassInfo classInfo, Map resolvedTypeParameters, + BeanDeployment beanDeployment) { Set types = new HashSet<>(); List typeParameters = classInfo.typeParameters(); if (!typeParameters.isEmpty()) { @@ -209,7 +230,8 @@ static Set getTypeClosure(ClassInfo classInfo, Map res if (interfaceClassInfo != null) { Map resolved = Collections.emptyMap(); if (Kind.PARAMETERIZED_TYPE.equals(interfaceType.kind())) { - resolved = buildResolvedMap(interfaceType.asParameterizedType().arguments(), interfaceClassInfo.typeParameters(), resolvedTypeParameters); + resolved = buildResolvedMap(interfaceType.asParameterizedType().arguments(), + interfaceClassInfo.typeParameters(), resolvedTypeParameters); } types.addAll(getTypeClosure(interfaceClassInfo, resolved, beanDeployment)); } @@ -220,23 +242,33 @@ static Set getTypeClosure(ClassInfo classInfo, Map res if (superClassInfo != null) { Map resolved = Collections.emptyMap(); if (Kind.PARAMETERIZED_TYPE.equals(classInfo.superClassType().kind())) { - resolved = buildResolvedMap(classInfo.superClassType().asParameterizedType().arguments(), superClassInfo.typeParameters(), + resolved = buildResolvedMap(classInfo.superClassType().asParameterizedType().arguments(), + superClassInfo.typeParameters(), resolvedTypeParameters); } types.addAll(getTypeClosure(superClassInfo, resolved, beanDeployment)); } } + return types; + } - // Bean types restriction - AnnotationInstance typed = classInfo.classAnnotations().stream().filter(a -> a.name().equals(DotNames.TYPED)).findFirst().orElse(null); + static Set restrictBeanTypes(Set types, Collection annotations) { + AnnotationInstance typed = annotations.stream().filter(a -> a.name().equals(DotNames.TYPED)) + .findFirst().orElse(null); if (typed != null) { - Set typedClasses = new HashSet<>(); - for (Type type : typed.value().asClassArray()) { - typedClasses.add(type.name()); - } - for (Iterator iterator = types.iterator(); iterator.hasNext();) { - if (!typedClasses.contains(iterator.next().name())) { - iterator.remove(); + AnnotationValue typedValue = typed.value(); + if (typedValue == null) { + types.clear(); + types.add(OBJECT_TYPE); + } else { + Set typedClasses = new HashSet<>(); + for (Type type : typedValue.asClassArray()) { + typedClasses.add(type.name()); + } + for (Iterator iterator = types.iterator(); iterator.hasNext();) { + if (!typedClasses.contains(iterator.next().name())) { + iterator.remove(); + } } } } @@ -265,4 +297,34 @@ static String getSimpleName(String className) { return className.contains(".") ? className.substring(className.lastIndexOf(".") + 1, className.length()) : className; } + static Type box(Type type) { + if (type.kind() == Kind.PRIMITIVE) { + return box(type.asPrimitiveType().primitive()); + } + return type; + } + + static Type box(Primitive primitive) { + switch (primitive) { + case BOOLEAN: + return Type.create(DotNames.BOOLEAN, Kind.CLASS); + case DOUBLE: + return Type.create(DotNames.DOUBLE, Kind.CLASS); + case FLOAT: + return Type.create(DotNames.FLOAT, Kind.CLASS); + case LONG: + return Type.create(DotNames.LONG, Kind.CLASS); + case INT: + return Type.create(DotNames.INTEGER, Kind.CLASS); + case BYTE: + return Type.create(DotNames.BYTE, Kind.CLASS); + case CHAR: + return Type.create(DotNames.CHARACTER, Kind.CLASS); + case SHORT: + return Type.create(DotNames.SHORT, Kind.CLASS); + default: + throw new IllegalArgumentException("Unsupported primitive: " + primitive); + } + } + } diff --git a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/Basics.java b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/Basics.java index 140e463341052..f009ef7820cf5 100644 --- a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/Basics.java +++ b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/Basics.java @@ -18,7 +18,6 @@ import java.io.IOException; import java.io.InputStream; - import org.jboss.jandex.DotName; import org.jboss.jandex.Index; import org.jboss.jandex.Indexer; @@ -32,7 +31,8 @@ public static DotName name(Class clazz) { public static Index index(Class... classes) throws IOException { Indexer indexer = new Indexer(); for (Class clazz : classes) { - try (InputStream stream = Basics.class.getClassLoader().getResourceAsStream(clazz.getName().replace('.', '/') + ".class")) { + try (InputStream stream = Basics.class.getClassLoader() + .getResourceAsStream(clazz.getName().replace('.', '/') + ".class")) { indexer.index(stream); } } diff --git a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/BeanGeneratorTest.java b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/BeanGeneratorTest.java index cd0b8432c96be..bfcb745ce91d3 100644 --- a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/BeanGeneratorTest.java +++ b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/BeanGeneratorTest.java @@ -18,20 +18,18 @@ import static io.quarkus.arc.processor.Basics.index; +import io.quarkus.arc.processor.BeanProcessor.PrivateMembersCollector; +import io.quarkus.arc.processor.types.Foo; +import io.quarkus.arc.processor.types.FooQualifier; import java.io.IOException; import java.util.AbstractCollection; import java.util.AbstractList; import java.util.Collection; import java.util.List; - import javax.enterprise.context.ApplicationScoped; import javax.enterprise.context.Dependent; import javax.enterprise.inject.Produces; - import org.jboss.jandex.Index; -import io.quarkus.arc.processor.BeanProcessor.PrivateMembersCollector; -import io.quarkus.arc.processor.types.Foo; -import io.quarkus.arc.processor.types.FooQualifier; import org.junit.Test; public class BeanGeneratorTest { @@ -39,11 +37,13 @@ public class BeanGeneratorTest { @Test public void testGenerator() throws IOException { - Index index = index(Foo.class, FooQualifier.class, AbstractList.class, AbstractCollection.class, Collection.class, List.class, Iterable.class); + Index index = index(Foo.class, FooQualifier.class, AbstractList.class, AbstractCollection.class, Collection.class, + List.class, Iterable.class); BeanDeployment deployment = new BeanDeployment(index, null, null); deployment.init(); - BeanGenerator generator = new BeanGenerator(new AnnotationLiteralProcessor(true, TruePredicate.INSTANCE), TruePredicate.INSTANCE, new PrivateMembersCollector()); + BeanGenerator generator = new BeanGenerator(new AnnotationLiteralProcessor(true, TruePredicate.INSTANCE), + TruePredicate.INSTANCE, new PrivateMembersCollector()); deployment.getBeans().forEach(bean -> generator.generate(bean, ReflectionRegistration.NOOP)); // TODO test generated bytecode @@ -56,7 +56,8 @@ public void testGeneratorForNormalScopedProducer() throws IOException { BeanDeployment deployment = new BeanDeployment(index, null, null); deployment.init(); - BeanGenerator generator = new BeanGenerator(new AnnotationLiteralProcessor(true, TruePredicate.INSTANCE), TruePredicate.INSTANCE, new PrivateMembersCollector()); + BeanGenerator generator = new BeanGenerator(new AnnotationLiteralProcessor(true, TruePredicate.INSTANCE), + TruePredicate.INSTANCE, new PrivateMembersCollector()); deployment.getBeans().forEach(bean -> generator.generate(bean, ReflectionRegistration.NOOP)); // TODO test generated bytecode diff --git a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/BeanInfoInjectionsTest.java b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/BeanInfoInjectionsTest.java index 78930d6d07094..ededb348c853a 100644 --- a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/BeanInfoInjectionsTest.java +++ b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/BeanInfoInjectionsTest.java @@ -20,21 +20,20 @@ import static io.quarkus.arc.processor.Basics.name; import static org.junit.Assert.assertEquals; +import io.quarkus.arc.processor.types.Bar; +import io.quarkus.arc.processor.types.Foo; +import io.quarkus.arc.processor.types.FooQualifier; import java.io.IOException; import java.util.AbstractCollection; import java.util.AbstractList; import java.util.Collection; import java.util.List; - import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; import org.jboss.jandex.Index; import org.jboss.jandex.ParameterizedType; import org.jboss.jandex.Type; import org.jboss.jandex.Type.Kind; -import io.quarkus.arc.processor.types.Bar; -import io.quarkus.arc.processor.types.Foo; -import io.quarkus.arc.processor.types.FooQualifier; import org.junit.Assert; import org.junit.Test; @@ -47,29 +46,34 @@ public class BeanInfoInjectionsTest { @Test public void testInjections() throws IOException { - Index index = index(Foo.class, Bar.class, FooQualifier.class, AbstractList.class, AbstractCollection.class, Collection.class, List.class, + Index index = index(Foo.class, Bar.class, FooQualifier.class, AbstractList.class, AbstractCollection.class, + Collection.class, List.class, Iterable.class); DotName barName = name(Bar.class); ClassInfo barClass = index.getClassByName(barName); Type fooType = Type.create(name(Foo.class), Kind.CLASS); - Type listStringType = ParameterizedType.create(name(List.class), new Type[] { Type.create(name(String.class), Kind.CLASS) }, null); + Type listStringType = ParameterizedType.create(name(List.class), + new Type[] { Type.create(name(String.class), Kind.CLASS) }, null); BeanDeployment deployment = new BeanDeployment(index, null, null); BeanInfo barBean = deployment.getBeans().stream().filter(b -> b.getTarget().get().equals(barClass)).findFirst().get(); List injections = barBean.getInjections(); assertEquals(3, injections.size()); for (Injection injection : injections) { - if (injection.target.kind().equals(org.jboss.jandex.AnnotationTarget.Kind.FIELD) && injection.target.asField().name().equals("foo")) { + if (injection.target.kind().equals(org.jboss.jandex.AnnotationTarget.Kind.FIELD) + && injection.target.asField().name().equals("foo")) { assertEquals(1, injection.injectionPoints.size()); assertEquals(fooType, injection.injectionPoints.get(0).getRequiredType()); assertEquals(1, injection.injectionPoints.get(0).getRequiredQualifiers().size()); - } else if (injection.target.kind().equals(org.jboss.jandex.AnnotationTarget.Kind.METHOD) && injection.target.asMethod().name().equals("")) { + } else if (injection.target.kind().equals(org.jboss.jandex.AnnotationTarget.Kind.METHOD) + && injection.target.asMethod().name().equals("")) { // Constructor assertEquals(2, injection.injectionPoints.size()); assertEquals(listStringType, injection.injectionPoints.get(0).getRequiredType()); assertEquals(fooType, injection.injectionPoints.get(1).getRequiredType()); assertEquals(1, injection.injectionPoints.get(1).getRequiredQualifiers().size()); - } else if (injection.target.kind().equals(org.jboss.jandex.AnnotationTarget.Kind.METHOD) && injection.target.asMethod().name().equals("init")) { + } else if (injection.target.kind().equals(org.jboss.jandex.AnnotationTarget.Kind.METHOD) + && injection.target.asMethod().name().equals("init")) { // Initializer assertEquals(2, injection.injectionPoints.size()); assertEquals(listStringType, injection.injectionPoints.get(1).getRequiredType()); diff --git a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/BeanInfoQualifiersTest.java b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/BeanInfoQualifiersTest.java index 46f2f92997dcf..71b214faa86cc 100644 --- a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/BeanInfoQualifiersTest.java +++ b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/BeanInfoQualifiersTest.java @@ -21,16 +21,15 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import io.quarkus.arc.processor.types.Bar; +import io.quarkus.arc.processor.types.Foo; +import io.quarkus.arc.processor.types.FooQualifier; import java.io.IOException; - import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationTarget.Kind; import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; import org.jboss.jandex.Index; -import io.quarkus.arc.processor.types.Bar; -import io.quarkus.arc.processor.types.Foo; -import io.quarkus.arc.processor.types.FooQualifier; import org.junit.Test; /** @@ -49,7 +48,8 @@ public void testQualifiers() throws IOException { BeanInfo bean = Beans.createClassBean(fooClass, new BeanDeployment(index, null, null)); AnnotationInstance requiredFooQualifier = index.getAnnotations(fooQualifierName).stream() - .filter(a -> Kind.FIELD.equals(a.target().kind()) && a.target().asField().name().equals("foo")).findFirst().orElse(null); + .filter(a -> Kind.FIELD.equals(a.target().kind()) && a.target().asField().name().equals("foo")).findFirst() + .orElse(null); assertNotNull(requiredFooQualifier); // FooQualifier#alpha() is @Nonbinding diff --git a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/BeanInfoTypesTest.java b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/BeanInfoTypesTest.java index f8d3f85afc120..11364e44a7e8a 100644 --- a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/BeanInfoTypesTest.java +++ b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/BeanInfoTypesTest.java @@ -21,22 +21,21 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import io.quarkus.arc.processor.types.Bar; +import io.quarkus.arc.processor.types.Foo; +import io.quarkus.arc.processor.types.FooQualifier; import java.io.IOException; import java.util.AbstractCollection; import java.util.AbstractList; import java.util.Collection; import java.util.List; import java.util.Set; - import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; import org.jboss.jandex.Index; import org.jboss.jandex.ParameterizedType; import org.jboss.jandex.Type; import org.jboss.jandex.Type.Kind; -import io.quarkus.arc.processor.types.Bar; -import io.quarkus.arc.processor.types.Foo; -import io.quarkus.arc.processor.types.FooQualifier; import org.junit.Test; /** @@ -48,7 +47,8 @@ public class BeanInfoTypesTest { @Test public void testResolver() throws IOException { - Index index = index(Foo.class, Bar.class, FooQualifier.class, AbstractList.class, AbstractCollection.class, Collection.class, List.class, + Index index = index(Foo.class, Bar.class, FooQualifier.class, AbstractList.class, AbstractCollection.class, + Collection.class, List.class, Iterable.class); BeanDeployment deployment = new BeanDeployment(index, null, null); @@ -60,11 +60,16 @@ public void testResolver() throws IOException { // Foo, AbstractList, AbstractCollection, List, Collection, Iterable assertEquals(6, types.size()); assertTrue(types.contains(Type.create(fooName, Kind.CLASS))); - assertTrue(types.contains(ParameterizedType.create(name(AbstractList.class), new Type[] { Type.create(name(String.class), Kind.CLASS) }, null))); - assertTrue(types.contains(ParameterizedType.create(name(List.class), new Type[] { Type.create(name(String.class), Kind.CLASS) }, null))); - assertTrue(types.contains(ParameterizedType.create(name(Collection.class), new Type[] { Type.create(name(String.class), Kind.CLASS) }, null))); - assertTrue(types.contains(ParameterizedType.create(name(AbstractCollection.class), new Type[] { Type.create(name(String.class), Kind.CLASS) }, null))); - assertTrue(types.contains(ParameterizedType.create(name(Iterable.class), new Type[] { Type.create(name(String.class), Kind.CLASS) }, null))); + assertTrue(types.contains(ParameterizedType.create(name(AbstractList.class), + new Type[] { Type.create(name(String.class), Kind.CLASS) }, null))); + assertTrue(types.contains( + ParameterizedType.create(name(List.class), new Type[] { Type.create(name(String.class), Kind.CLASS) }, null))); + assertTrue(types.contains(ParameterizedType.create(name(Collection.class), + new Type[] { Type.create(name(String.class), Kind.CLASS) }, null))); + assertTrue(types.contains(ParameterizedType.create(name(AbstractCollection.class), + new Type[] { Type.create(name(String.class), Kind.CLASS) }, null))); + assertTrue(types.contains(ParameterizedType.create(name(Iterable.class), + new Type[] { Type.create(name(String.class), Kind.CLASS) }, null))); } diff --git a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/BeanResolverTest.java b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/BeanResolverTest.java deleted file mode 100644 index c629211fcd0e2..0000000000000 --- a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/BeanResolverTest.java +++ /dev/null @@ -1,48 +0,0 @@ -package io.quarkus.arc.processor; - -import org.jboss.jandex.ClassType; -import org.jboss.jandex.DotName; -import org.jboss.jandex.PrimitiveType; -import org.jboss.jandex.Type; -import org.junit.Test; - -import static org.junit.Assert.assertTrue; - -public class BeanResolverTest { - - @Test - public void testPrimitiveMatches() { - assertTrue(BeanResolver.primitiveMatch(PrimitiveType.Primitive.BOOLEAN, PrimitiveType.BOOLEAN)); - assertTrue(BeanResolver.primitiveMatch(PrimitiveType.Primitive.BOOLEAN, - ClassType.create(DotName.createSimple(Boolean.class.getName()), Type.Kind.CLASS))); - - assertTrue(BeanResolver.primitiveMatch(PrimitiveType.Primitive.BYTE, PrimitiveType.BYTE)); - assertTrue(BeanResolver.primitiveMatch(PrimitiveType.Primitive.BYTE, - ClassType.create(DotName.createSimple(Byte.class.getName()), Type.Kind.CLASS))); - - assertTrue(BeanResolver.primitiveMatch(PrimitiveType.Primitive.CHAR, - ClassType.create(DotName.createSimple(Character.class.getName()), Type.Kind.CLASS))); - assertTrue(BeanResolver.primitiveMatch(PrimitiveType.Primitive.CHAR, PrimitiveType.CHAR)); - - assertTrue(BeanResolver.primitiveMatch(PrimitiveType.Primitive.DOUBLE, PrimitiveType.DOUBLE)); - assertTrue(BeanResolver.primitiveMatch(PrimitiveType.Primitive.DOUBLE, - ClassType.create(DotName.createSimple(Double.class.getName()), Type.Kind.CLASS))); - - assertTrue(BeanResolver.primitiveMatch(PrimitiveType.Primitive.FLOAT, PrimitiveType.FLOAT)); - assertTrue(BeanResolver.primitiveMatch(PrimitiveType.Primitive.FLOAT, - ClassType.create(DotName.createSimple(Float.class.getName()), Type.Kind.CLASS))); - - assertTrue(BeanResolver.primitiveMatch(PrimitiveType.Primitive.INT, PrimitiveType.INT)); - assertTrue(BeanResolver.primitiveMatch(PrimitiveType.Primitive.INT, - ClassType.create(DotName.createSimple(Integer.class.getName()), Type.Kind.CLASS))); - - assertTrue(BeanResolver.primitiveMatch(PrimitiveType.Primitive.LONG, PrimitiveType.LONG)); - assertTrue(BeanResolver.primitiveMatch(PrimitiveType.Primitive.LONG, - ClassType.create(DotName.createSimple(Long.class.getName()), Type.Kind.CLASS))); - - assertTrue(BeanResolver.primitiveMatch(PrimitiveType.Primitive.SHORT, PrimitiveType.SHORT)); - assertTrue(BeanResolver.primitiveMatch(PrimitiveType.Primitive.SHORT, - ClassType.create(DotName.createSimple(Short.class.getName()), Type.Kind.CLASS))); - } - -} \ No newline at end of file diff --git a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/ClientProxyGeneratorTest.java b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/ClientProxyGeneratorTest.java index de6ea829dda0d..688fa02e85a06 100644 --- a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/ClientProxyGeneratorTest.java +++ b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/ClientProxyGeneratorTest.java @@ -18,18 +18,16 @@ import static io.quarkus.arc.processor.Basics.index; +import io.quarkus.arc.processor.BeanProcessor.PrivateMembersCollector; +import io.quarkus.arc.processor.ResourceOutput.Resource; import java.io.IOException; import java.util.AbstractList; import java.util.Collection; import java.util.List; - import javax.enterprise.context.ApplicationScoped; import javax.enterprise.context.Dependent; import javax.enterprise.inject.Produces; - import org.jboss.jandex.Index; -import io.quarkus.arc.processor.BeanProcessor.PrivateMembersCollector; -import io.quarkus.arc.processor.ResourceOutput.Resource; import org.junit.Test; public class ClientProxyGeneratorTest { @@ -41,7 +39,8 @@ public void testGenerator() throws IOException { BeanDeployment deployment = new BeanDeployment(index, null, null); deployment.init(); - BeanGenerator beanGenerator = new BeanGenerator( new AnnotationLiteralProcessor(true, TruePredicate.INSTANCE), TruePredicate.INSTANCE, new PrivateMembersCollector()); + BeanGenerator beanGenerator = new BeanGenerator(new AnnotationLiteralProcessor(true, TruePredicate.INSTANCE), + TruePredicate.INSTANCE, new PrivateMembersCollector()); ClientProxyGenerator proxyGenerator = new ClientProxyGenerator(TruePredicate.INSTANCE); deployment.getBeans().stream().filter(bean -> bean.getScope().isNormal()).forEach(bean -> { diff --git a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/DotNamesTest.java b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/DotNamesTest.java index 41a666d620aa7..de6dd38aaeecf 100644 --- a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/DotNamesTest.java +++ b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/DotNamesTest.java @@ -3,11 +3,10 @@ import static io.quarkus.arc.processor.Basics.index; import static org.junit.Assert.assertEquals; +import io.quarkus.arc.processor.DotNamesTest.Nested.NestedNested; import java.io.IOException; - import org.jboss.jandex.DotName; import org.jboss.jandex.Index; -import io.quarkus.arc.processor.DotNamesTest.Nested.NestedNested; import org.junit.Test; public class DotNamesTest { @@ -16,17 +15,21 @@ public class DotNamesTest { public void testSimpleName() throws IOException { Index index = index(Nested.class, NestedNested.class, DotNamesTest.class); assertEquals("Nested", DotNames.simpleName(index.getClassByName(DotName.createSimple(Nested.class.getName())))); - assertEquals("DotNamesTest$Nested", DotNames.simpleName(index.getClassByName(DotName.createSimple(Nested.class.getName())).name())); - assertEquals("NestedNested", DotNames.simpleName(index.getClassByName(DotName.createSimple(NestedNested.class.getName())))); - assertEquals("DotNamesTest$Nested$NestedNested", DotNames.simpleName(index.getClassByName(DotName.createSimple(NestedNested.class.getName())).name())); - assertEquals("DotNamesTest", DotNames.simpleName(index.getClassByName(DotName.createSimple(DotNamesTest.class.getName())))); + assertEquals("DotNamesTest$Nested", + DotNames.simpleName(index.getClassByName(DotName.createSimple(Nested.class.getName())).name())); + assertEquals("NestedNested", + DotNames.simpleName(index.getClassByName(DotName.createSimple(NestedNested.class.getName())))); + assertEquals("DotNamesTest$Nested$NestedNested", + DotNames.simpleName(index.getClassByName(DotName.createSimple(NestedNested.class.getName())).name())); + assertEquals("DotNamesTest", + DotNames.simpleName(index.getClassByName(DotName.createSimple(DotNamesTest.class.getName())))); assertEquals("DotNamesTest$Nested", DotNames.simpleName("io.quarkus.arc.processor.DotNamesTest$Nested")); } static final class Nested { - + static final class NestedNested { - + } } diff --git a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/InterceptorGeneratorTest.java b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/InterceptorGeneratorTest.java index 10d522d2c6e1d..c5ffcb4208eba 100644 --- a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/InterceptorGeneratorTest.java +++ b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/InterceptorGeneratorTest.java @@ -20,21 +20,19 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import io.quarkus.arc.processor.BeanProcessor.PrivateMembersCollector; +import io.quarkus.arc.processor.types.Baz; import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; - import javax.annotation.Priority; import javax.inject.Inject; import javax.interceptor.AroundInvoke; import javax.interceptor.Interceptor; import javax.interceptor.InterceptorBinding; import javax.interceptor.InvocationContext; - import org.jboss.jandex.DotName; import org.jboss.jandex.Index; -import io.quarkus.arc.processor.BeanProcessor.PrivateMembersCollector; -import io.quarkus.arc.processor.types.Baz; import org.junit.Test; public class InterceptorGeneratorTest { @@ -47,13 +45,15 @@ public void testGenerator() throws IOException { deployment.init(); InterceptorInfo myInterceptor = deployment.getInterceptors().stream() - .filter(i -> i.getTarget().get().asClass().name().equals(DotName.createSimple(MyInterceptor.class.getName()))).findAny().orElse(null); + .filter(i -> i.getTarget().get().asClass().name().equals(DotName.createSimple(MyInterceptor.class.getName()))) + .findAny().orElse(null); assertNotNull(myInterceptor); assertEquals(10, myInterceptor.getPriority()); assertEquals(1, myInterceptor.getBindings().size()); assertNotNull(myInterceptor.getAroundInvoke()); - InterceptorGenerator generator = new InterceptorGenerator(new AnnotationLiteralProcessor(true, TruePredicate.INSTANCE), TruePredicate.INSTANCE, new PrivateMembersCollector()); + InterceptorGenerator generator = new InterceptorGenerator(new AnnotationLiteralProcessor(true, TruePredicate.INSTANCE), + TruePredicate.INSTANCE, new PrivateMembersCollector()); deployment.getInterceptors().forEach(interceptor -> generator.generate(interceptor, ReflectionRegistration.NOOP)); // TODO test generated bytecode diff --git a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/SubclassGeneratorTest.java b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/SubclassGeneratorTest.java index 37f8beb0b1c8e..eb9e18909c98d 100644 --- a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/SubclassGeneratorTest.java +++ b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/SubclassGeneratorTest.java @@ -16,16 +16,18 @@ package io.quarkus.arc.processor; +import static io.quarkus.arc.processor.Basics.index; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; -import static io.quarkus.arc.processor.Basics.index; +import io.quarkus.arc.processor.BeanProcessor.PrivateMembersCollector; +import io.quarkus.arc.processor.ResourceOutput.Resource; +import io.quarkus.arc.processor.types.Baz; import java.io.IOException; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.Target; - import javax.annotation.Priority; import javax.enterprise.context.Dependent; import javax.inject.Inject; @@ -33,12 +35,8 @@ import javax.interceptor.Interceptor; import javax.interceptor.InterceptorBinding; import javax.interceptor.InvocationContext; - import org.jboss.jandex.DotName; import org.jboss.jandex.Index; -import io.quarkus.arc.processor.BeanProcessor.PrivateMembersCollector; -import io.quarkus.arc.processor.ResourceOutput.Resource; -import io.quarkus.arc.processor.types.Baz; import org.junit.Test; public class SubclassGeneratorTest { @@ -51,10 +49,12 @@ public void testGenerator() throws IOException { deployment.init(); AnnotationLiteralProcessor annotationLiteralProcessor = new AnnotationLiteralProcessor(true, TruePredicate.INSTANCE); - BeanGenerator beanGenerator = new BeanGenerator(annotationLiteralProcessor, TruePredicate.INSTANCE, new PrivateMembersCollector()); + BeanGenerator beanGenerator = new BeanGenerator(annotationLiteralProcessor, TruePredicate.INSTANCE, + new PrivateMembersCollector()); SubclassGenerator generator = new SubclassGenerator(annotationLiteralProcessor, TruePredicate.INSTANCE); BeanInfo simpleBean = deployment.getBeans().stream() - .filter(b -> b.getTarget().get().asClass().name().equals(DotName.createSimple(SimpleBean.class.getName()))).findAny().get(); + .filter(b -> b.getTarget().get().asClass().name().equals(DotName.createSimple(SimpleBean.class.getName()))) + .findAny().get(); for (Resource resource : beanGenerator.generate(simpleBean, ReflectionRegistration.NOOP)) { generator.generate(simpleBean, resource.getFullyQualifiedName(), ReflectionRegistration.NOOP); } diff --git a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/TruePredicate.java b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/TruePredicate.java index 734e85c64dec0..c8d079911c5e2 100644 --- a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/TruePredicate.java +++ b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/TruePredicate.java @@ -17,10 +17,9 @@ package io.quarkus.arc.processor; import java.util.function.Predicate; - import org.jboss.jandex.DotName; -public class TruePredicate implements Predicate{ +public class TruePredicate implements Predicate { public static TruePredicate INSTANCE = new TruePredicate(); diff --git a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/types/Bar.java b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/types/Bar.java index ae8a492c3ccae..34cc072e087f1 100644 --- a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/types/Bar.java +++ b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/types/Bar.java @@ -17,7 +17,6 @@ package io.quarkus.arc.processor.types; import java.util.List; - import javax.enterprise.context.ApplicationScoped; import javax.inject.Inject; @@ -36,4 +35,4 @@ public class Bar { public void init(Foo foo, List list) { } -} \ No newline at end of file +} diff --git a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/types/Baz.java b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/types/Baz.java index 065bb155937f5..cec6261921063 100644 --- a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/types/Baz.java +++ b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/types/Baz.java @@ -17,7 +17,6 @@ package io.quarkus.arc.processor.types; import java.util.List; - import javax.enterprise.context.Dependent; import javax.enterprise.inject.Instance; import javax.inject.Inject; @@ -32,4 +31,4 @@ public boolean isListResolvable() { return list.isResolvable(); } -} \ No newline at end of file +} diff --git a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/types/Foo.java b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/types/Foo.java index 0ecf542750dce..70aebdbbe27db 100644 --- a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/types/Foo.java +++ b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/types/Foo.java @@ -17,7 +17,6 @@ package io.quarkus.arc.processor.types; import java.util.AbstractList; - import javax.annotation.PreDestroy; import javax.enterprise.context.Dependent; import javax.enterprise.inject.Default; @@ -41,4 +40,4 @@ public int size() { return 0; } -} \ No newline at end of file +} diff --git a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/types/FooQualifier.java b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/types/FooQualifier.java index 169e2401ce970..bfa6bc7c2873e 100644 --- a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/types/FooQualifier.java +++ b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/types/FooQualifier.java @@ -25,7 +25,6 @@ import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.Target; - import javax.enterprise.util.Nonbinding; import javax.inject.Qualifier; @@ -40,4 +39,4 @@ String bravo() default ""; -} \ No newline at end of file +} diff --git a/independent-projects/arc/runtime/pom.xml b/independent-projects/arc/runtime/pom.xml index 1191cfc6d1a42..3a4aa7e75cf1a 100644 --- a/independent-projects/arc/runtime/pom.xml +++ b/independent-projects/arc/runtime/pom.xml @@ -1,3 +1,4 @@ + - - 4.0.0 - - - io.quarkus.arc - arc-parent - 999-SNAPSHOT - ../ - - - arc-runtime - ArC - Runtime - - - - - javax.enterprise - cdi-api - - - - javax.annotation - javax.annotation-api - - - - junit - junit - - - - org.jboss.logging - jboss-logging - - - + 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"> + 4.0.0 + + + io.quarkus.arc + arc-parent + 999-SNAPSHOT + ../ + + + arc + ArC - Runtime + + + + + javax.enterprise + cdi-api + + + + javax.annotation + javax.annotation-api + + + + junit + junit + + + + org.jboss.logging + jboss-logging + + + diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/AbstractSharedContext.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/AbstractSharedContext.java index faa0404635070..9a3d68915f3ca 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/AbstractSharedContext.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/AbstractSharedContext.java @@ -20,7 +20,6 @@ import java.util.Collection; import java.util.Iterator; import java.util.Set; - import javax.enterprise.context.spi.Contextual; import javax.enterprise.context.spi.CreationalContext; @@ -30,7 +29,8 @@ abstract class AbstractSharedContext implements InjectableContext { @SuppressWarnings("rawtypes") public AbstractSharedContext() { - this.instances = new ComputingCache<>(key -> createInstanceHandle((InjectableBean) key.contextual, key.creationalContext)); + this.instances = new ComputingCache<>( + key -> createInstanceHandle((InjectableBean) key.contextual, key.creationalContext)); } @SuppressWarnings("unchecked") @@ -132,7 +132,7 @@ public boolean equals(Object obj) { public String toString() { return "Key [contextual=" + contextual + "]"; } - + } } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ApplicationContext.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ApplicationContext.java index 30db99afb2e87..5c1c2754f1b86 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ApplicationContext.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ApplicationContext.java @@ -17,7 +17,6 @@ package io.quarkus.arc; import java.lang.annotation.Annotation; - import javax.enterprise.context.ApplicationScoped; class ApplicationContext extends AbstractSharedContext { diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ArcCDIProvider.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ArcCDIProvider.java index 6a3cfbda665b2..2b85487c527bb 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ArcCDIProvider.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ArcCDIProvider.java @@ -17,7 +17,6 @@ import java.lang.annotation.Annotation; import java.util.Iterator; - import javax.enterprise.inject.Instance; import javax.enterprise.inject.spi.BeanManager; import javax.enterprise.inject.spi.CDI; diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ArcContainer.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ArcContainer.java index cad3af4f8cbc9..d3e3c098da958 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ArcContainer.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ArcContainer.java @@ -17,9 +17,9 @@ package io.quarkus.arc; import java.lang.annotation.Annotation; +import java.lang.reflect.Type; import java.util.Set; import java.util.function.Supplier; - import javax.enterprise.context.ContextNotActiveException; import javax.enterprise.inject.spi.BeanManager; import javax.enterprise.util.TypeLiteral; @@ -68,6 +68,16 @@ public interface ArcContainer { */ InstanceHandle instance(TypeLiteral type, Annotation... qualifiers); + /** + * Never returns null. However, the handle is empty if no bean matches/multiple beans match the specified type and + * qualifiers. + * + * @param type + * @param qualifiers + * @return a new instance handle + */ + InstanceHandle instance(Type type, Annotation... qualifiers); + /** * Never returns null. However, the handle is empty if no bean matches/multiple beans match the specified name. * diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ArcContainerImpl.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ArcContainerImpl.java index b0e4717f4033a..7a41180e57bad 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ArcContainerImpl.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ArcContainerImpl.java @@ -16,6 +16,7 @@ package io.quarkus.arc; +import io.quarkus.arc.ArcCDIProvider.ArcCDI; import java.lang.annotation.Annotation; import java.lang.reflect.Type; import java.util.ArrayList; @@ -31,7 +32,6 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Supplier; import java.util.stream.Collectors; - import javax.enterprise.context.ApplicationScoped; import javax.enterprise.context.BeforeDestroyed; import javax.enterprise.context.Dependent; @@ -48,11 +48,8 @@ import javax.enterprise.inject.spi.Interceptor; import javax.enterprise.util.TypeLiteral; import javax.inject.Singleton; - import org.jboss.logging.Logger; -import io.quarkus.arc.ArcCDIProvider.ArcCDI; - /** * * @author Martin Kouba @@ -80,20 +77,20 @@ class ArcContainerImpl implements ArcContainer { private final ComputingCache>> beansByName; private final List resourceProviders; - + public ArcContainerImpl() { id = UUID.randomUUID().toString(); running = new AtomicBoolean(true); beans = new ArrayList<>(); interceptors = new ArrayList<>(); observers = new ArrayList<>(); - + applicationContext = new ApplicationContext(); singletonContext = new SingletonContext(); requestContext = new RequestContext(); contexts = new ArrayList<>(); contexts.add(requestContext); - + for (ComponentsProvider componentsProvider : ServiceLoader.load(ComponentsProvider.class)) { Components components = componentsProvider.getComponents(); for (InjectableBean bean : components.getBeans()) { @@ -107,16 +104,18 @@ public ArcContainerImpl() { // Add custom contexts for (InjectableContext context : components.getContexts()) { if (ApplicationScoped.class.equals(context.getScope())) { - throw new IllegalStateException("Failed to register a context - built-in application context is always active: " + context); + throw new IllegalStateException( + "Failed to register a context - built-in application context is always active: " + context); } if (Singleton.class.equals(context.getScope())) { - throw new IllegalStateException("Failed to register a context - built-in singleton context is always active: " + context); + throw new IllegalStateException( + "Failed to register a context - built-in singleton context is always active: " + context); } contexts.add(context); } } Collections.sort(interceptors, (i1, i2) -> Integer.compare(i2.getPriority(), i1.getPriority())); - + resolved = new ComputingCache<>(this::resolve); beansById = new ComputingCache<>(this::findById); beansByName = new ComputingCache<>(this::resolve); @@ -144,7 +143,7 @@ public InjectableContext getActiveContext(Class scopeType) // Application/Singleton context is always active if (ApplicationScoped.class.equals(scopeType)) { return applicationContext; - } else if(Singleton.class.equals(scopeType)) { + } else if (Singleton.class.equals(scopeType)) { return singletonContext; } List active = new ArrayList<>(); @@ -155,7 +154,7 @@ public InjectableContext getActiveContext(Class scopeType) } if (active.isEmpty()) { return null; - } else if(active.size() == 1) { + } else if (active.size() == 1) { return active.get(0); } throw new IllegalArgumentException("More than one context object for the given scope: " + active); @@ -163,7 +162,8 @@ public InjectableContext getActiveContext(Class scopeType) @Override public Set> getScopes() { - Set> scopes = contexts.stream().map(InjectableContext::getScope).collect(Collectors.toSet()); + Set> scopes = contexts.stream().map(InjectableContext::getScope) + .collect(Collectors.toSet()); scopes.add(ApplicationScoped.class); scopes.add(Singleton.class); return scopes; @@ -181,6 +181,12 @@ public InstanceHandle instance(TypeLiteral type, Annotation... qualifi return instanceHandle(type.getType(), qualifiers); } + @Override + public InstanceHandle instance(Type type, Annotation... qualifiers) { + requireRunning(); + return instanceHandle(type, qualifiers); + } + @Override public Supplier> instanceSupplier(Class type, Annotation... qualifiers) { requireRunning(); @@ -192,7 +198,7 @@ public Supplier> instanceSupplier(Class type, Annotatio return new Supplier>() { @Override public InstanceHandle get() { - return beanInstanceHandle(bean, null); + return beanInstanceHandle(bean, null); } }; } @@ -203,7 +209,6 @@ public InstanceHandle instance(InjectableBean bean) { requireRunning(); return bean != null ? (InstanceHandle) beanInstanceHandle(bean, null) : InstanceHandleImpl.unavailable(); } - @SuppressWarnings("unchecked") @Override @@ -215,7 +220,7 @@ public InjectableBean bean(String beanIdentifier) { @SuppressWarnings("unchecked") @Override - public InstanceHandle instance(String name) { + public InstanceHandle instance(String name) { Objects.requireNonNull(name); requireRunning(); Set> resolvedBeans = beansByName.getValue(name); @@ -237,7 +242,8 @@ public BeanManager beanManager() { @Override public String toString() { - return "ArcContainerImpl [id=" + id + ", running=" + running + ", beans=" + beans.size() + ", observers=" + observers.size() + ", scopes=" + return "ArcContainerImpl [id=" + id + ", running=" + running + ", beans=" + beans.size() + ", observers=" + + observers.size() + ", scopes=" + getScopes() + "]"; } @@ -287,21 +293,24 @@ private InstanceHandle instanceHandle(Type type, Annotation... qualifiers return beanInstanceHandle(getBean(type, qualifiers), null); } - InstanceHandle beanInstanceHandle(InjectableBean bean, CreationalContextImpl parentContext) { + InstanceHandle beanInstanceHandle(InjectableBean bean, CreationalContextImpl parentContext, + boolean resetCurrentInjectionPoint) { if (bean != null) { if (parentContext == null && Dependent.class.equals(bean.getScope())) { parentContext = new CreationalContextImpl<>(); } - CreationalContextImpl creationalContext = parentContext != null ? parentContext.child() : new CreationalContextImpl<>(); - InjectionPoint prev = InjectionPointProvider.CURRENT.get(); - InjectionPointProvider.CURRENT.set(CurrentInjectionPointProvider.EMPTY); + CreationalContextImpl creationalContext = parentContext != null ? parentContext.child() + : new CreationalContextImpl<>(); + InjectionPoint prev = null; + if (resetCurrentInjectionPoint) { + prev = InjectionPointProvider.set(CurrentInjectionPointProvider.EMPTY); + } + try { return new InstanceHandleImpl(bean, bean.get(creationalContext), creationalContext, parentContext); } finally { - if (prev != null) { - InjectionPointProvider.CURRENT.set(prev); - } else { - InjectionPointProvider.CURRENT.remove(); + if (resetCurrentInjectionPoint) { + InjectionPointProvider.set(prev); } } } else { @@ -309,6 +318,10 @@ InstanceHandle beanInstanceHandle(InjectableBean bean, CreationalConte } } + InstanceHandle beanInstanceHandle(InjectableBean bean, CreationalContextImpl parentContext) { + return beanInstanceHandle(bean, parentContext, true); + } + @SuppressWarnings("unchecked") private InjectableBean getBean(Type requiredType, Annotation... qualifiers) { if (qualifiers == null || qualifiers.length == 0) { @@ -322,7 +335,7 @@ Set> getBeans(Type requiredType, Annotation... qualifiers) { // This method does not cache the results return new HashSet<>(getMatchingBeans(new Resolvable(requiredType, qualifiers))); } - + Set> getBeans(String name) { // This method does not cache the results return new HashSet<>(getMatchingBeans(name)); @@ -331,11 +344,11 @@ Set> getBeans(String name) { private Set> resolve(Resolvable resolvable) { return resolve(getMatchingBeans(resolvable)); } - + private Set> resolve(String name) { return resolve(getMatchingBeans(name)); } - + private InjectableBean findById(String identifier) { for (InjectableBean bean : beans) { if (bean.getIdentifier().equals(identifier)) { @@ -344,7 +357,7 @@ private InjectableBean findById(String identifier) { } return null; } - + @SuppressWarnings("unchecked") static Bean resolve(Set> beans) { if (beans == null || beans.isEmpty()) { @@ -389,7 +402,8 @@ private static Set> resolve(List> matching) List> resolved = new ArrayList<>(matching); for (Iterator> iterator = resolved.iterator(); iterator.hasNext();) { InjectableBean bean = iterator.next(); - if (bean.getAlternativePriority() == null && (bean.getDeclaringBean() == null || bean.getDeclaringBean().getAlternativePriority() == null)) { + if (bean.getAlternativePriority() == null + && (bean.getDeclaringBean() == null || bean.getDeclaringBean().getAlternativePriority() == null)) { // Remove non-alternatives iterator.remove(); } @@ -413,7 +427,8 @@ private static Set> resolve(List> matching) } private static Integer getAlternativePriority(InjectableBean bean) { - return bean.getDeclaringBean() != null ? bean.getDeclaringBean().getAlternativePriority() : bean.getAlternativePriority(); + return bean.getDeclaringBean() != null ? bean.getDeclaringBean().getAlternativePriority() + : bean.getAlternativePriority(); } List> getMatchingBeans(Resolvable resolvable) { @@ -425,7 +440,7 @@ List> getMatchingBeans(Resolvable resolvable) { } return matching; } - + List> getMatchingBeans(String name) { List> matching = new ArrayList<>(); for (InjectableBean bean : beans) { @@ -438,8 +453,10 @@ List> getMatchingBeans(String name) { private static int compareAlternativeBeans(InjectableBean bean1, InjectableBean bean2) { // The highest priority wins - Integer priority2 = bean2.getDeclaringBean() != null ? bean2.getDeclaringBean().getAlternativePriority() : bean2.getAlternativePriority(); - Integer priority1 = bean1.getDeclaringBean() != null ? bean1.getDeclaringBean().getAlternativePriority() : bean1.getAlternativePriority(); + Integer priority2 = bean2.getDeclaringBean() != null ? bean2.getDeclaringBean().getAlternativePriority() + : bean2.getAlternativePriority(); + Integer priority1 = bean1.getDeclaringBean() != null ? bean1.getDeclaringBean().getAlternativePriority() + : bean1.getAlternativePriority(); return priority2.compareTo(priority1); } @@ -452,7 +469,8 @@ List> resolveObservers(Type eventType, S List> resolvedObservers = new ArrayList<>(); for (InjectableObserverMethod observer : observers) { if (EventTypeAssignabilityRules.matches(observer.getObservedType(), eventTypes)) { - if (observer.getObservedQualifiers().isEmpty() || Qualifiers.isSubset(observer.getObservedQualifiers(), eventQualifiers)) { + if (observer.getObservedQualifiers().isEmpty() + || Qualifiers.isSubset(observer.getObservedQualifiers(), eventQualifiers)) { resolvedObservers.add((InjectableObserverMethod) observer); } } @@ -461,7 +479,7 @@ List> resolveObservers(Type eventType, S Collections.sort(resolvedObservers, InjectableObserverMethod::compare); return resolvedObservers; } - + List> resolveInterceptors(InterceptionType type, Annotation... interceptorBindings) { if (interceptors.isEmpty()) { return Collections.emptyList(); @@ -474,7 +492,7 @@ List> resolveInterceptors(InterceptionType type, Annotation... in } return interceptors; } - + private boolean hasAllInterceptionBindings(InjectableInterceptor interceptor, Annotation[] interceptorBindings) { for (Annotation binding : interceptorBindings) { // The resolution rules are the same for qualifiers diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/AsyncEventDeliveryStage.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/AsyncEventDeliveryStage.java index 150e09e5f79f6..9b9b7dbe1d8ba 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/AsyncEventDeliveryStage.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/AsyncEventDeliveryStage.java @@ -95,32 +95,38 @@ public CompletionStage thenRunAsync(Runnable action, Executor executor) { } @Override - public CompletionStage thenCombine(CompletionStage other, BiFunction fn) { + public CompletionStage thenCombine(CompletionStage other, + BiFunction fn) { return wrap(delegate.thenCombine(other, fn)); } @Override - public CompletionStage thenCombineAsync(CompletionStage other, BiFunction fn) { + public CompletionStage thenCombineAsync(CompletionStage other, + BiFunction fn) { return wrap(delegate.thenCombineAsync(other, fn, defaultExecutor)); } @Override - public CompletionStage thenCombineAsync(CompletionStage other, BiFunction fn, Executor executor) { + public CompletionStage thenCombineAsync(CompletionStage other, + BiFunction fn, Executor executor) { return wrap(delegate.thenCombineAsync(other, fn, executor)); } @Override - public CompletionStage thenAcceptBoth(CompletionStage other, BiConsumer action) { + public CompletionStage thenAcceptBoth(CompletionStage other, + BiConsumer action) { return wrap(delegate.thenAcceptBoth(other, action)); } @Override - public CompletionStage thenAcceptBothAsync(CompletionStage other, BiConsumer action) { + public CompletionStage thenAcceptBothAsync(CompletionStage other, + BiConsumer action) { return wrap(delegate.thenAcceptBothAsync(other, action, defaultExecutor)); } @Override - public CompletionStage thenAcceptBothAsync(CompletionStage other, BiConsumer action, Executor executor) { + public CompletionStage thenAcceptBothAsync(CompletionStage other, + BiConsumer action, Executor executor) { return wrap(delegate.thenAcceptBothAsync(other, action, executor)); } @@ -150,7 +156,8 @@ public CompletionStage applyToEitherAsync(CompletionStage ot } @Override - public CompletionStage applyToEitherAsync(CompletionStage other, Function fn, Executor executor) { + public CompletionStage applyToEitherAsync(CompletionStage other, Function fn, + Executor executor) { return wrap(delegate.applyToEitherAsync(other, fn, executor)); } @@ -165,7 +172,8 @@ public CompletionStage acceptEitherAsync(CompletionStage othe } @Override - public CompletionStage acceptEitherAsync(CompletionStage other, Consumer action, Executor executor) { + public CompletionStage acceptEitherAsync(CompletionStage other, Consumer action, + Executor executor) { return wrap(delegate.acceptEitherAsync(other, action, executor)); } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/BeanCreator.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/BeanCreator.java index 484fbc88b233e..722633318a1af 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/BeanCreator.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/BeanCreator.java @@ -17,7 +17,6 @@ package io.quarkus.arc; import java.util.Map; - import javax.enterprise.context.spi.Contextual; import javax.enterprise.context.spi.CreationalContext; @@ -37,4 +36,4 @@ public interface BeanCreator { */ T create(CreationalContext creationalContext, Map params); -} \ No newline at end of file +} diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/BeanDestroyer.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/BeanDestroyer.java index 418ff391bdb73..e09b610fedcd3 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/BeanDestroyer.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/BeanDestroyer.java @@ -17,7 +17,6 @@ package io.quarkus.arc; import java.util.Map; - import javax.enterprise.context.spi.Contextual; import javax.enterprise.context.spi.CreationalContext; @@ -37,4 +36,4 @@ public interface BeanDestroyer { */ void destroy(T instance, CreationalContext creationalContext, Map params); -} \ No newline at end of file +} diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/BeanManagerImpl.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/BeanManagerImpl.java index d26213ced8e35..fdd9cc0036865 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/BeanManagerImpl.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/BeanManagerImpl.java @@ -18,11 +18,11 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Type; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; - import javax.el.ELResolver; import javax.el.ExpressionFactory; import javax.enterprise.context.ContextNotActiveException; @@ -31,6 +31,7 @@ import javax.enterprise.context.spi.CreationalContext; import javax.enterprise.event.Event; import javax.enterprise.inject.Instance; +import javax.enterprise.inject.UnsatisfiedResolutionException; import javax.enterprise.inject.spi.AnnotatedField; import javax.enterprise.inject.spi.AnnotatedMember; import javax.enterprise.inject.spi.AnnotatedMethod; @@ -49,6 +50,7 @@ import javax.enterprise.inject.spi.Interceptor; import javax.enterprise.inject.spi.ObserverMethod; import javax.enterprise.inject.spi.ProducerFactory; +import javax.enterprise.util.TypeLiteral; import javax.interceptor.InterceptorBinding; /** @@ -56,25 +58,46 @@ */ public class BeanManagerImpl implements BeanManager { + @SuppressWarnings("serial") + static final TypeLiteral> INSTANCE_LITERAL = new TypeLiteral>() { + }; + static final LazyValue INSTANCE = new LazyValue<>(BeanManagerImpl::new); @SuppressWarnings({ "unchecked", "rawtypes" }) @Override public Object getReference(Bean bean, Type beanType, CreationalContext ctx) { - if (bean == null) { - throw new NullPointerException("Managed Bean [" + beanType + "] is null"); - } + Objects.requireNonNull(bean, "Bean is null"); + Objects.requireNonNull(beanType, "Bean type is null"); Objects.requireNonNull(ctx, "CreationalContext is null"); if (bean instanceof InjectableBean && ctx instanceof CreationalContextImpl) { return ArcContainerImpl.instance().beanInstanceHandle((InjectableBean) bean, (CreationalContextImpl) ctx).get(); } throw new IllegalArgumentException( - "Arguments must be instances of " + InjectableBean.class + " and " + CreationalContextImpl.class + ": \nbean: " + bean + "\nctx: " + ctx); + "Arguments must be instances of " + InjectableBean.class + " and " + CreationalContextImpl.class + ": \nbean: " + + bean + "\nctx: " + ctx); } + @SuppressWarnings({ "unchecked", "rawtypes" }) @Override public Object getInjectableReference(InjectionPoint ij, CreationalContext ctx) { - throw new UnsupportedOperationException(); + Objects.requireNonNull(ij, "InjectionPoint is null"); + Objects.requireNonNull(ctx, "CreationalContext is null"); + if (ctx instanceof CreationalContextImpl) { + Set> beans = getBeans(ij.getType(), ij.getQualifiers().toArray(new Annotation[] {})); + if (beans.isEmpty()) { + throw new UnsatisfiedResolutionException(); + } + InjectableBean bean = (InjectableBean) resolve(beans); + InjectionPoint prev = InjectionPointProvider.set(ij); + try { + return ArcContainerImpl.instance().beanInstanceHandle(bean, (CreationalContextImpl) ctx, false).get(); + } finally { + InjectionPointProvider.set(prev); + } + } + throw new IllegalArgumentException( + "CreationalContext must be an instances of " + CreationalContextImpl.class); } @Override @@ -242,7 +265,8 @@ public BeanAttributes createBeanAttributes(AnnotatedMember type) { } @Override - public Bean createBean(BeanAttributes attributes, Class beanClass, InjectionTargetFactory injectionTargetFactory) { + public Bean createBean(BeanAttributes attributes, Class beanClass, + InjectionTargetFactory injectionTargetFactory) { throw new UnsupportedOperationException(); } @@ -278,7 +302,8 @@ public Event getEvent() { @Override public Instance createInstance() { - return new InstanceImpl<>(Object.class, null, new CreationalContextImpl<>()); + return new InstanceImpl<>(null, INSTANCE_LITERAL.getType(), Collections.emptySet(), new CreationalContextImpl<>(), + Collections.emptySet(), null, -1); } } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/BeanTypeAssignabilityRules.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/BeanTypeAssignabilityRules.java index e7df0719290e9..a3c89f561c3dd 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/BeanTypeAssignabilityRules.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/BeanTypeAssignabilityRules.java @@ -71,10 +71,12 @@ private static boolean matches(Class requiredType, Class beanType) { } /** - * A parameterized bean type is considered assignable to a raw required type if the raw types are identical and all type parameters of the bean type are + * A parameterized bean type is considered assignable to a raw required type if the raw types are identical and all type + * parameters of the bean type are * either unbounded type variables or java.lang.Object. *

- * A raw bean type is considered assignable to a parameterized required type if the raw types are identical and all type parameters of the required type are + * A raw bean type is considered assignable to a parameterized required type if the raw types are identical and all type + * parameters of the required type are * either unbounded type variables or java.lang.Object. * */ @@ -86,7 +88,8 @@ private static boolean matches(Class type1, ParameterizedType type2) { } /** - * A parameterized bean type is considered assignable to a parameterized required type if they have identical raw type and for each parameter: + * A parameterized bean type is considered assignable to a parameterized required type if they have identical raw type and + * for each parameter: */ private static boolean matches(ParameterizedType requiredType, ParameterizedType beanType) { if (!requiredType.getRawType().equals(beanType.getRawType())) { @@ -110,35 +113,41 @@ private static boolean matches(ParameterizedType requiredType, ParameterizedType private static boolean parametersMatch(Type requiredParameter, Type beanParameter) { if (Types.isActualType(requiredParameter) && Types.isActualType(beanParameter)) { /* - * the required type parameter and the bean type parameter are actual types with identical raw type, and, if the type is parameterized, the bean + * the required type parameter and the bean type parameter are actual types with identical raw type, and, if the + * type is parameterized, the bean * type parameter is assignable to the required type parameter according to these rules, or */ return matches(requiredParameter, beanParameter); } if (requiredParameter instanceof WildcardType && Types.isActualType(beanParameter)) { /* - * the required type parameter is a wildcard, the bean type parameter is an actual type and the actual type is assignable to the upper bound, if + * the required type parameter is a wildcard, the bean type parameter is an actual type and the actual type is + * assignable to the upper bound, if * any, of the wildcard and assignable from the lower bound, if any, of the wildcard, or */ return parametersMatch((WildcardType) requiredParameter, beanParameter); } if (requiredParameter instanceof WildcardType && beanParameter instanceof TypeVariable) { /* - * the required type parameter is a wildcard, the bean type parameter is a type variable and the upper bound of the type variable is assignable to - * or assignable from the upper bound, if any, of the wildcard and assignable from the lower bound, if any, of the wildcard, or + * the required type parameter is a wildcard, the bean type parameter is a type variable and the upper bound of the + * type variable is assignable to + * or assignable from the upper bound, if any, of the wildcard and assignable from the lower bound, if any, of the + * wildcard, or */ return parametersMatch((WildcardType) requiredParameter, (TypeVariable) beanParameter); } if (Types.isActualType(requiredParameter) && beanParameter instanceof TypeVariable) { /* - * the required type parameter is an actual type, the bean type parameter is a type variable and the actual type is assignable to the upper bound, + * the required type parameter is an actual type, the bean type parameter is a type variable and the actual type is + * assignable to the upper bound, * if any, of the type variable, or */ return parametersMatch(requiredParameter, (TypeVariable) beanParameter); } if (requiredParameter instanceof TypeVariable && beanParameter instanceof TypeVariable) { /* - * the required type parameter and the bean type parameter are both type variables and the upper bound of the required type parameter is assignable + * the required type parameter and the bean type parameter are both type variables and the upper bound of the + * required type parameter is assignable * to the upper bound, if any, of the bean type parameter */ return parametersMatch((TypeVariable) requiredParameter, (TypeVariable) beanParameter); @@ -147,7 +156,8 @@ private static boolean parametersMatch(Type requiredParameter, Type beanParamete } private static boolean parametersMatch(WildcardType requiredParameter, Type beanParameter) { - return (lowerBoundsOfWildcardMatch(beanParameter, requiredParameter) && upperBoundsOfWildcardMatch(requiredParameter, beanParameter)); + return (lowerBoundsOfWildcardMatch(beanParameter, requiredParameter) + && upperBoundsOfWildcardMatch(requiredParameter, beanParameter)); } private static boolean parametersMatch(WildcardType requiredParameter, TypeVariable beanParameter) { diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/Components.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/Components.java index a9a3a3bb398c7..c2033643cbc01 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/Components.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/Components.java @@ -23,10 +23,11 @@ public final class Components { private final Collection> beans; private final Collection> observers; - + private final Collection contexts; - public Components(Collection> beans, Collection> observers, Collection contexts) { + public Components(Collection> beans, Collection> observers, + Collection contexts) { this.beans = beans; this.observers = observers; this.contexts = contexts; @@ -43,5 +44,5 @@ public Collection> getObservers() { public Collection getContexts() { return contexts; } - + } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ComponentsProvider.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ComponentsProvider.java index f6b8a3f517db3..06374a1726782 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ComponentsProvider.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ComponentsProvider.java @@ -23,5 +23,5 @@ public interface ComponentsProvider { Components getComponents(); - + } \ No newline at end of file diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ComputingCache.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ComputingCache.java index b4f7bad8e8018..b7d259a23646c 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ComputingCache.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ComputingCache.java @@ -27,7 +27,8 @@ import java.util.stream.Collectors; /** - * Computing cache backed by a {@link ConcurrentHashMap} which intentionally does not use {@link Map#computeIfAbsent(Object, Function)} and is reentrant. + * Computing cache backed by a {@link ConcurrentHashMap} which intentionally does not use + * {@link Map#computeIfAbsent(Object, Function)} and is reentrant. * Derived from {@code org.jboss.weld.util.cache.ReentrantMapBackedComputingCache}. * * @param @@ -58,7 +59,7 @@ public V getValue(K key) { public V getValueIfPresent(K key) { LazyValue value = map.get(key); - return value != null ? value.getIfPresent() :null; + return value != null ? value.getIfPresent() : null; } public V remove(K key) { @@ -80,12 +81,12 @@ public void forEachValue(Consumer action) { public void forEachExistingValue(Consumer action) { Objects.requireNonNull(action); for (LazyValue value : map.values()) { - if(value.isSet()) { + if (value.isSet()) { action.accept(value.get()); } } } - + public Set getPresentValues() { return map.values().stream().map(LazyValue::getIfPresent).filter(Objects::nonNull).collect(Collectors.toSet()); } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/CovariantTypes.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/CovariantTypes.java index ef68a9ce35866..cf9cffdcf9ff3 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/CovariantTypes.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/CovariantTypes.java @@ -27,12 +27,14 @@ * * Utility class that captures standard covariant Java assignability rules. * - * This class operates on all the possible Type subtypes: Class, ParameterizedType, TypeVariable, WildcardType, GenericArrayType. + * This class operates on all the possible Type subtypes: Class, ParameterizedType, TypeVariable, WildcardType, + * GenericArrayType. * To make this class easier to understand and maintain, there is a separate isAssignableFrom method for each combination * of possible types. Each of these methods compares two type instances and determines whether the first one is assignable from * the other. * - * TypeVariables are considered a specific unknown type restricted by the upper bound. No inference of type variables is performed. + * TypeVariables are considered a specific unknown type restricted by the upper bound. No inference of type variables is + * performed. * * @author Jozef Hartinger * diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/CreationalContextImpl.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/CreationalContextImpl.java index 9b013c8170a65..c7b4c0bd40c93 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/CreationalContextImpl.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/CreationalContextImpl.java @@ -20,7 +20,6 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; - import javax.enterprise.context.spi.CreationalContext; /** diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/CurrentInjectionPointProvider.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/CurrentInjectionPointProvider.java index 185a1b0f7159b..0167909756a1b 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/CurrentInjectionPointProvider.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/CurrentInjectionPointProvider.java @@ -28,7 +28,6 @@ import java.util.HashSet; import java.util.List; import java.util.Set; - import javax.enterprise.context.spi.CreationalContext; import javax.enterprise.inject.spi.Annotated; import javax.enterprise.inject.spi.AnnotatedCallable; @@ -46,7 +45,8 @@ */ public class CurrentInjectionPointProvider implements InjectableReferenceProvider { - static final InjectionPoint EMPTY = new InjectionPointImpl(Object.class, Collections.emptySet(), null, null, null, -1); + static final InjectionPoint EMPTY = new InjectionPointImpl(Object.class, Object.class, Collections.emptySet(), null, null, + null, -1); private final InjectableReferenceProvider delegate; @@ -55,25 +55,21 @@ public class CurrentInjectionPointProvider implements InjectableReferenceProv public CurrentInjectionPointProvider(InjectableBean bean, InjectableReferenceProvider delegate, Type requiredType, Set qualifiers, Set annotations, Member javaMember, int position) { this.delegate = delegate; - this.injectionPoint = new InjectionPointImpl(requiredType, qualifiers, bean, annotations, javaMember, position); + this.injectionPoint = new InjectionPointImpl(requiredType, requiredType, qualifiers, bean, annotations, javaMember, + position); } @Override public T get(CreationalContext creationalContext) { - InjectionPoint prev = InjectionPointProvider.CURRENT.get(); - InjectionPointProvider.CURRENT.set(injectionPoint); + InjectionPoint prev = InjectionPointProvider.set(injectionPoint); try { return delegate.get(creationalContext); } finally { - if (prev != null) { - InjectionPointProvider.CURRENT.set(prev); - } else { - InjectionPointProvider.CURRENT.remove(); - } + InjectionPointProvider.set(prev); } } - private static class InjectionPointImpl implements InjectionPoint { + public static class InjectionPointImpl implements InjectionPoint { private final Type requiredType; private final Set qualifiers; @@ -81,15 +77,18 @@ private static class InjectionPointImpl implements InjectionPoint { private final Annotated annotated; private final Member member; - InjectionPointImpl(Type requiredType, Set qualifiers, InjectableBean bean, Set annotations, + public InjectionPointImpl(Type injectionPointType, Type requiredType, Set qualifiers, + InjectableBean bean, + Set annotations, Member javaMember, int position) { this.requiredType = requiredType; this.qualifiers = qualifiers; this.bean = bean; if (javaMember instanceof Executable) { - this.annotated = new AnnotatedParameterImpl<>(requiredType, annotations, position, (Executable) javaMember); + this.annotated = new AnnotatedParameterImpl<>(injectionPointType, annotations, position, + (Executable) javaMember); } else { - this.annotated = new AnnotatedFieldImpl<>(requiredType, annotations, (Field) javaMember); + this.annotated = new AnnotatedFieldImpl<>(injectionPointType, annotations, (Field) javaMember); } this.member = javaMember; } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/EventImpl.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/EventImpl.java index df698a87b1639..42fb7353a5b1f 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/EventImpl.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/EventImpl.java @@ -34,7 +34,6 @@ import java.util.concurrent.Executor; import java.util.concurrent.ForkJoinPool; import java.util.function.Supplier; - import javax.enterprise.context.RequestScoped; import javax.enterprise.event.Event; import javax.enterprise.event.NotificationOptions; @@ -166,7 +165,8 @@ private Notifier createNotifier(Class runtimeType) { return createNotifier(runtimeType, eventType, qualifiers, ArcContainerImpl.unwrap(Arc.container())); } - static Notifier createNotifier(Class runtimeType, Type eventType, Set qualifiers, ArcContainerImpl container) { + static Notifier createNotifier(Class runtimeType, Type eventType, Set qualifiers, + ArcContainerImpl container) { EventMetadata metadata = new EventMetadataImpl(qualifiers, eventType); List> notifierObserverMethods = new ArrayList<>(); for (ObserverMethod observerMethod : container.resolveObservers(eventType, qualifiers)) { @@ -179,18 +179,21 @@ private Type getEventType(Class runtimeType) { Type resolvedType = runtimeType; if (Types.containsTypeVariable(resolvedType)) { /* - * If the container is unable to resolve the parameterized type of the event object, it uses the specified type to infer the parameterized type of + * If the container is unable to resolve the parameterized type of the event object, it uses the specified type to + * infer the parameterized type of * the event types. */ resolvedType = injectionPointTypeHierarchy.resolveType(resolvedType); } if (Types.containsTypeVariable(resolvedType)) { /* - * Examining the hierarchy of the specified type did not help. This may still be one of the cases when combining the event type and the specified + * Examining the hierarchy of the specified type did not help. This may still be one of the cases when combining the + * event type and the specified * type reveals the actual values for type variables. Let's try that. */ Type canonicalEventType = Types.getCanonicalType(runtimeType); - TypeResolver objectTypeResolver = new EventObjectTypeResolverBuilder(injectionPointTypeHierarchy.getResolver().getResolvedTypeVariables(), + TypeResolver objectTypeResolver = new EventObjectTypeResolverBuilder( + injectionPointTypeHierarchy.getResolver().getResolvedTypeVariables(), new HierarchyDiscovery(canonicalEventType).getResolver().getResolvedTypeVariables()).build(); resolvedType = objectTypeResolver.resolveType(canonicalEventType); } @@ -294,7 +297,7 @@ public Set getQualifiers() { @Override public InjectionPoint getInjectionPoint() { - // TODO add partial support + // Currently we do not support injection point of the injected Event instance which fired the event return null; } @@ -306,9 +309,12 @@ public Type getType() { } /** - * There are two different strategies of exception handling for observer methods. When an exception is raised by a synchronous or transactional observer for - * a synchronous event, this exception stops the notification chain and the exception is propagated immediately. On the other hand, an exception thrown - * during asynchronous event delivery never is never propagated directly. Instead, all the exceptions for a given asynchronous event are collected and then + * There are two different strategies of exception handling for observer methods. When an exception is raised by a + * synchronous or transactional observer for + * a synchronous event, this exception stops the notification chain and the exception is propagated immediately. On the + * other hand, an exception thrown + * during asynchronous event delivery never is never propagated directly. Instead, all the exceptions for a given + * asynchronous event are collected and then * made available together using CompletionException. * * @author Jozef Hartinger diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/EventObjectTypeResolverBuilder.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/EventObjectTypeResolverBuilder.java index 1469aa8e85a99..a6c4593b01845 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/EventObjectTypeResolverBuilder.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/EventObjectTypeResolverBuilder.java @@ -27,7 +27,8 @@ /** * Builds a special {@link TypeResolver} capable of resolving type variables by using a combination of two type hierarchies. * - * The special resolver is only necessary for situations where the type of the event object contains an unresolved type variable which cannot be resolved using + * The special resolver is only necessary for situations where the type of the event object contains an unresolved type variable + * which cannot be resolved using * the selected event type because the selected event type is a subtype of the event object. * * For example: @@ -38,7 +39,8 @@ * * The event object type is {@link ArrayList} (raw type due to type erasure) The selected type is List * - * We cannot simply infer the correct type (ArrayList) from the runtime type nor from the selected type. What this special resolver does is that it + * We cannot simply infer the correct type (ArrayList) from the runtime type nor from the selected type. What this + * special resolver does is that it * combines the following type variable assignments: * * L -> E @@ -57,7 +59,8 @@ class EventObjectTypeResolverBuilder { private final Map, Type> resolvedTypes; - public EventObjectTypeResolverBuilder(Map, Type> selectedTypeVariables, Map, Type> eventTypeVariables) { + public EventObjectTypeResolverBuilder(Map, Type> selectedTypeVariables, + Map, Type> eventTypeVariables) { this.selectedTypeVariables = selectedTypeVariables; this.eventTypeVariables = eventTypeVariables; this.resolvedTypes = new HashMap, Type>(); diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/EventProvider.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/EventProvider.java index 9b4a57a7e1f4b..e7cabd6747380 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/EventProvider.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/EventProvider.java @@ -19,7 +19,6 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Type; import java.util.Set; - import javax.enterprise.context.spi.CreationalContext; import javax.enterprise.event.Event; diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/EventTypeAssignabilityRules.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/EventTypeAssignabilityRules.java index 4587aea924b5f..ae30af687d465 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/EventTypeAssignabilityRules.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/EventTypeAssignabilityRules.java @@ -51,7 +51,8 @@ static boolean matches(Type observedType, Type eventType) { static boolean matchesNoBoxing(Type observedType, Type eventType) { if (observedType instanceof TypeVariable) { /* - * An event type is considered assignable to a type variable if the event type is assignable to the upper bound, if any. + * An event type is considered assignable to a type variable if the event type is assignable to the upper bound, if + * any. */ return matches((TypeVariable) observedType, eventType); } @@ -63,7 +64,8 @@ static boolean matchesNoBoxing(Type observedType, Type eventType) { } if (observedType instanceof ParameterizedType && eventType instanceof ParameterizedType) { /* - * A parameterized event type is considered assignable to a parameterized observed event type if they have identical raw type and for each + * A parameterized event type is considered assignable to a parameterized observed event type if they have identical + * raw type and for each * parameter: */ return matches((ParameterizedType) observedType, (ParameterizedType) eventType); @@ -102,33 +104,38 @@ private static boolean matches(ParameterizedType observedType, ParameterizedType } /** - * A parameterized event type is considered assignable to a parameterized observed event type if they have identical raw type and for each parameter: + * A parameterized event type is considered assignable to a parameterized observed event type if they have identical raw + * type and for each parameter: */ private static boolean parametersMatch(Type observedParameter, Type eventParameter) { if (Types.isActualType(observedParameter) && Types.isActualType(eventParameter)) { /* - * the observed event type parameter is an actual type with identical raw type to the event type parameter, and, if the type is parameterized, the + * the observed event type parameter is an actual type with identical raw type to the event type parameter, and, if + * the type is parameterized, the * event type parameter is assignable to the observed event type parameter according to these rules, or */ return matches(observedParameter, eventParameter); } if (observedParameter instanceof WildcardType && eventParameter instanceof WildcardType) { /* - * both the observed event type parameter and the event type parameter are wildcards, and the event type parameter is assignable to the observed + * both the observed event type parameter and the event type parameter are wildcards, and the event type parameter + * is assignable to the observed * event type */ return CovariantTypes.isAssignableFrom(observedParameter, eventParameter); } if (observedParameter instanceof WildcardType) { /* - * the observed event type parameter is a wildcard and the event type parameter is assignable to the upper bound, if any, of the wildcard and + * the observed event type parameter is a wildcard and the event type parameter is assignable to the upper bound, if + * any, of the wildcard and * assignable from the lower bound, if any, of the wildcard, or */ return parametersMatch((WildcardType) observedParameter, eventParameter); } if (observedParameter instanceof TypeVariable) { /* - * the observed event type parameter is a type variable and the event type parameter is assignable to the upper bound, if any, of the type variable. + * the observed event type parameter is a type variable and the event type parameter is assignable to the upper + * bound, if any, of the type variable. */ return parametersMatch((TypeVariable) observedParameter, eventParameter); } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/HierarchyDiscovery.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/HierarchyDiscovery.java index 83aa523bf2ff1..5ac429b3dcdc1 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/HierarchyDiscovery.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/HierarchyDiscovery.java @@ -45,6 +45,7 @@ class HierarchyDiscovery { /** * Constructs a new {@link HierarchyDiscovery} instance. + * * @param type the type whose hierarchy will be discovered */ HierarchyDiscovery(Type type) { diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InitializedInterceptor.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InitializedInterceptor.java index a221ce3577e4c..860cbcea328f8 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InitializedInterceptor.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InitializedInterceptor.java @@ -19,7 +19,6 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Type; import java.util.Set; - import javax.enterprise.context.spi.CreationalContext; import javax.enterprise.inject.spi.InterceptionType; import javax.interceptor.InvocationContext; diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableBean.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableBean.java index 939979173a3a7..0c2297265068f 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableBean.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableBean.java @@ -20,7 +20,6 @@ import java.lang.reflect.Type; import java.util.Collections; import java.util.Set; - import javax.enterprise.context.Dependent; import javax.enterprise.context.spi.CreationalContext; import javax.enterprise.inject.spi.Bean; diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableContext.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableContext.java index c00ac8c80d3c5..3ed5a17cf3bda 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableContext.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableContext.java @@ -17,7 +17,6 @@ package io.quarkus.arc; import java.util.Collection; - import javax.enterprise.context.spi.AlterableContext; /** diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableObserverMethod.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableObserverMethod.java index cb8c76dc2a112..5b7fcb1bd5493 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableObserverMethod.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableObserverMethod.java @@ -19,7 +19,6 @@ import java.lang.annotation.Annotation; import java.util.Collections; import java.util.Set; - import javax.enterprise.event.Reception; import javax.enterprise.event.TransactionPhase; import javax.enterprise.inject.spi.ObserverMethod; diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableRequestContextController.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableRequestContextController.java new file mode 100644 index 0000000000000..c61c21bdaff8e --- /dev/null +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectableRequestContextController.java @@ -0,0 +1,40 @@ +package io.quarkus.arc; + +import java.util.concurrent.atomic.AtomicBoolean; +import javax.enterprise.context.ContextNotActiveException; +import javax.enterprise.context.Dependent; +import javax.enterprise.context.RequestScoped; +import javax.enterprise.context.control.RequestContextController; + +@Dependent +public class InjectableRequestContextController implements RequestContextController { + + private final ManagedContext requestContext; + private final AtomicBoolean isActivator; + + public InjectableRequestContextController() { + this.requestContext = Arc.container().requestContext(); + this.isActivator = new AtomicBoolean(false); + } + + @Override + public boolean activate() { + if (Arc.container().getActiveContext(RequestScoped.class) != null) { + return false; + } + requestContext.activate(); + isActivator.set(true); + return true; + } + + @Override + public void deactivate() throws ContextNotActiveException { + if (Arc.container().getActiveContext(RequestScoped.class) == null) { + throw new ContextNotActiveException(RequestScoped.class.getName()); + } + if (isActivator.compareAndSet(true, false)) { + requestContext.terminate(); + } + } + +} diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectionPointProvider.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectionPointProvider.java index 7965332ebcfab..1fda9831d0f8d 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectionPointProvider.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InjectionPointProvider.java @@ -25,11 +25,32 @@ */ public class InjectionPointProvider implements InjectableReferenceProvider { - static final ThreadLocal CURRENT = new ThreadLocal<>(); + private static final ThreadLocal CURRENT = new ThreadLocal<>(); @Override public InjectionPoint get(CreationalContext creationalContext) { return CURRENT.get(); } + /** + * Set the current injection point for a non-null parameter, remove the threadlocal for null parameter. + * + * @param injectionPoint + * @return the previous injection point or {@code null} + */ + static InjectionPoint set(InjectionPoint injectionPoint) { + if (injectionPoint != null) { + InjectionPoint prev = InjectionPointProvider.CURRENT.get(); + InjectionPointProvider.CURRENT.set(injectionPoint); + return prev; + } else { + CURRENT.remove(); + return null; + } + } + + public static InjectionPoint get() { + return CURRENT.get(); + } + } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InstanceHandleImpl.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InstanceHandleImpl.java index d1d1beb91f573..48c2a389e8392 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InstanceHandleImpl.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InstanceHandleImpl.java @@ -17,7 +17,6 @@ package io.quarkus.arc; import java.util.concurrent.atomic.AtomicBoolean; - import javax.enterprise.context.Dependent; import javax.enterprise.context.spi.CreationalContext; @@ -43,14 +42,15 @@ public static final InstanceHandle unavailable() { private final CreationalContext creationalContext; private final CreationalContext parentCreationalContext; - + private final AtomicBoolean destroyed; InstanceHandleImpl(InjectableBean bean, T instance, CreationalContext creationalContext) { this(bean, instance, creationalContext, null); } - InstanceHandleImpl(InjectableBean bean, T instance, CreationalContext creationalContext, CreationalContext parentCreationalContext) { + InstanceHandleImpl(InjectableBean bean, T instance, CreationalContext creationalContext, + CreationalContext parentCreationalContext) { this.bean = bean; this.instance = instance; this.creationalContext = creationalContext; @@ -95,5 +95,5 @@ public String toString() { return "InstanceHandleImpl [bean=" + bean + ", instance=" + instance + ", creationalContext=" + creationalContext + ", parentCreationalContext=" + parentCreationalContext + ", destroyed=" + destroyed + "]"; } - + } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InstanceImpl.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InstanceImpl.java index ab2c323299ea2..bf5ac316df652 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InstanceImpl.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InstanceImpl.java @@ -16,7 +16,9 @@ package io.quarkus.arc; +import io.quarkus.arc.CurrentInjectionPointProvider.InjectionPointImpl; import java.lang.annotation.Annotation; +import java.lang.reflect.Member; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.Collection; @@ -24,10 +26,10 @@ import java.util.HashSet; import java.util.Iterator; import java.util.Set; - 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; import javax.inject.Provider; @@ -39,34 +41,43 @@ class InstanceImpl implements Instance { private static final Annotation[] EMPTY_ANNOTATION_ARRAY = new Annotation[] {}; - private final Type type; - - private final Set qualifiers; - private final CreationalContextImpl creationalContext; + private final Set> resolvedBeans; + + private final Type injectionPointType; + private final Type requiredType; + private final Set requiredQualifiers; + private final InjectableBean targetBean; + private final Set annotations; + private final Member javaMember; + private final int position; + + InstanceImpl(InjectableBean targetBean, Type type, Set qualifiers, + CreationalContextImpl creationalContext, Set annotations, Member javaMember, int position) { + this(targetBean, type, getRequiredType(type), qualifiers, creationalContext, annotations, javaMember, position); + } - private final Set> beans; + InstanceImpl(InstanceImpl parent, Type requiredType, Set requiredQualifiers) { + this(parent.targetBean, parent.injectionPointType, requiredType, requiredQualifiers, parent.creationalContext, + parent.annotations, parent.javaMember, parent.position); + } - InstanceImpl(Type type, Set qualifiers, CreationalContextImpl creationalContext) { - if (type instanceof ParameterizedType) { - ParameterizedType parameterizedType = (ParameterizedType) type; - if (Provider.class.isAssignableFrom(Types.getRawType(parameterizedType.getRawType()))) { - this.type = parameterizedType.getActualTypeArguments()[0]; - } else { - this.type = type; - } - } else { - this.type = type; - } - this.qualifiers = qualifiers != null ? qualifiers : Collections.emptySet(); + InstanceImpl(InjectableBean targetBean, Type injectionPointType, Type requiredType, Set requiredQualifiers, + CreationalContextImpl creationalContext, Set annotations, Member javaMember, int position) { + this.injectionPointType = injectionPointType; + this.requiredType = requiredType; + this.requiredQualifiers = requiredQualifiers != null ? requiredQualifiers : Collections.emptySet(); this.creationalContext = creationalContext; - - if (this.qualifiers.isEmpty() && Object.class.equals(type)) { + if (this.requiredQualifiers.isEmpty() && Object.class.equals(requiredType)) { // Do not prefetch the beans for Instance with no qualifiers - this.beans = null; + this.resolvedBeans = null; } else { - this.beans = resolve(); + this.resolvedBeans = resolve(); } + this.targetBean = targetBean; + this.annotations = annotations; + this.javaMember = javaMember; + this.position = position; } @Override @@ -88,23 +99,23 @@ public T get() { @Override public Instance select(Annotation... qualifiers) { - Set newQualifiers = new HashSet<>(this.qualifiers); + Set newQualifiers = new HashSet<>(this.requiredQualifiers); Collections.addAll(newQualifiers, qualifiers); - return new InstanceImpl<>(type, newQualifiers, creationalContext); + return new InstanceImpl<>(this, requiredType, newQualifiers); } @Override public Instance select(Class subtype, Annotation... qualifiers) { - Set newQualifiers = new HashSet<>(this.qualifiers); + Set newQualifiers = new HashSet<>(this.requiredQualifiers); Collections.addAll(newQualifiers, qualifiers); - return new InstanceImpl<>(subtype, newQualifiers, creationalContext); + return new InstanceImpl<>(this, subtype, newQualifiers); } @Override public Instance select(TypeLiteral subtype, Annotation... qualifiers) { - Set newQualifiers = new HashSet<>(this.qualifiers); + Set newQualifiers = new HashSet<>(this.requiredQualifiers); Collections.addAll(newQualifiers, qualifiers); - return new InstanceImpl<>(subtype.getType(), newQualifiers, creationalContext); + return new InstanceImpl<>(this, subtype.getType(), newQualifiers); } @Override @@ -125,24 +136,31 @@ public void destroy(T instance) { // Try to destroy a dependent instance creationalContext.destroyDependentInstance(instance); } - + void destroy() { creationalContext.release(); } private T getBeanInstance(InjectableBean bean) { CreationalContextImpl ctx = creationalContext.child(); - // TODO current injection point? - T instance = bean.get(ctx); + InjectionPoint prev = InjectionPointProvider + .set(new InjectionPointImpl(injectionPointType, requiredType, requiredQualifiers, targetBean, annotations, + javaMember, position)); + T instance; + try { + instance = bean.get(ctx); + } finally { + InjectionPointProvider.set(prev); + } return instance; } private Set> beans() { - return beans != null ? beans : resolve(); + return resolvedBeans != null ? resolvedBeans : resolve(); } private Set> resolve() { - return ArcContainerImpl.instance().getResolvedBeans(type, qualifiers.toArray(EMPTY_ANNOTATION_ARRAY)); + return ArcContainerImpl.instance().getResolvedBeans(requiredType, requiredQualifiers.toArray(EMPTY_ANNOTATION_ARRAY)); } class InstanceIterator implements Iterator { @@ -171,4 +189,14 @@ public T next() { } + private static Type getRequiredType(Type type) { + if (type instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) type; + if (Provider.class.isAssignableFrom(Types.getRawType(parameterizedType.getRawType()))) { + return parameterizedType.getActualTypeArguments()[0]; + } + } + throw new IllegalArgumentException("Not a valid type: " + type); + } + } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InstanceProvider.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InstanceProvider.java index 4dbcaded31d01..6605ccaa0bc40 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InstanceProvider.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InstanceProvider.java @@ -17,9 +17,9 @@ package io.quarkus.arc; import java.lang.annotation.Annotation; +import java.lang.reflect.Member; import java.lang.reflect.Type; import java.util.Set; - import javax.enterprise.context.spi.CreationalContext; import javax.enterprise.inject.Instance; @@ -30,17 +30,26 @@ public class InstanceProvider implements InjectableReferenceProvider> { private final Type requiredType; - private final Set qualifiers; + private final InjectableBean targetBean; + private final Set annotations; + private final Member javaMember; + private final int position; - public InstanceProvider(Type type, Set qualifiers) { + public InstanceProvider(Type type, Set qualifiers, InjectableBean targetBean, Set annotations, + Member javaMember, int position) { this.requiredType = type; this.qualifiers = qualifiers; + this.targetBean = targetBean; + this.annotations = annotations; + this.javaMember = javaMember; + this.position = position; } @Override public Instance get(CreationalContext> creationalContext) { - return new InstanceImpl(requiredType, qualifiers, CreationalContextImpl.unwrap(creationalContext)); + return new InstanceImpl(targetBean, requiredType, qualifiers, CreationalContextImpl.unwrap(creationalContext), + annotations, javaMember, position); } } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InvariantTypes.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InvariantTypes.java index 6428db929d63d..e21c499400447 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InvariantTypes.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InvariantTypes.java @@ -27,13 +27,17 @@ * * Utility class that captures invariant Java assignability rules (used with generics). * - * This class operates on all the possible Type subtypes: Class, ParameterizedType, TypeVariable, WildcardType, GenericArrayType. To make this class easier to - * understand and maintain, there is a separate isAssignableFrom method for each combination of possible types. Each of these methods compares two type + * This class operates on all the possible Type subtypes: Class, ParameterizedType, TypeVariable, WildcardType, + * GenericArrayType. To make this class easier to + * understand and maintain, there is a separate isAssignableFrom method for each combination of possible types. Each of these + * methods compares two type * instances and determines whether the first one is assignable from the other. * - * Since Java wildcards are by definition covariant, this class does not operate on wildcards and instead delegates to {@link CovariantTypes}. + * Since Java wildcards are by definition covariant, this class does not operate on wildcards and instead delegates to + * {@link CovariantTypes}. * - * TypeVariables are considered a specific unknown type restricted by the upper bound. No inference of type variables is performed. + * TypeVariables are considered a specific unknown type restricted by the upper bound. No inference of type variables is + * performed. * * @author Jozef Hartinger * diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InvocationContextImpl.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InvocationContextImpl.java index 246ae14783877..62e9434fcc677 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InvocationContextImpl.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InvocationContextImpl.java @@ -27,7 +27,6 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; import java.util.function.Supplier; - import javax.enterprise.inject.spi.InterceptionType; import javax.interceptor.InvocationContext; @@ -49,7 +48,8 @@ public class InvocationContextImpl implements InvocationContext { * @param interceptorBindings * @return a new {@link javax.interceptor.AroundInvoke} invocation context */ - public static InvocationContextImpl aroundInvoke(Object target, Method method, Object[] args, List chain, + public static InvocationContextImpl aroundInvoke(Object target, Method method, Object[] args, + List chain, Function aroundInvokeForward, Set interceptorBindings) { return new InvocationContextImpl(target, method, null, args, chain, aroundInvokeForward, null, interceptorBindings); } @@ -61,7 +61,8 @@ public static InvocationContextImpl aroundInvoke(Object target, Method method, O * @param interceptorBindings * @return a new {@link javax.annotation.PostConstruct} invocation context */ - public static InvocationContextImpl postConstruct(Object target, List chain, Set interceptorBindings) { + public static InvocationContextImpl postConstruct(Object target, List chain, + Set interceptorBindings) { return new InvocationContextImpl(target, null, null, null, chain, null, null, interceptorBindings); } @@ -72,7 +73,8 @@ public static InvocationContextImpl postConstruct(Object target, List chain, Set interceptorBindings) { + public static InvocationContextImpl preDestroy(Object target, List chain, + Set interceptorBindings) { return new InvocationContextImpl(target, null, null, null, chain, null, null, interceptorBindings); } @@ -83,9 +85,11 @@ public static InvocationContextImpl preDestroy(Object target, List constructor, List chain, Supplier aroundConstructForward, + public static InvocationContextImpl aroundConstruct(Constructor constructor, List chain, + Supplier aroundConstructForward, Set interceptorBindings) { - return new InvocationContextImpl(null, null, constructor, null, chain, null, aroundConstructForward, interceptorBindings); + return new InvocationContextImpl(null, null, constructor, null, chain, null, aroundConstructForward, + interceptorBindings); } private final AtomicReference target; @@ -118,8 +122,10 @@ public static InvocationContextImpl aroundConstruct(Constructor constructor, * @param aroundConstructForward * @param interceptorBindings */ - InvocationContextImpl(Object target, Method method, Constructor constructor, Object[] args, List chain, - Function aroundInvokeForward, Supplier aroundConstructForward, Set interceptorBindings) { + InvocationContextImpl(Object target, Method method, Constructor constructor, Object[] args, + List chain, + Function aroundInvokeForward, Supplier aroundConstructForward, + Set interceptorBindings) { this.target = new AtomicReference<>(target); this.method = method; this.constructor = constructor; @@ -252,7 +258,8 @@ public static InterceptorInvocation aroundConstruct(InjectableInterceptor int private final Object interceptorInstance; - InterceptorInvocation(InterceptionType interceptionType, InjectableInterceptor interceptor, Object interceptorInstance) { + InterceptorInvocation(InterceptionType interceptionType, InjectableInterceptor interceptor, + Object interceptorInstance) { this.interceptionType = interceptionType; this.interceptor = interceptor; this.interceptorInstance = interceptorInstance; diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ManagedContext.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ManagedContext.java index 60e7956253ea9..6b9aef35c74ee 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ManagedContext.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ManagedContext.java @@ -32,7 +32,8 @@ default void activate() { } /** - * Activate the context. All instance handles from the initial state must have the same scope as the context, otherwise an {@link IllegalArgumentException} + * Activate the context. All instance handles from the initial state must have the same scope as the context, otherwise an + * {@link IllegalArgumentException} * is thrown. * * @param initialState The initial state, may be {@code null} diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ParameterizedTypeImpl.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ParameterizedTypeImpl.java index 64489255725e6..4207acbbec4cd 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ParameterizedTypeImpl.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ParameterizedTypeImpl.java @@ -56,7 +56,8 @@ public Type getRawType() { @Override public int hashCode() { - return Arrays.hashCode(actualTypeArguments) ^ (ownerType == null ? 0 : ownerType.hashCode()) ^ (rawType == null ? 0 : rawType.hashCode()); + return Arrays.hashCode(actualTypeArguments) ^ (ownerType == null ? 0 : ownerType.hashCode()) + ^ (rawType == null ? 0 : rawType.hashCode()); } @Override diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/Qualifiers.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/Qualifiers.java index e60352ae15a64..b9a7ea50c352b 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/Qualifiers.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/Qualifiers.java @@ -21,7 +21,6 @@ import java.lang.reflect.Method; import java.util.HashSet; import java.util.Set; - import javax.enterprise.inject.Any; import javax.enterprise.inject.Default; import javax.enterprise.util.Nonbinding; @@ -52,7 +51,8 @@ static boolean hasQualifier(InjectableBean bean, Annotation requiredQualifier if (qualifierClass.equals(requiredQualifier.annotationType())) { boolean matches = true; for (Method value : members) { - if (!value.isAnnotationPresent(Nonbinding.class) && !invoke(value, requiredQualifier).equals(invoke(value, qualifier))) { + if (!value.isAnnotationPresent(Nonbinding.class) + && !invoke(value, requiredQualifier).equals(invoke(value, qualifier))) { matches = false; break; } @@ -64,7 +64,7 @@ static boolean hasQualifier(InjectableBean bean, Annotation requiredQualifier } return false; } - + static boolean hasQualifier(Set qualifiers, Annotation requiredQualifier) { Class requiredQualifierClass = requiredQualifier.annotationType(); @@ -75,7 +75,8 @@ static boolean hasQualifier(Set qualifiers, Annotation requiredQuali if (qualifierClass.equals(requiredQualifier.annotationType())) { boolean matches = true; for (Method value : members) { - if (!value.isAnnotationPresent(Nonbinding.class) && !invoke(value, requiredQualifier).equals(invoke(value, qualifier))) { + if (!value.isAnnotationPresent(Nonbinding.class) + && !invoke(value, requiredQualifier).equals(invoke(value, qualifier))) { matches = false; break; } @@ -87,7 +88,7 @@ static boolean hasQualifier(Set qualifiers, Annotation requiredQuali } return false; } - + static boolean isSubset(Set observedQualifiers, Set eventQualifiers) { for (Annotation required : observedQualifiers) { if (!hasQualifier(eventQualifiers, required)) { @@ -111,7 +112,8 @@ private static Object invoke(Method method, Object instance) { } return method.invoke(instance); } catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) { - throw new RuntimeException("Error checking value of member method " + method.getName() + " on " + method.getDeclaringClass(), e); + throw new RuntimeException( + "Error checking value of member method " + method.getName() + " on " + method.getDeclaringClass(), e); } } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/Reflections.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/Reflections.java index 861b01b70aecf..36431dd2a7922 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/Reflections.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/Reflections.java @@ -31,7 +31,7 @@ public final class Reflections { private Reflections() { } - + public static Field findField(Class clazz, String fieldName) { try { return clazz.getDeclaredField(fieldName); @@ -74,7 +74,8 @@ public static Object newInstance(Class clazz, Class[] parameterTypes, Obje throw new RuntimeException("Cannot invoke constructor: " + clazz.getName(), e); } } - throw new RuntimeException("No " + clazz.getName() + "constructor found for params: " + Arrays.toString(parameterTypes)); + throw new RuntimeException( + "No " + clazz.getName() + "constructor found for params: " + Arrays.toString(parameterTypes)); } public static Object readField(Class clazz, String name, Object instance) { @@ -108,7 +109,13 @@ public static Object invokeMethod(Class clazz, String name, Class[] paramT method.setAccessible(true); } return method.invoke(instance, args); - } catch (NoSuchMethodException | SecurityException | IllegalArgumentException | IllegalAccessException | InvocationTargetException e) { + } catch (InvocationTargetException e) { + Throwable t = e.getTargetException(); + if (t instanceof RuntimeException) { + throw (RuntimeException) t; + } + throw new RuntimeException("Cannot invoke method: " + clazz.getName() + "#" + name + " on " + instance, e); + } catch (NoSuchMethodException | SecurityException | IllegalArgumentException | IllegalAccessException e) { throw new RuntimeException("Cannot invoke method: " + clazz.getName() + "#" + name + " on " + instance, e); } } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/RequestContext.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/RequestContext.java index 1bc17fd79baf8..d082db4e2f002 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/RequestContext.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/RequestContext.java @@ -18,15 +18,20 @@ import java.lang.annotation.Annotation; 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.Map; - +import javax.enterprise.context.BeforeDestroyed; import javax.enterprise.context.ContextNotActiveException; +import javax.enterprise.context.Destroyed; +import javax.enterprise.context.Initialized; import javax.enterprise.context.RequestScoped; import javax.enterprise.context.spi.Contextual; import javax.enterprise.context.spi.CreationalContext; +import javax.enterprise.inject.Any; /** * The built-in context for {@link RequestScoped}. @@ -38,6 +43,22 @@ class RequestContext implements ManagedContext { // It's a normal scope so there may be no more than one mapped instance per contextual type per thread private final ThreadLocal, ContextInstanceHandle>> currentContext = new ThreadLocal<>(); + private final LazyValue> initializedNotifier; + private final LazyValue> beforeDestroyedNotifier; + private final LazyValue> destroyedNotifier; + + public RequestContext() { + this.initializedNotifier = new LazyValue<>(() -> EventImpl.createNotifier(Object.class, Object.class, + new HashSet<>(Arrays.asList(Initialized.Literal.REQUEST, Any.Literal.INSTANCE)), + ArcContainerImpl.instance())); + this.beforeDestroyedNotifier = new LazyValue<>(() -> EventImpl.createNotifier(Object.class, Object.class, + new HashSet<>(Arrays.asList(BeforeDestroyed.Literal.REQUEST, Any.Literal.INSTANCE)), + ArcContainerImpl.instance())); + this.destroyedNotifier = new LazyValue<>(() -> EventImpl.createNotifier(Object.class, Object.class, + new HashSet<>(Arrays.asList(Destroyed.Literal.REQUEST, Any.Literal.INSTANCE)), + ArcContainerImpl.instance())); + } + @Override public Class getScope() { return RequestScoped.class; @@ -93,6 +114,18 @@ public void destroy(Contextual contextual) { } } + private void fireIfNotEmpty(LazyValue> value) { + EventImpl.Notifier notifier = value.get(); + if (!notifier.isEmpty()) { + notifier.notify(toString()); + } + } + + @Override + public void deactivate() { + currentContext.remove(); + } + @Override public void activate(Collection> initialState) { Map, ContextInstanceHandle> state = new HashMap<>(); @@ -105,11 +138,8 @@ public void activate(Collection> initialState) { } } currentContext.set(state); - } - - @Override - public void deactivate() { - currentContext.remove(); + // Fire an event with qualifier @Initialized(RequestScoped.class) if there are any observers for it + fireIfNotEmpty(initializedNotifier); } @Override @@ -119,7 +149,11 @@ public void destroy() { synchronized (ctx) { for (InstanceHandle instance : ctx.values()) { try { + // Fire an event with qualifier @BeforeDestroyed(RequestScoped.class) if there are any observers for it + fireIfNotEmpty(beforeDestroyedNotifier); instance.destroy(); + // Fire an event with qualifier @Destroyed(RequestScoped.class) if there are any observers for it + fireIfNotEmpty(destroyedNotifier); } catch (Exception e) { throw new IllegalStateException("Unable to destroy instance" + instance.get(), e); } @@ -128,5 +162,4 @@ public void destroy() { } } } - } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ResourceProvider.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ResourceProvider.java index 144e41f4178dc..44b5c5d2d1a5b 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ResourceProvider.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ResourceProvider.java @@ -19,7 +19,6 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Type; import java.util.Set; - import javax.enterprise.context.spi.CreationalContext; /** diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ResourceReferenceProvider.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ResourceReferenceProvider.java index bdf3940e8e1bf..17b13d0d3ec9e 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ResourceReferenceProvider.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ResourceReferenceProvider.java @@ -26,7 +26,8 @@ public interface ResourceReferenceProvider { /** - * A resource reference handle is a dependent object of the object it is injected into. {@link InstanceHandle#destroy()} is called when the target object is + * A resource reference handle is a dependent object of the object it is injected into. {@link InstanceHandle#destroy()} is + * called when the target object is * destroyed. * *
diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/SingletonContext.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/SingletonContext.java
index e4054b8a7d11a..b80e763424672 100644
--- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/SingletonContext.java
+++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/SingletonContext.java
@@ -17,7 +17,6 @@
 package io.quarkus.arc;
 
 import java.lang.annotation.Annotation;
-
 import javax.inject.Singleton;
 
 class SingletonContext extends AbstractSharedContext {
diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/TypeResolver.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/TypeResolver.java
index 36df666abcd68..9f851b3db110f 100644
--- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/TypeResolver.java
+++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/TypeResolver.java
@@ -96,7 +96,8 @@ public Type resolveType(GenericArrayType type) {
             resolvedType = resolveType((GenericArrayType) genericComponentType);
         }
         /*
-         * If the generic component type resolved to a class (e.g. String) we return [Ljava.lang.String; (the class representing the
+         * If the generic component type resolved to a class (e.g. String) we return [Ljava.lang.String; (the class representing
+         * the
          * array) instead of GenericArrayType with String as its generic component type.
          */
         if (resolvedType instanceof Class) {
diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/Types.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/Types.java
index 9d279fe360673..2919dc23e9ecb 100644
--- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/Types.java
+++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/Types.java
@@ -129,7 +129,8 @@ static  Class getRawType(Type type) {
     /**
      * Returns a canonical type for a given class.
      *
-     * If the class is a raw type of a parameterized class, the matching {@link ParameterizedType} (with unresolved type variables) is resolved.
+     * If the class is a raw type of a parameterized class, the matching {@link ParameterizedType} (with unresolved type
+     * variables) is resolved.
      *
      * If the class is an array then the component type of the array is canonicalized
      *
diff --git a/independent-projects/arc/tests/pom.xml b/independent-projects/arc/tests/pom.xml
index ce7fbd404c9bc..5b7643111b595 100644
--- a/independent-projects/arc/tests/pom.xml
+++ b/independent-projects/arc/tests/pom.xml
@@ -1,3 +1,4 @@
+
 
-
 
-  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">
+    4.0.0
 
-  
-    io.quarkus.arc
-    arc-parent
-    999-SNAPSHOT
-    ../
-  
+    
+        io.quarkus.arc
+        arc-parent
+        999-SNAPSHOT
+        ../
+    
 
-  arc-tests
-  ArC - Tests
+    arc-tests
+    ArC - Tests
 
-  
-    true
-  
+    
+        true
+    
 
-  
+    
 
-    
-      io.quarkus.arc
-      arc-runtime
-    
+        
+            io.quarkus.arc
+            arc
+        
 
-    
-      io.quarkus.arc
-      arc-processor
-    
+        
+            io.quarkus.arc
+            arc-processor
+        
 
-    
-      junit
-      junit
-    
+        
+            junit
+            junit
+        
 
-    
-      javax.persistence
-      javax.persistence-api
-    
+        
+            javax.persistence
+            javax.persistence-api
+        
 
-  
+    
 
+    
+        
+            
+                org.sonatype.plugins
+                nexus-staging-maven-plugin
+                ${nexus-staging-maven-plugin.version}
+                
+                    true
+                
+            
+        
+    
 
 
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 1ab1f59985242..7f2a6fed4e6d3 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
@@ -16,6 +16,18 @@
 
 package io.quarkus.arc.test;
 
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.ComponentsProvider;
+import io.quarkus.arc.ResourceReferenceProvider;
+import io.quarkus.arc.processor.AnnotationsTransformer;
+import io.quarkus.arc.processor.BeanArchives;
+import io.quarkus.arc.processor.BeanDeploymentValidator;
+import io.quarkus.arc.processor.BeanInfo;
+import io.quarkus.arc.processor.BeanProcessor;
+import io.quarkus.arc.processor.BeanRegistrar;
+import io.quarkus.arc.processor.ContextRegistrar;
+import io.quarkus.arc.processor.DeploymentEnhancer;
+import io.quarkus.arc.processor.ResourceOutput;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
@@ -32,7 +44,6 @@
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
-
 import org.jboss.jandex.DotName;
 import org.jboss.jandex.Index;
 import org.jboss.jandex.Indexer;
@@ -40,18 +51,6 @@
 import org.junit.runner.Description;
 import org.junit.runners.model.Statement;
 
-import io.quarkus.arc.Arc;
-import io.quarkus.arc.ComponentsProvider;
-import io.quarkus.arc.ResourceReferenceProvider;
-import io.quarkus.arc.processor.AnnotationsTransformer;
-import io.quarkus.arc.processor.BeanDeploymentValidator;
-import io.quarkus.arc.processor.BeanInfo;
-import io.quarkus.arc.processor.BeanProcessor;
-import io.quarkus.arc.processor.BeanRegistrar;
-import io.quarkus.arc.processor.ContextRegistrar;
-import io.quarkus.arc.processor.DeploymentEnhancer;
-import io.quarkus.arc.processor.ResourceOutput;
-
 public class ArcTestContainer implements TestRule {
 
     private static final String TARGET_TEST_CLASSES = "target/test-classes";
@@ -106,7 +105,7 @@ public Builder beanRegistrars(BeanRegistrar... registrars) {
             Collections.addAll(this.beanRegistrars, registrars);
             return this;
         }
-        
+
         public Builder contextRegistrars(ContextRegistrar... registrars) {
             Collections.addAll(this.contextRegistrars, registrars);
             return this;
@@ -143,7 +142,8 @@ public Builder shouldFail() {
         }
 
         public ArcTestContainer build() {
-            return new ArcTestContainer(resourceReferenceProviders, beanClasses, resourceAnnotations, beanRegistrars, contextRegistrars, annotationsTransformers,
+            return new ArcTestContainer(resourceReferenceProviders, beanClasses, resourceAnnotations, beanRegistrars,
+                    contextRegistrars, annotationsTransformers,
                     deploymentEnhancers, beanDeploymentValidators, shouldFail, removeUnusedBeans, exclusions);
         }
 
@@ -156,7 +156,7 @@ public ArcTestContainer build() {
     private final List> resourceAnnotations;
 
     private final List beanRegistrars;
-    
+
     private final List contextRegistrars;
 
     private final List annotationsTransformers;
@@ -174,13 +174,18 @@ public ArcTestContainer build() {
     private URLClassLoader testClassLoader;
 
     public ArcTestContainer(Class... beanClasses) {
-        this(Collections.emptyList(), Arrays.asList(beanClasses), Collections.emptyList(), Collections.emptyList(), Collections.emptyList(),
-                Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), false, false, Collections.emptyList());
+        this(Collections.emptyList(), Arrays.asList(beanClasses), Collections.emptyList(), Collections.emptyList(),
+                Collections.emptyList(),
+                Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), false, false,
+                Collections.emptyList());
     }
 
-    public ArcTestContainer(List> resourceReferenceProviders, List> beanClasses, List> resourceAnnotations,
-            List beanRegistrars, List contextRegistrars, List annotationsTransformers, List deploymentEnhancers,
-            List beanDeploymentValidators, boolean shouldFail, boolean removeUnusedBeans, List> exclusions) {
+    public ArcTestContainer(List> resourceReferenceProviders, List> beanClasses,
+            List> resourceAnnotations,
+            List beanRegistrars, List contextRegistrars,
+            List annotationsTransformers, List deploymentEnhancers,
+            List beanDeploymentValidators, boolean shouldFail, boolean removeUnusedBeans,
+            List> exclusions) {
         this.resourceReferenceProviders = resourceReferenceProviders;
         this.beanClasses = beanClasses;
         this.resourceAnnotations = resourceAnnotations;
@@ -205,10 +210,10 @@ public void evaluate() throws Throwable {
                     base.evaluate();
                 } finally {
                     Thread.currentThread().setContextClassLoader(oldTccl);
-                    if(testClassLoader != null) {
+                    if (testClassLoader != null) {
                         try {
                             testClassLoader.close();
-                        } catch(IOException e) {
+                        } catch (IOException e) {
                             e.printStackTrace();
                         }
                     }
@@ -243,8 +248,8 @@ private ClassLoader init(Class testClass) {
                 .getContextClassLoader();
 
         try {
-            String arcContainerAbsolutePath =
-                    ArcTestContainer.class.getClassLoader().getResource(ArcTestContainer.class.getName().replace(".", "/") + ".class").getFile();
+            String arcContainerAbsolutePath = ArcTestContainer.class.getClassLoader()
+                    .getResource(ArcTestContainer.class.getName().replace(".", "/") + ".class").getFile();
             int targetClassesIndex = arcContainerAbsolutePath.indexOf(TARGET_TEST_CLASSES);
             String testClassesRootPath = arcContainerAbsolutePath.substring(0, targetClassesIndex);
             File generatedSourcesDirectory = new File("target/generated-arc-sources");
@@ -269,7 +274,7 @@ private ClassLoader init(Class testClass) {
 
             BeanProcessor.Builder beanProcessorBuilder = BeanProcessor.builder()
                     .setName(testClass.getSimpleName())
-                    .setIndex(BeanProcessor.addBuiltinClasses(index));
+                    .setIndex(BeanArchives.buildBeanArchiveIndex(index));
             if (!resourceAnnotations.isEmpty()) {
                 beanProcessorBuilder.addResourceAnnotations(resourceAnnotations.stream()
                         .map(c -> DotName.createSimple(c.getName()))
@@ -279,7 +284,7 @@ private ClassLoader init(Class testClass) {
                 beanProcessorBuilder.addBeanRegistrar(registrar);
             }
             for (ContextRegistrar registrar : contextRegistrars) {
-                beanProcessorBuilder.addContextRegistrar(registrar);    
+                beanProcessorBuilder.addContextRegistrar(registrar);
             }
             for (AnnotationsTransformer annotationsTransformer : annotationsTransformers) {
                 beanProcessorBuilder.addAnnotationTransformer(annotationsTransformer);
@@ -333,7 +338,8 @@ public Enumeration getResources(String name) throws IOException {
                         // return URL that points to the correct test bean provider
                         return Collections.enumeration(Collections.singleton(componentsProviderFile.toURI()
                                 .toURL()));
-                    } else if (("META-INF/services/" + ResourceReferenceProvider.class.getName()).equals(name) && !resourceReferenceProviders.isEmpty()) {
+                    } else if (("META-INF/services/" + ResourceReferenceProvider.class.getName()).equals(name)
+                            && !resourceReferenceProviders.isEmpty()) {
                         return Collections.enumeration(Collections.singleton(resourceReferenceProviderFile.toURI()
                                 .toURL()));
                     }
@@ -359,7 +365,8 @@ public Enumeration getResources(String name) throws IOException {
     private Index index(Iterable> classes) throws IOException {
         Indexer indexer = new Indexer();
         for (Class clazz : classes) {
-            try (InputStream stream = ArcTestContainer.class.getClassLoader().getResourceAsStream(clazz.getName().replace('.', '/') + ".class")) {
+            try (InputStream stream = ArcTestContainer.class.getClassLoader()
+                    .getResourceAsStream(clazz.getName().replace('.', '/') + ".class")) {
                 indexer.index(stream);
             }
         }
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/MyQualifier.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/MyQualifier.java
index b05846e770a3f..19171c2666253 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/MyQualifier.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/MyQualifier.java
@@ -25,7 +25,6 @@
 import java.lang.annotation.Inherited;
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
-
 import javax.inject.Qualifier;
 
 @Qualifier
@@ -34,4 +33,4 @@
 @Retention(RUNTIME)
 public @interface MyQualifier {
 
-}
\ No newline at end of file
+}
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/alternatives/AlternativesPriorityTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/alternatives/AlternativesPriorityTest.java
index f4827308571f3..556dd25dc6b63 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/alternatives/AlternativesPriorityTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/alternatives/AlternativesPriorityTest.java
@@ -18,16 +18,14 @@
 
 import static org.junit.Assert.assertEquals;
 
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.test.ArcTestContainer;
 import java.util.function.Supplier;
-
 import javax.annotation.Priority;
 import javax.enterprise.inject.Alternative;
 import javax.enterprise.inject.Produces;
 import javax.enterprise.util.TypeLiteral;
 import javax.inject.Singleton;
-
-import io.quarkus.arc.Arc;
-import io.quarkus.arc.test.ArcTestContainer;
 import org.junit.Rule;
 import org.junit.Test;
 
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/alternatives/AlternativesTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/alternatives/AlternativesTest.java
index fbd91c545f350..9fe7390edf364 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/alternatives/AlternativesTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/alternatives/AlternativesTest.java
@@ -18,16 +18,14 @@
 
 import static org.junit.Assert.assertEquals;
 
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.test.ArcTestContainer;
 import java.util.function.Supplier;
-
 import javax.annotation.Priority;
 import javax.enterprise.inject.Alternative;
 import javax.enterprise.util.TypeLiteral;
 import javax.inject.Inject;
 import javax.inject.Singleton;
-
-import io.quarkus.arc.Arc;
-import io.quarkus.arc.test.ArcTestContainer;
 import org.junit.Rule;
 import org.junit.Test;
 
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/beanmanager/BeanManagerEventTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/beanmanager/BeanManagerEventTest.java
index d881a010fc8b3..85eb93d2d922c 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/beanmanager/BeanManagerEventTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/beanmanager/BeanManagerEventTest.java
@@ -18,14 +18,12 @@
 
 import static org.junit.Assert.assertEquals;
 
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.test.ArcTestContainer;
 import java.util.concurrent.atomic.AtomicReference;
-
 import javax.enterprise.context.Dependent;
 import javax.enterprise.event.Observes;
 import javax.enterprise.inject.spi.BeanManager;
-
-import io.quarkus.arc.Arc;
-import io.quarkus.arc.test.ArcTestContainer;
 import org.junit.Rule;
 import org.junit.Test;
 
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/beanmanager/BeanManagerInstanceTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/beanmanager/BeanManagerInstanceTest.java
index 602f1e5b67be0..f0023f1c45978 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/beanmanager/BeanManagerInstanceTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/beanmanager/BeanManagerInstanceTest.java
@@ -19,13 +19,12 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.test.ArcTestContainer;
 import javax.annotation.PostConstruct;
 import javax.enterprise.context.Dependent;
 import javax.enterprise.inject.Instance;
 import javax.enterprise.inject.spi.BeanManager;
-
-import io.quarkus.arc.Arc;
-import io.quarkus.arc.test.ArcTestContainer;
 import org.junit.Rule;
 import org.junit.Test;
 
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/beanmanager/BeanManagerTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/beanmanager/BeanManagerTest.java
index d9e2db6e53d47..8b12b42de30d6 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/beanmanager/BeanManagerTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/beanmanager/BeanManagerTest.java
@@ -21,16 +21,23 @@
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.ManagedContext;
+import io.quarkus.arc.test.ArcTestContainer;
+import java.lang.annotation.Annotation;
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
+import java.lang.reflect.Member;
+import java.lang.reflect.Type;
+import java.util.Collections;
 import java.util.List;
 import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.atomic.AtomicBoolean;
-
 import javax.annotation.PostConstruct;
 import javax.annotation.PreDestroy;
 import javax.annotation.Priority;
@@ -38,8 +45,11 @@
 import javax.enterprise.context.RequestScoped;
 import javax.enterprise.context.spi.CreationalContext;
 import javax.enterprise.inject.Alternative;
+import javax.enterprise.inject.Any;
+import javax.enterprise.inject.spi.Annotated;
 import javax.enterprise.inject.spi.Bean;
 import javax.enterprise.inject.spi.BeanManager;
+import javax.enterprise.inject.spi.InjectionPoint;
 import javax.enterprise.inject.spi.InterceptionType;
 import javax.enterprise.util.AnnotationLiteral;
 import javax.enterprise.util.Nonbinding;
@@ -48,18 +58,15 @@
 import javax.interceptor.Interceptor;
 import javax.interceptor.InterceptorBinding;
 import javax.interceptor.InvocationContext;
-
-import io.quarkus.arc.Arc;
-import io.quarkus.arc.ManagedContext;
-import io.quarkus.arc.test.ArcTestContainer;
 import org.junit.Rule;
 import org.junit.Test;
 
 public class BeanManagerTest {
 
     @Rule
-    public ArcTestContainer container = new ArcTestContainer(Legacy.class, AlternativeLegacy.class, Fool.class, DummyInterceptor.class, DummyBinding.class,
-            LowPriorityInterceptor.class);
+    public ArcTestContainer container = new ArcTestContainer(Legacy.class, AlternativeLegacy.class, Fool.class,
+            DummyInterceptor.class, DummyBinding.class,
+            LowPriorityInterceptor.class, WithInjectionPointMetadata.class);
 
     @Test
     public void testGetBeans() {
@@ -81,10 +88,11 @@ public void testGetReference() {
         @SuppressWarnings("unchecked")
         Bean foolBean = (Bean) foolBeans.iterator().next();
         Fool fool1 = (Fool) beanManager.getReference(foolBean, Fool.class, beanManager.createCreationalContext(foolBean));
-        
+
         ManagedContext requestContext = Arc.container().requestContext();
         requestContext.activate();
-        assertEquals(fool1.getId(), ((Fool) beanManager.getReference(foolBean, Fool.class, beanManager.createCreationalContext(foolBean))).getId());
+        assertEquals(fool1.getId(),
+                ((Fool) beanManager.getReference(foolBean, Fool.class, beanManager.createCreationalContext(foolBean))).getId());
         requestContext.terminate();
         assertTrue(Fool.DESTROYED.get());
 
@@ -98,7 +106,57 @@ public void testGetReference() {
         ctx.release();
         assertTrue(Legacy.DESTROYED.get());
     }
-    
+
+    @Test
+    public void testGetInjectableReference() {
+        BeanManager beanManager = Arc.container().beanManager();
+        Set> beans = beanManager.getBeans(WithInjectionPointMetadata.class);
+        assertEquals(1, beans.size());
+        @SuppressWarnings("unchecked")
+        Bean bean = (Bean) beans.iterator().next();
+        WithInjectionPointMetadata injectableReference = (WithInjectionPointMetadata) beanManager
+                .getInjectableReference(new InjectionPoint() {
+
+                    @Override
+                    public boolean isTransient() {
+                        return false;
+                    }
+
+                    @Override
+                    public boolean isDelegate() {
+                        return false;
+                    }
+
+                    @Override
+                    public Type getType() {
+                        return WithInjectionPointMetadata.class;
+                    }
+
+                    @Override
+                    public Set getQualifiers() {
+                        return Collections.singleton(Any.Literal.INSTANCE);
+                    }
+
+                    @Override
+                    public Member getMember() {
+                        return null;
+                    }
+
+                    @Override
+                    public Bean getBean() {
+                        return null;
+                    }
+
+                    @Override
+                    public Annotated getAnnotated() {
+                        return null;
+                    }
+                }, beanManager.createCreationalContext(bean));
+        assertNotNull(injectableReference.injectionPoint);
+        assertEquals(WithInjectionPointMetadata.class, injectableReference.injectionPoint.getType());
+        assertNull(injectableReference.injectionPoint.getBean());
+    }
+
     @Test
     public void testResolveInterceptors() {
         BeanManager beanManager = Arc.container().beanManager();
@@ -161,24 +219,24 @@ String getId() {
         }
 
     }
-    
+
     @Target({ TYPE, METHOD })
     @Retention(RUNTIME)
     @Documented
     @InterceptorBinding
     public @interface DummyBinding {
-        
+
         @Nonbinding
         boolean alpha();
-        
+
         boolean bravo();
-        
+
         @SuppressWarnings("serial")
         static class Literal extends AnnotationLiteral implements DummyBinding {
 
             private final boolean alpha;
             private final boolean bravo;
-            
+
             public Literal(boolean alpha, boolean bravo) {
                 this.alpha = alpha;
                 this.bravo = bravo;
@@ -193,11 +251,11 @@ public boolean alpha() {
             public boolean bravo() {
                 return bravo;
             }
-            
+
         }
 
     }
-    
+
     @DummyBinding(alpha = true, bravo = true)
     @Priority(10)
     @Interceptor
@@ -208,7 +266,7 @@ Object intercept(InvocationContext ctx) throws Exception {
             return ctx.proceed();
         }
     }
-    
+
     @DummyBinding(alpha = true, bravo = true)
     @Priority(1)
     @Interceptor
@@ -220,4 +278,12 @@ Object intercept(InvocationContext ctx) throws Exception {
         }
     }
 
+    @Dependent
+    static class WithInjectionPointMetadata {
+
+        @Inject
+        InjectionPoint injectionPoint;
+
+    }
+
 }
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/build/extension/annotations/AddObservesTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/build/extension/annotations/AddObservesTest.java
index 87cf9c0518a0a..297a7e3f2ce15 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/build/extension/annotations/AddObservesTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/build/extension/annotations/AddObservesTest.java
@@ -18,42 +18,43 @@
 
 import static org.junit.Assert.assertTrue;
 
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.processor.AnnotationsTransformer;
+import io.quarkus.arc.test.ArcTestContainer;
 import java.util.concurrent.atomic.AtomicBoolean;
-
 import javax.enterprise.event.Observes;
 import javax.inject.Singleton;
-
-import org.jboss.jandex.MethodInfo;
-import org.jboss.jandex.MethodParameterInfo;
 import org.jboss.jandex.AnnotationInstance;
 import org.jboss.jandex.AnnotationTarget.Kind;
 import org.jboss.jandex.AnnotationValue;
 import org.jboss.jandex.DotName;
-import io.quarkus.arc.Arc;
-import io.quarkus.arc.processor.AnnotationsTransformer;
-import io.quarkus.arc.test.ArcTestContainer;
+import org.jboss.jandex.MethodInfo;
+import org.jboss.jandex.MethodParameterInfo;
 import org.junit.Rule;
 import org.junit.Test;
 
 public class AddObservesTest {
 
     @Rule
-    public ArcTestContainer container = ArcTestContainer.builder().beanClasses(IWantToObserve.class).annotationsTransformers(new AnnotationsTransformer() {
+    public ArcTestContainer container = ArcTestContainer.builder().beanClasses(IWantToObserve.class)
+            .annotationsTransformers(new AnnotationsTransformer() {
 
-        @Override
-        public boolean appliesTo(Kind kind) {
-            return Kind.METHOD == kind;
-        }
+                @Override
+                public boolean appliesTo(Kind kind) {
+                    return Kind.METHOD == kind;
+                }
 
-        @Override
-        public void transform(TransformationContext transformationContext) {
-            MethodInfo method = transformationContext.getTarget().asMethod();
-            if (method.name().equals("observe")) {
-                transformationContext.transform().add(AnnotationInstance.create(DotName.createSimple(Observes.class.getName()),
-                        MethodParameterInfo.create(method, (short) 0), new AnnotationValue[] {})).done();
-            }
-        }
-    }).build();
+                @Override
+                public void transform(TransformationContext transformationContext) {
+                    MethodInfo method = transformationContext.getTarget().asMethod();
+                    if (method.name().equals("observe")) {
+                        transformationContext.transform()
+                                .add(AnnotationInstance.create(DotName.createSimple(Observes.class.getName()),
+                                        MethodParameterInfo.create(method, (short) 0), new AnnotationValue[] {}))
+                                .done();
+                    }
+                }
+            }).build();
 
     @Test
     public void testObserved() {
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/build/extension/annotations/AnnotationsTransformerInterceptorBindingTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/build/extension/annotations/AnnotationsTransformerInterceptorBindingTest.java
index 4f73fa9261d8d..4db8ce3bcb90a 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/build/extension/annotations/AnnotationsTransformerInterceptorBindingTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/build/extension/annotations/AnnotationsTransformerInterceptorBindingTest.java
@@ -18,12 +18,11 @@
 
 import static org.junit.Assert.assertEquals;
 
-import javax.enterprise.context.Dependent;
-
-import org.jboss.jandex.AnnotationTarget.Kind;
 import io.quarkus.arc.Arc;
 import io.quarkus.arc.processor.AnnotationsTransformer;
 import io.quarkus.arc.test.ArcTestContainer;
+import javax.enterprise.context.Dependent;
+import org.jboss.jandex.AnnotationTarget.Kind;
 import org.junit.Rule;
 import org.junit.Test;
 
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/build/extension/annotations/AnnotationsTransformerTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/build/extension/annotations/AnnotationsTransformerTest.java
index 560b757ca4ff1..e80414fa2027e 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/build/extension/annotations/AnnotationsTransformerTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/build/extension/annotations/AnnotationsTransformerTest.java
@@ -21,19 +21,17 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.ArcContainer;
+import io.quarkus.arc.InstanceHandle;
+import io.quarkus.arc.processor.AnnotationsTransformer;
+import io.quarkus.arc.test.ArcTestContainer;
 import java.util.AbstractList;
-
 import javax.enterprise.context.Dependent;
 import javax.enterprise.inject.Vetoed;
 import javax.inject.Inject;
-
 import org.jboss.jandex.AnnotationTarget.Kind;
 import org.jboss.jandex.DotName;
-import io.quarkus.arc.Arc;
-import io.quarkus.arc.ArcContainer;
-import io.quarkus.arc.InstanceHandle;
-import io.quarkus.arc.processor.AnnotationsTransformer;
-import io.quarkus.arc.test.ArcTestContainer;
 import org.junit.Rule;
 import org.junit.Test;
 
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/build/extension/annotations/Simple.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/build/extension/annotations/Simple.java
index a525ce6bd9a35..09ca783271dad 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/build/extension/annotations/Simple.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/build/extension/annotations/Simple.java
@@ -23,7 +23,6 @@
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
-
 import javax.interceptor.InterceptorBinding;
 
 @Target({ TYPE, METHOD })
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 f00badc2ac848..0d832846d6a87 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
@@ -19,11 +19,6 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
-import java.util.Map;
-
-import javax.enterprise.context.ApplicationScoped;
-import javax.enterprise.context.spi.CreationalContext;
-
 import io.quarkus.arc.Arc;
 import io.quarkus.arc.BeanCreator;
 import io.quarkus.arc.processor.BeanConfigurator;
@@ -31,13 +26,17 @@
 import io.quarkus.arc.test.ArcTestContainer;
 import io.quarkus.gizmo.MethodDescriptor;
 import io.quarkus.gizmo.ResultHandle;
+import java.util.Map;
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.context.spi.CreationalContext;
 import org.junit.Rule;
 import org.junit.Test;
 
 public class BeanRegistrarTest {
 
     @Rule
-    public ArcTestContainer container = ArcTestContainer.builder().beanClasses(UselessBean.class).beanRegistrars(new TestRegistrar()).build();
+    public ArcTestContainer container = ArcTestContainer.builder().beanClasses(UselessBean.class)
+            .beanRegistrars(new TestRegistrar()).build();
 
     @Test
     public void testSyntheticBean() {
@@ -49,7 +48,8 @@ static class TestRegistrar implements BeanRegistrar {
 
         @Override
         public boolean initialize(BuildContext buildContext) {
-            assertTrue(buildContext.get(Key.INDEX).getKnownClasses().stream().anyMatch(cl -> cl.name().toString().equals(UselessBean.class.getName())));
+            assertTrue(buildContext.get(Key.INDEX).getKnownClasses().stream()
+                    .anyMatch(cl -> cl.name().toString().equals(UselessBean.class.getName())));
             return true;
         }
 
@@ -66,7 +66,8 @@ public void register(RegistrationContext registrationContext) {
             });
             integerConfigurator.done();
 
-            registrationContext.configure(String.class).types(String.class).param("name", "Frantisek").creator(StringCreator.class).done();
+            registrationContext.configure(String.class).types(String.class).param("name", "Frantisek")
+                    .creator(StringCreator.class).done();
         }
 
     }
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/build/extension/deployment/DeploymentEnhancerTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/build/extension/deployment/DeploymentEnhancerTest.java
index 2d061f8bc39f3..a92b566cbbd9e 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/build/extension/deployment/DeploymentEnhancerTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/build/extension/deployment/DeploymentEnhancerTest.java
@@ -18,10 +18,9 @@
 
 import static org.junit.Assert.assertTrue;
 
-import javax.enterprise.context.Dependent;
-
 import io.quarkus.arc.Arc;
 import io.quarkus.arc.test.ArcTestContainer;
+import javax.enterprise.context.Dependent;
 import org.junit.Rule;
 import org.junit.Test;
 
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/build/extension/validator/BeanDeploymentValidatorTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/build/extension/validator/BeanDeploymentValidatorTest.java
index aa1f9f1df7a07..865ee579aee68 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/build/extension/validator/BeanDeploymentValidatorTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/build/extension/validator/BeanDeploymentValidatorTest.java
@@ -18,25 +18,23 @@
 
 import static org.junit.Assert.assertTrue;
 
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.BeanCreator;
+import io.quarkus.arc.processor.BeanDeploymentValidator;
+import io.quarkus.arc.processor.BeanRegistrar;
+import io.quarkus.arc.test.ArcTestContainer;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
-
 import javax.enterprise.context.ApplicationScoped;
 import javax.enterprise.context.Initialized;
 import javax.enterprise.context.spi.CreationalContext;
 import javax.enterprise.event.Observes;
 import javax.inject.Inject;
-
 import org.jboss.jandex.DotName;
 import org.jboss.jandex.ParameterizedType;
 import org.jboss.jandex.Type;
 import org.jboss.jandex.Type.Kind;
-import io.quarkus.arc.Arc;
-import io.quarkus.arc.BeanCreator;
-import io.quarkus.arc.processor.BeanDeploymentValidator;
-import io.quarkus.arc.processor.BeanRegistrar;
-import io.quarkus.arc.test.ArcTestContainer;
 import org.junit.Rule;
 import org.junit.Test;
 
@@ -55,7 +53,8 @@ static class TestRegistrar implements BeanRegistrar {
 
         @Override
         public void register(RegistrationContext registrationContext) {
-            registrationContext.configure(List.class).types(EmptyStringListCreator.listStringType()).creator(EmptyStringListCreator.class).done();
+            registrationContext.configure(List.class).types(EmptyStringListCreator.listStringType())
+                    .creator(EmptyStringListCreator.class).done();
         }
 
     }
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/cdiprovider/CDIProviderTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/cdiprovider/CDIProviderTest.java
index 7a5884b992b40..9bc2e90dd94e0 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/cdiprovider/CDIProviderTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/cdiprovider/CDIProviderTest.java
@@ -19,16 +19,14 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+import io.quarkus.arc.test.ArcTestContainer;
 import java.io.IOException;
 import java.lang.reflect.Field;
 import java.util.concurrent.atomic.AtomicBoolean;
-
 import javax.annotation.PostConstruct;
 import javax.annotation.PreDestroy;
 import javax.enterprise.context.Dependent;
 import javax.enterprise.inject.spi.CDI;
-
-import io.quarkus.arc.test.ArcTestContainer;
 import org.junit.AfterClass;
 import org.junit.Rule;
 import org.junit.Test;
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/ClientProxyGetContextualInstanceTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/ClientProxyGetContextualInstanceTest.java
index bd54ea8395b55..963bd81a6785c 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/ClientProxyGetContextualInstanceTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/ClientProxyGetContextualInstanceTest.java
@@ -19,14 +19,12 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
-import java.io.IOException;
-
-import javax.annotation.PostConstruct;
-import javax.enterprise.context.ApplicationScoped;
-
 import io.quarkus.arc.Arc;
 import io.quarkus.arc.ClientProxy;
 import io.quarkus.arc.test.ArcTestContainer;
+import java.io.IOException;
+import javax.annotation.PostConstruct;
+import javax.enterprise.context.ApplicationScoped;
 import org.junit.Rule;
 import org.junit.Test;
 
@@ -39,14 +37,14 @@ public class ClientProxyGetContextualInstanceTest {
     public void testProducer() throws IOException {
         Moo moo = Arc.container().instance(Moo.class).get();
         assertTrue(moo instanceof ClientProxy);
-        assertEquals(10, ((Moo)((ClientProxy)moo).getContextualInstance()).val);
+        assertEquals(10, ((Moo) ((ClientProxy) moo).getContextualInstance()).val);
     }
 
     @ApplicationScoped
     static class Moo {
 
         private int val;
-        
+
         @PostConstruct
         void init() {
             val = 10;
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/ProducerClientProxyTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/ProducerClientProxyTest.java
index a972df0ee5dbe..ba0aac8199ee4 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/ProducerClientProxyTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/clientproxy/ProducerClientProxyTest.java
@@ -18,17 +18,14 @@
 
 import static org.junit.Assert.assertEquals;
 
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.InstanceHandle;
+import io.quarkus.arc.test.ArcTestContainer;
 import java.io.IOException;
 import java.util.function.Function;
-
 import javax.enterprise.context.ApplicationScoped;
 import javax.enterprise.inject.Produces;
 import javax.inject.Singleton;
-
-import io.quarkus.arc.Arc;
-import io.quarkus.arc.InstanceHandle;
-import io.quarkus.arc.test.ArcTestContainer;
-
 import org.junit.Rule;
 import org.junit.Test;
 
@@ -47,7 +44,6 @@ public void testProducer() throws IOException {
         assertEquals(Long.valueOf(1), instance2.get().get(Long.valueOf(1)));
         assertEquals(Long.valueOf(1), instance2.get().getDefault(Long.valueOf(1)));
 
-
         InstanceHandle supInstance = Arc.container().instance(FunctionChild.class);
         assertEquals("hi stu", supInstance.get().apply("stu"));
     }
@@ -74,6 +70,7 @@ MyProduct produce() {
         Product2 produce2() {
             return new MyProduct2();
         };
+
         @Produces
         @ApplicationScoped
         FunctionChild produceSupplier() {
@@ -81,7 +78,7 @@ FunctionChild produceSupplier() {
 
             };
         }
-}
+    }
 
     static class MyProduct implements Product {
         @Override
@@ -100,7 +97,8 @@ public  T get(T number) throws IOException {
     interface Product {
 
          T get(T number) throws IOException;
-        default  T getDefault(T number) throws IOException{
+
+        default  T getDefault(T number) throws IOException {
             return number;
         }
     }
@@ -111,7 +109,8 @@ interface Product2 extends Product2Interface {
 
     interface Product2Interface {
          T get(T number) throws IOException;
-        default  T getDefault(T number) throws IOException{
+
+        default  T getDefault(T number) throws IOException {
             return number;
         }
     }
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/contexts/application/ApplicationInitializedTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/contexts/application/ApplicationInitializedTest.java
index 00a8fd80fe9ca..e541bf1d8f96c 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/contexts/application/ApplicationInitializedTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/contexts/application/ApplicationInitializedTest.java
@@ -18,15 +18,14 @@
 
 import static org.junit.Assert.assertTrue;
 
+import io.quarkus.arc.test.ArcTestContainer;
 import java.util.concurrent.atomic.AtomicBoolean;
-
 import javax.enterprise.context.ApplicationScoped;
 import javax.enterprise.context.BeforeDestroyed;
 import javax.enterprise.context.Dependent;
 import javax.enterprise.context.Destroyed;
 import javax.enterprise.context.Initialized;
 import javax.enterprise.event.Observes;
-
 import org.junit.Assert;
 import org.junit.Rule;
 import org.junit.Test;
@@ -35,8 +34,6 @@
 import org.junit.runner.Description;
 import org.junit.runners.model.Statement;
 
-import io.quarkus.arc.test.ArcTestContainer;
-
 public class ApplicationInitializedTest {
 
     @Rule
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/assignability/ListJdkElementTypeTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/assignability/ListJdkElementTypeTest.java
index 4b7c4864c8ce3..0897cfbeb13cb 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/assignability/ListJdkElementTypeTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/assignability/ListJdkElementTypeTest.java
@@ -18,16 +18,14 @@
 
 import static org.junit.Assert.assertEquals;
 
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.test.ArcTestContainer;
 import java.util.Collections;
 import java.util.List;
-
 import javax.enterprise.context.ApplicationScoped;
 import javax.enterprise.context.Dependent;
 import javax.enterprise.inject.Produces;
 import javax.inject.Inject;
-
-import io.quarkus.arc.Arc;
-import io.quarkus.arc.test.ArcTestContainer;
 import org.junit.Rule;
 import org.junit.Test;
 
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/assignability/OptionalAssignabilityTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/assignability/OptionalAssignabilityTest.java
index 47f78ae8baf0b..9f3b97e1218a5 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/assignability/OptionalAssignabilityTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/assignability/OptionalAssignabilityTest.java
@@ -18,16 +18,14 @@
 
 import static org.junit.Assert.assertEquals;
 
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.test.ArcTestContainer;
 import java.util.Optional;
-
 import javax.enterprise.context.ApplicationScoped;
 import javax.enterprise.context.Dependent;
 import javax.enterprise.inject.Produces;
 import javax.enterprise.inject.spi.InjectionPoint;
 import javax.inject.Inject;
-
-import io.quarkus.arc.Arc;
-import io.quarkus.arc.test.ArcTestContainer;
 import org.junit.Rule;
 import org.junit.Test;
 
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/constructornoinject/SingleNonNoArgConstructorInjectionTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/constructornoinject/SingleNonNoArgConstructorInjectionTest.java
index 1b4fd57ae49f2..7305e6195f066 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/constructornoinject/SingleNonNoArgConstructorInjectionTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/constructornoinject/SingleNonNoArgConstructorInjectionTest.java
@@ -16,16 +16,15 @@
 
 package io.quarkus.arc.test.injection.constructornoinject;
 
+import static org.junit.Assert.assertNotNull;
+
 import io.quarkus.arc.Arc;
 import io.quarkus.arc.test.ArcTestContainer;
-import org.junit.Rule;
-import org.junit.Test;
-
 import javax.enterprise.context.Dependent;
 import javax.inject.Inject;
 import javax.inject.Singleton;
-
-import static org.junit.Assert.assertNotNull;
+import org.junit.Rule;
+import org.junit.Test;
 
 public class SingleNonNoArgConstructorInjectionTest {
 
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/privateconstructor/PrivateConstructorInjectionTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/privateconstructor/PrivateConstructorInjectionTest.java
index 785a0770287c3..8c5e8d818fc06 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/privateconstructor/PrivateConstructorInjectionTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/privateconstructor/PrivateConstructorInjectionTest.java
@@ -18,12 +18,11 @@
 
 import static org.junit.Assert.assertNotNull;
 
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.test.ArcTestContainer;
 import javax.enterprise.context.Dependent;
 import javax.inject.Inject;
 import javax.inject.Singleton;
-
-import io.quarkus.arc.Arc;
-import io.quarkus.arc.test.ArcTestContainer;
 import org.junit.Rule;
 import org.junit.Test;
 
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/privatefield/PrivateFieldInjectionTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/privatefield/PrivateFieldInjectionTest.java
index dd385613cbe84..18157a0b3038e 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/privatefield/PrivateFieldInjectionTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/privatefield/PrivateFieldInjectionTest.java
@@ -18,12 +18,11 @@
 
 import static org.junit.Assert.assertNotNull;
 
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.test.ArcTestContainer;
 import javax.enterprise.context.ApplicationScoped;
 import javax.enterprise.context.Dependent;
 import javax.inject.Inject;
-
-import io.quarkus.arc.Arc;
-import io.quarkus.arc.test.ArcTestContainer;
 import org.junit.Rule;
 import org.junit.Test;
 
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/privateinitializer/PrivateInitializerInjectionTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/privateinitializer/PrivateInitializerInjectionTest.java
index 3f8ffb309bde2..42e2b0b1109ef 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/privateinitializer/PrivateInitializerInjectionTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/privateinitializer/PrivateInitializerInjectionTest.java
@@ -18,12 +18,11 @@
 
 import static org.junit.Assert.assertNotNull;
 
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.test.ArcTestContainer;
 import javax.enterprise.context.ApplicationScoped;
 import javax.enterprise.context.Dependent;
 import javax.inject.Inject;
-
-import io.quarkus.arc.Arc;
-import io.quarkus.arc.test.ArcTestContainer;
 import org.junit.Rule;
 import org.junit.Test;
 
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/resource/ResourceInjectionTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/resource/ResourceInjectionTest.java
index f974b029dd483..90e77ce603b56 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/resource/ResourceInjectionTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/resource/ResourceInjectionTest.java
@@ -21,6 +21,10 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.InstanceHandle;
+import io.quarkus.arc.ResourceReferenceProvider;
+import io.quarkus.arc.test.ArcTestContainer;
 import java.lang.annotation.Annotation;
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
@@ -31,7 +35,6 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
-
 import javax.enterprise.context.Dependent;
 import javax.enterprise.inject.Produces;
 import javax.inject.Inject;
@@ -51,11 +54,6 @@
 import javax.persistence.criteria.CriteriaQuery;
 import javax.persistence.criteria.CriteriaUpdate;
 import javax.persistence.metamodel.Metamodel;
-
-import io.quarkus.arc.Arc;
-import io.quarkus.arc.InstanceHandle;
-import io.quarkus.arc.ResourceReferenceProvider;
-import io.quarkus.arc.test.ArcTestContainer;
 import org.junit.Rule;
 import org.junit.Test;
 
@@ -63,7 +61,8 @@ public class ResourceInjectionTest {
 
     @Rule
     public ArcTestContainer container = ArcTestContainer.builder().beanClasses(EEResourceField.class, JpaClient.class)
-            .resourceReferenceProviders(EntityManagerProvider.class, DummyProvider.class).resourceAnnotations(PersistenceContext.class, Dummy.class).build();
+            .resourceReferenceProviders(EntityManagerProvider.class, DummyProvider.class)
+            .resourceAnnotations(PersistenceContext.class, Dummy.class).build();
 
     @Test
     public void testInjection() {
@@ -261,7 +260,8 @@ public void flush() {
                     }
 
                     @Override
-                    public  T find(Class entityClass, Object primaryKey, LockModeType lockMode, Map properties) {
+                    public  T find(Class entityClass, Object primaryKey, LockModeType lockMode,
+                            Map properties) {
                         return null;
                     }
 
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/superclass/SuperclassInjectionTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/superclass/SuperclassInjectionTest.java
index a6efd7703afbe..cf3226f1f3d41 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/superclass/SuperclassInjectionTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/superclass/SuperclassInjectionTest.java
@@ -20,19 +20,17 @@
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.test.ArcTestContainer;
+import io.quarkus.arc.test.injection.superclass.foo.FooHarvester;
 import java.util.HashSet;
 import java.util.Set;
 import java.util.UUID;
-
 import javax.annotation.PostConstruct;
 import javax.enterprise.context.ApplicationScoped;
 import javax.enterprise.context.Dependent;
 import javax.inject.Inject;
 import javax.inject.Singleton;
-
-import io.quarkus.arc.Arc;
-import io.quarkus.arc.test.ArcTestContainer;
-import io.quarkus.arc.test.injection.superclass.foo.FooHarvester;
 import org.junit.Rule;
 import org.junit.Test;
 
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/superclass/foo/FooHarvester.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/superclass/foo/FooHarvester.java
index a08172b91321a..45afbd135051d 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/superclass/foo/FooHarvester.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/injection/superclass/foo/FooHarvester.java
@@ -16,10 +16,9 @@
 
 package io.quarkus.arc.test.injection.superclass.foo;
 
-import javax.inject.Inject;
-
 import io.quarkus.arc.test.injection.superclass.SuperclassInjectionTest.Head;
 import io.quarkus.arc.test.injection.superclass.SuperclassInjectionTest.SuperHarvester;
+import javax.inject.Inject;
 
 public abstract class FooHarvester extends SuperHarvester {
 
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/instance/destroy/InstanceDestroyTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/instance/destroy/InstanceDestroyTest.java
index 38d4fabc4b87d..0f0d85c0ad5c8 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/instance/destroy/InstanceDestroyTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/instance/destroy/InstanceDestroyTest.java
@@ -19,16 +19,14 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.test.ArcTestContainer;
 import java.util.concurrent.atomic.AtomicBoolean;
-
 import javax.annotation.PreDestroy;
 import javax.enterprise.context.Dependent;
 import javax.enterprise.inject.Instance;
 import javax.inject.Inject;
 import javax.inject.Singleton;
-
-import io.quarkus.arc.Arc;
-import io.quarkus.arc.test.ArcTestContainer;
 import org.junit.Rule;
 import org.junit.Test;
 
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/instance/frombean/InstanceFromBeanTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/instance/frombean/InstanceFromBeanTest.java
index 001f3b6713d29..a377af0b46003 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/instance/frombean/InstanceFromBeanTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/instance/frombean/InstanceFromBeanTest.java
@@ -18,14 +18,12 @@
 
 import static org.junit.Assert.assertEquals;
 
-import java.util.UUID;
-
-import javax.annotation.PostConstruct;
-import javax.inject.Singleton;
-
 import io.quarkus.arc.Arc;
 import io.quarkus.arc.InjectableBean;
 import io.quarkus.arc.test.ArcTestContainer;
+import java.util.UUID;
+import javax.annotation.PostConstruct;
+import javax.inject.Singleton;
 import org.junit.Rule;
 import org.junit.Test;
 
@@ -37,7 +35,8 @@ public class InstanceFromBeanTest {
     @SuppressWarnings("unchecked")
     @Test
     public void testDestroy() {
-        InjectableBean bean1 = (InjectableBean) Arc.container().beanManager().getBeans(Alpha.class).iterator().next();
+        InjectableBean bean1 = (InjectableBean) Arc.container().beanManager().getBeans(Alpha.class).iterator()
+                .next();
         InjectableBean bean2 = Arc.container().bean(bean1.getIdentifier());
         assertEquals(bean1, bean2);
         assertEquals(Arc.container().instance(bean2).get().getId(), Arc.container().instance(bean2).get().getId());
@@ -47,7 +46,7 @@ public void testDestroy() {
     static class Alpha {
 
         private String id;
-        
+
         @PostConstruct
         void init() {
             this.id = UUID.randomUUID().toString();
@@ -59,5 +58,4 @@ String getId() {
 
     }
 
-
 }
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/Counter.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/Counter.java
index 0ce8fca7dbaa4..b46dbaa90384d 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/Counter.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/Counter.java
@@ -17,7 +17,6 @@
 package io.quarkus.arc.test.interceptors;
 
 import java.util.concurrent.atomic.AtomicInteger;
-
 import javax.inject.Singleton;
 
 @Singleton
@@ -26,7 +25,7 @@ public class Counter {
     private AtomicInteger counter = new AtomicInteger();
 
     int incrementAndGet() {
-       return counter.incrementAndGet();
+        return counter.incrementAndGet();
     }
 
     void reset() {
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/Lifecycle.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/Lifecycle.java
index f6976f1ef781d..ae7cc47b68154 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/Lifecycle.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/Lifecycle.java
@@ -23,7 +23,6 @@
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
-
 import javax.interceptor.InterceptorBinding;
 
 @Target({ TYPE, METHOD })
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/LifecycleInterceptor.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/LifecycleInterceptor.java
index e979224c11c2d..502c393b2273f 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/LifecycleInterceptor.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/LifecycleInterceptor.java
@@ -16,9 +16,9 @@
 
 package io.quarkus.arc.test.interceptors;
 
+import io.quarkus.arc.InvocationContextImpl;
 import java.util.List;
 import java.util.concurrent.CopyOnWriteArrayList;
-
 import javax.annotation.PostConstruct;
 import javax.annotation.PreDestroy;
 import javax.annotation.Priority;
@@ -26,8 +26,6 @@
 import javax.interceptor.Interceptor;
 import javax.interceptor.InvocationContext;
 
-import io.quarkus.arc.InvocationContextImpl;
-
 @Lifecycle
 @Priority(1)
 @Interceptor
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/Logging.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/Logging.java
index 43ee46c6abfc2..4ffb6d99a530e 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/Logging.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/Logging.java
@@ -23,7 +23,6 @@
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
-
 import javax.interceptor.InterceptorBinding;
 
 @Target({ TYPE, METHOD })
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/LoggingInterceptor.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/LoggingInterceptor.java
index a526382ab749e..83ae9db9b6abf 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/LoggingInterceptor.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/LoggingInterceptor.java
@@ -17,7 +17,6 @@
 package io.quarkus.arc.test.interceptors;
 
 import java.util.concurrent.atomic.AtomicReference;
-
 import javax.annotation.Priority;
 import javax.interceptor.AroundInvoke;
 import javax.interceptor.Interceptor;
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/Simple.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/Simple.java
index 025eb4394e8a0..af301747c986e 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/Simple.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/Simple.java
@@ -23,7 +23,6 @@
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
-
 import javax.interceptor.InterceptorBinding;
 
 @Target({ TYPE, METHOD })
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/SimpleInterceptor.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/SimpleInterceptor.java
index 9a3bfc48d0d73..4ff424336b333 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/SimpleInterceptor.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/SimpleInterceptor.java
@@ -16,14 +16,13 @@
 
 package io.quarkus.arc.test.interceptors;
 
+import io.quarkus.arc.InvocationContextImpl;
 import javax.annotation.Priority;
 import javax.inject.Inject;
 import javax.interceptor.AroundInvoke;
 import javax.interceptor.Interceptor;
 import javax.interceptor.InvocationContext;
 
-import io.quarkus.arc.InvocationContextImpl;
-
 @Simple
 @Priority(1)
 @Interceptor
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/SimpleInterceptorTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/SimpleInterceptorTest.java
index e7b981de65add..52b09d62b4fb7 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/SimpleInterceptorTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/SimpleInterceptorTest.java
@@ -29,7 +29,8 @@
 public class SimpleInterceptorTest {
 
     @Rule
-    public ArcTestContainer container = new ArcTestContainer(Counter.class, SimpleBean.class, Simple.class, SimpleInterceptor.class, Logging.class,
+    public ArcTestContainer container = new ArcTestContainer(Counter.class, SimpleBean.class, Simple.class,
+            SimpleInterceptor.class, Logging.class,
             LoggingInterceptor.class, Lifecycle.class, LifecycleInterceptor.class);
 
     @Test
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/aroundconstruct/AroundConstructReturningObjectTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/aroundconstruct/AroundConstructReturningObjectTest.java
new file mode 100644
index 0000000000000..ae31437f3c264
--- /dev/null
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/aroundconstruct/AroundConstructReturningObjectTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2018 Red Hat, Inc.
+ *
+ * Licensed 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.arc.test.interceptors.aroundconstruct;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.ArcContainer;
+import io.quarkus.arc.test.ArcTestContainer;
+import java.util.concurrent.atomic.AtomicBoolean;
+import javax.inject.Singleton;
+import javax.interceptor.AroundConstruct;
+import javax.interceptor.Interceptor;
+import javax.interceptor.InvocationContext;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class AroundConstructReturningObjectTest {
+
+    @Rule
+    public ArcTestContainer container = new ArcTestContainer(MyTransactional.class, SimpleBean.class,
+            SimpleInterceptor.class);
+
+    public static AtomicBoolean INTERCEPTOR_CALLED = new AtomicBoolean(false);
+
+    @Test
+    public void testInterception() {
+        SimpleBean simpleBean = Arc.container().instance(SimpleBean.class).get();
+        assertNotNull(simpleBean);
+        assertTrue(INTERCEPTOR_CALLED.get());
+    }
+
+    @Singleton
+    @MyTransactional
+    static class SimpleBean {
+
+    }
+
+    @MyTransactional
+    @Interceptor
+    public static class SimpleInterceptor {
+
+        @AroundConstruct
+        Object mySuperCoolAroundConstruct(InvocationContext ctx) throws Exception {
+            INTERCEPTOR_CALLED.set(true);
+            return ctx.proceed();
+        }
+
+    }
+}
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/aroundconstruct/AroundConstructTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/aroundconstruct/AroundConstructTest.java
new file mode 100644
index 0000000000000..8fa1ff8e49674
--- /dev/null
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/aroundconstruct/AroundConstructTest.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2018 Red Hat, Inc.
+ *
+ * Licensed 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.arc.test.interceptors.aroundconstruct;
+
+import static org.junit.Assert.assertNotNull;
+
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.ArcContainer;
+import io.quarkus.arc.test.ArcTestContainer;
+import java.util.concurrent.atomic.AtomicBoolean;
+import javax.inject.Singleton;
+import javax.interceptor.AroundConstruct;
+import javax.interceptor.Interceptor;
+import javax.interceptor.InvocationContext;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class AroundConstructTest {
+
+    @Rule
+    public ArcTestContainer container = new ArcTestContainer(MyTransactional.class, SimpleBean.class,
+            SimpleInterceptor.class);
+
+    public static AtomicBoolean INTERCEPTOR_CALLED = new AtomicBoolean(false);
+
+    @Test
+    public void testInterception() {
+        SimpleBean simpleBean = Arc.container().instance(SimpleBean.class).get();
+        assertNotNull(simpleBean);
+        Assert.assertTrue(INTERCEPTOR_CALLED.get());
+    }
+
+    @Singleton
+    @MyTransactional
+    static class SimpleBean {
+
+    }
+
+    @MyTransactional
+    @Interceptor
+    public static class SimpleInterceptor {
+
+        @AroundConstruct
+        void mySuperCoolAroundConstruct(InvocationContext ctx) throws Exception {
+            INTERCEPTOR_CALLED.set(true);
+            ctx.proceed();
+        }
+
+    }
+}
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/aroundconstruct/MyTransactional.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/aroundconstruct/MyTransactional.java
new file mode 100644
index 0000000000000..971174bf26b7a
--- /dev/null
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/aroundconstruct/MyTransactional.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2018 Red Hat, Inc.
+ *
+ * Licensed 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.arc.test.interceptors.aroundconstruct;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import javax.interceptor.InterceptorBinding;
+
+@Target({ TYPE })
+@Retention(RUNTIME)
+@InterceptorBinding
+public @interface MyTransactional {
+
+}
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindingdefaultvalue/BindingDefaultValueTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindingdefaultvalue/BindingDefaultValueTest.java
index 84c08e69e3a7e..a60e74b3e169c 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindingdefaultvalue/BindingDefaultValueTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindingdefaultvalue/BindingDefaultValueTest.java
@@ -18,22 +18,22 @@
 
 import static org.junit.Assert.assertEquals;
 
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.ArcContainer;
+import io.quarkus.arc.test.ArcTestContainer;
 import javax.annotation.Priority;
 import javax.inject.Singleton;
 import javax.interceptor.AroundInvoke;
 import javax.interceptor.Interceptor;
 import javax.interceptor.InvocationContext;
-
-import io.quarkus.arc.Arc;
-import io.quarkus.arc.ArcContainer;
-import io.quarkus.arc.test.ArcTestContainer;
 import org.junit.Rule;
 import org.junit.Test;
 
 public class BindingDefaultValueTest {
 
     @Rule
-    public ArcTestContainer container = new ArcTestContainer(MyTransactional.class, SimpleBean.class, AlphaInterceptor.class, BravoInterceptor.class);
+    public ArcTestContainer container = new ArcTestContainer(MyTransactional.class, SimpleBean.class, AlphaInterceptor.class,
+            BravoInterceptor.class);
 
     @Test
     public void testInterception() {
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindingdefaultvalue/MyTransactional.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindingdefaultvalue/MyTransactional.java
index 1f299bd45d562..eeeac4536beb0 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindingdefaultvalue/MyTransactional.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindingdefaultvalue/MyTransactional.java
@@ -23,7 +23,6 @@
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
-
 import javax.interceptor.InterceptorBinding;
 
 @Target({ TYPE, METHOD })
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindings/InvocationContextBindingsTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindings/InvocationContextBindingsTest.java
index 8df6fcfec3af0..f96e18d670cb0 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindings/InvocationContextBindingsTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindings/InvocationContextBindingsTest.java
@@ -18,24 +18,24 @@
 
 import static org.junit.Assert.assertTrue;
 
-import javax.annotation.Priority;
-import javax.inject.Singleton;
-import javax.interceptor.AroundInvoke;
-import javax.interceptor.Interceptor;
-import javax.interceptor.InvocationContext;
-
 import io.quarkus.arc.Arc;
 import io.quarkus.arc.ArcContainer;
 import io.quarkus.arc.InvocationContextImpl;
 import io.quarkus.arc.test.ArcTestContainer;
 import io.quarkus.arc.test.interceptors.Simple;
+import javax.annotation.Priority;
+import javax.inject.Singleton;
+import javax.interceptor.AroundInvoke;
+import javax.interceptor.Interceptor;
+import javax.interceptor.InvocationContext;
 import org.junit.Rule;
 import org.junit.Test;
 
 public class InvocationContextBindingsTest {
 
     @Rule
-    public ArcTestContainer container = new ArcTestContainer(Simple.class, MyTransactional.class, SimpleBean.class, SimpleInterceptor.class);
+    public ArcTestContainer container = new ArcTestContainer(Simple.class, MyTransactional.class, SimpleBean.class,
+            SimpleInterceptor.class);
 
     @Test
     public void testInterception() {
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindings/MyTransactional.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindings/MyTransactional.java
index 1e4819d713491..cea1d685d78c5 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindings/MyTransactional.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindings/MyTransactional.java
@@ -23,7 +23,6 @@
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
-
 import javax.enterprise.util.Nonbinding;
 import javax.interceptor.InterceptorBinding;
 
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/exceptionhandling/ExceptionHandlingBean.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/exceptionhandling/ExceptionHandlingBean.java
index 2388a990d3fc7..1a3d2088e2982 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/exceptionhandling/ExceptionHandlingBean.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/exceptionhandling/ExceptionHandlingBean.java
@@ -27,13 +27,13 @@ public ExceptionHandlingBean() {
     @ExceptionHandlingInterceptorBinding
     void foo(ExceptionHandlingCase exceptionHandlingCase) throws MyDeclaredException {
         switch (exceptionHandlingCase) {
-        case DECLARED_EXCEPTION:
-            throw new MyDeclaredException();
-        case RUNTIME_EXCEPTION:
-            throw new MyRuntimeException();
-        case OTHER_EXCEPTIONS:
-            // this case should be handled by the interceptor
-            break;
+            case DECLARED_EXCEPTION:
+                throw new MyDeclaredException();
+            case RUNTIME_EXCEPTION:
+                throw new MyRuntimeException();
+            case OTHER_EXCEPTIONS:
+                // this case should be handled by the interceptor
+                break;
         }
     }
 
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/exceptionhandling/ExceptionHandlingInterceptorBinding.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/exceptionhandling/ExceptionHandlingInterceptorBinding.java
index a3af1d368f569..b1a4f9273007b 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/exceptionhandling/ExceptionHandlingInterceptorBinding.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/exceptionhandling/ExceptionHandlingInterceptorBinding.java
@@ -23,7 +23,6 @@
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
-
 import javax.interceptor.InterceptorBinding;
 
 @Target({ TYPE, METHOD })
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/privatemethod/PrivateInterceptorMethodTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/privatemethod/PrivateInterceptorMethodTest.java
index 5418388d48d41..1dabf8e19f8df 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/privatemethod/PrivateInterceptorMethodTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/privatemethod/PrivateInterceptorMethodTest.java
@@ -18,16 +18,15 @@
 
 import static org.junit.Assert.assertEquals;
 
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.ArcContainer;
+import io.quarkus.arc.test.ArcTestContainer;
+import io.quarkus.arc.test.interceptors.Simple;
 import javax.annotation.Priority;
 import javax.inject.Singleton;
 import javax.interceptor.AroundInvoke;
 import javax.interceptor.Interceptor;
 import javax.interceptor.InvocationContext;
-
-import io.quarkus.arc.Arc;
-import io.quarkus.arc.ArcContainer;
-import io.quarkus.arc.test.ArcTestContainer;
-import io.quarkus.arc.test.interceptors.Simple;
 import org.junit.Rule;
 import org.junit.Test;
 
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/metadata/BeanMetadataTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/metadata/BeanMetadataTest.java
index bcf14b9fff023..2fc9f2c1f1f68 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/metadata/BeanMetadataTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/metadata/BeanMetadataTest.java
@@ -16,12 +16,11 @@
 
 package io.quarkus.arc.test.metadata;
 
-import javax.enterprise.util.AnnotationLiteral;
-import javax.inject.Qualifier;
-
 import io.quarkus.arc.Arc;
 import io.quarkus.arc.ArcContainer;
 import io.quarkus.arc.test.ArcTestContainer;
+import javax.enterprise.util.AnnotationLiteral;
+import javax.inject.Qualifier;
 import org.junit.Assert;
 import org.junit.Rule;
 import org.junit.Test;
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/metadata/InjectionPointMetadataTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/metadata/InjectionPointMetadataTest.java
index 8ae1d199f1ff4..ab60da7613ea9 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/metadata/InjectionPointMetadataTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/metadata/InjectionPointMetadataTest.java
@@ -20,33 +20,32 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.ArcContainer;
+import io.quarkus.arc.test.ArcTestContainer;
 import java.lang.annotation.Annotation;
 import java.util.Set;
-
 import javax.enterprise.context.Dependent;
 import javax.enterprise.inject.Default;
+import javax.enterprise.inject.Instance;
 import javax.enterprise.inject.spi.AnnotatedConstructor;
 import javax.enterprise.inject.spi.AnnotatedField;
 import javax.enterprise.inject.spi.AnnotatedParameter;
 import javax.enterprise.inject.spi.Bean;
 import javax.enterprise.inject.spi.BeanManager;
 import javax.enterprise.inject.spi.InjectionPoint;
+import javax.enterprise.util.TypeLiteral;
 import javax.inject.Inject;
 import javax.inject.Singleton;
-
 import org.junit.Rule;
 import org.junit.Test;
 
-import io.quarkus.arc.Arc;
-import io.quarkus.arc.ArcContainer;
-import io.quarkus.arc.test.ArcTestContainer;
-
 public class InjectionPointMetadataTest {
 
     @Rule
     public ArcTestContainer container = new ArcTestContainer(Controller.class, Controlled.class);
 
-    @SuppressWarnings("unchecked")
+    @SuppressWarnings({ "unchecked", "rawtypes", "serial" })
     @Test
     public void testInjectionPointMetadata() {
         ArcContainer arc = Arc.container();
@@ -86,7 +85,7 @@ public void testInjectionPointMetadata() {
         // Constructor
         InjectionPoint ctorInjectionPoint = controller.controlledCtor.injectionPoint;
         assertNotNull(ctorInjectionPoint);
-        assertEquals(Controlled.class, methodInjectionPoint.getType());
+        assertEquals(Controlled.class, ctorInjectionPoint.getType());
         assertTrue(ctorInjectionPoint.getAnnotated() instanceof AnnotatedParameter);
         assertEquals(bean, ctorInjectionPoint.getBean());
         AnnotatedParameter ctorParam = (AnnotatedParameter) ctorInjectionPoint.getAnnotated();
@@ -97,6 +96,27 @@ public void testInjectionPointMetadata() {
         assertEquals(1, ctorParam.getAnnotations().size());
         assertTrue(ctorParam.getDeclaringCallable() instanceof AnnotatedConstructor);
         assertEquals(Controller.class, ctorParam.getDeclaringCallable().getJavaMember().getDeclaringClass());
+
+        // Instance
+        InjectionPoint instanceInjectionPoint = controller.instanceControlled.get().injectionPoint;
+        assertNotNull(instanceInjectionPoint);
+        assertEquals(Controlled.class, instanceInjectionPoint.getType());
+        qualifiers = instanceInjectionPoint.getQualifiers();
+        assertEquals(1, qualifiers.size());
+        assertEquals(Default.class, qualifiers.iterator().next().annotationType());
+        bean = instanceInjectionPoint.getBean();
+        assertNotNull(bean);
+        assertTrue(bean.getTypes().stream().anyMatch(t -> t.equals(Controller.class)));
+        assertNotNull(instanceInjectionPoint.getAnnotated());
+        assertTrue(instanceInjectionPoint.getAnnotated() instanceof AnnotatedField);
+        annotatedField = (AnnotatedField) instanceInjectionPoint.getAnnotated();
+        assertEquals("instanceControlled", annotatedField.getJavaMember().getName());
+        assertEquals(new TypeLiteral>() {
+        }.getType(), annotatedField.getBaseType());
+        assertTrue(annotatedField.isAnnotationPresent(Inject.class));
+        assertTrue(annotatedField.getAnnotation(Singleton.class) == null);
+        assertTrue(annotatedField.getAnnotations(Singleton.class).isEmpty());
+        assertEquals(1, annotatedField.getAnnotations().size());
     }
 
     @Singleton
@@ -109,6 +129,9 @@ static class Controller {
 
         Controlled controlledCtor;
 
+        @Inject
+        Instance instanceControlled;
+
         @Inject
         public Controller(BeanManager beanManager, @Singleton Controlled controlled) {
             this.controlledCtor = controlled;
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/name/AmbiguousNameTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/name/AmbiguousNameTest.java
index c6b67ece28b3a..4fa10b26a9251 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/name/AmbiguousNameTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/name/AmbiguousNameTest.java
@@ -19,12 +19,11 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
+import io.quarkus.arc.test.ArcTestContainer;
 import javax.enterprise.context.Dependent;
 import javax.enterprise.inject.spi.DeploymentException;
 import javax.inject.Named;
 import javax.inject.Singleton;
-
-import io.quarkus.arc.test.ArcTestContainer;
 import org.junit.Rule;
 import org.junit.Test;
 
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/name/NameResolutionTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/name/NameResolutionTest.java
index a97e2e22de061..77bf989715f66 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/name/NameResolutionTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/name/NameResolutionTest.java
@@ -19,13 +19,12 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.test.ArcTestContainer;
 import javax.enterprise.context.Dependent;
 import javax.enterprise.inject.Produces;
 import javax.inject.Named;
 import javax.inject.Singleton;
-
-import io.quarkus.arc.Arc;
-import io.quarkus.arc.test.ArcTestContainer;
 import org.junit.Rule;
 import org.junit.Test;
 
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/ParameterizedPayloadTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/ParameterizedPayloadTest.java
index fe6132322b0f3..589e03330b339 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/ParameterizedPayloadTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/ParameterizedPayloadTest.java
@@ -19,20 +19,18 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.test.ArcTestContainer;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicReference;
-
 import javax.annotation.PostConstruct;
 import javax.enterprise.context.Dependent;
 import javax.enterprise.event.Event;
 import javax.enterprise.event.Observes;
 import javax.inject.Inject;
 import javax.inject.Singleton;
-
-import io.quarkus.arc.Arc;
-import io.quarkus.arc.test.ArcTestContainer;
 import org.junit.Rule;
 import org.junit.Test;
 
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/RuntimeClassTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/RuntimeClassTest.java
index 516f15d1dbbcb..08ca74fdeca07 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/RuntimeClassTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/RuntimeClassTest.java
@@ -18,18 +18,16 @@
 
 import static org.junit.Assert.assertEquals;
 
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.test.ArcTestContainer;
 import java.util.List;
 import java.util.concurrent.CopyOnWriteArrayList;
-
 import javax.annotation.PostConstruct;
 import javax.enterprise.context.Dependent;
 import javax.enterprise.event.Event;
 import javax.enterprise.event.Observes;
 import javax.inject.Inject;
 import javax.inject.Singleton;
-
-import io.quarkus.arc.Arc;
-import io.quarkus.arc.test.ArcTestContainer;
 import org.junit.Rule;
 import org.junit.Test;
 
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/SimpleObserverTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/SimpleObserverTest.java
index 7b5f5855b9f27..ece01a0f3ae2f 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/SimpleObserverTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/SimpleObserverTest.java
@@ -18,18 +18,16 @@
 
 import static org.junit.Assert.assertEquals;
 
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.test.ArcTestContainer;
 import java.util.List;
 import java.util.concurrent.CopyOnWriteArrayList;
-
 import javax.annotation.PostConstruct;
 import javax.enterprise.context.Dependent;
 import javax.enterprise.event.Event;
 import javax.enterprise.event.Observes;
 import javax.inject.Inject;
 import javax.inject.Singleton;
-
-import io.quarkus.arc.Arc;
-import io.quarkus.arc.test.ArcTestContainer;
 import org.junit.Rule;
 import org.junit.Test;
 
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/async/AsyncObserverExceptionTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/async/AsyncObserverExceptionTest.java
index b359a8e9be395..eae403312345f 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/async/AsyncObserverExceptionTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/async/AsyncObserverExceptionTest.java
@@ -20,6 +20,9 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.ArcContainer;
+import io.quarkus.arc.test.ArcTestContainer;
 import java.util.List;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.CompletionStage;
@@ -28,7 +31,6 @@
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
-
 import javax.annotation.PostConstruct;
 import javax.annotation.Priority;
 import javax.enterprise.context.Dependent;
@@ -36,10 +38,6 @@
 import javax.enterprise.event.ObservesAsync;
 import javax.inject.Inject;
 import javax.inject.Singleton;
-
-import io.quarkus.arc.Arc;
-import io.quarkus.arc.ArcContainer;
-import io.quarkus.arc.test.ArcTestContainer;
 import org.junit.Rule;
 import org.junit.Test;
 
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/async/AsyncObserverTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/async/AsyncObserverTest.java
index 190b921e8df60..60266ea23ad5a 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/async/AsyncObserverTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/async/AsyncObserverTest.java
@@ -20,13 +20,15 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.ArcContainer;
+import io.quarkus.arc.test.ArcTestContainer;
 import java.util.List;
 import java.util.concurrent.CompletionStage;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
-
 import javax.annotation.PostConstruct;
 import javax.enterprise.context.Dependent;
 import javax.enterprise.context.RequestScoped;
@@ -35,17 +37,14 @@
 import javax.enterprise.event.ObservesAsync;
 import javax.inject.Inject;
 import javax.inject.Singleton;
-
-import io.quarkus.arc.Arc;
-import io.quarkus.arc.ArcContainer;
-import io.quarkus.arc.test.ArcTestContainer;
 import org.junit.Rule;
 import org.junit.Test;
 
 public class AsyncObserverTest {
 
     @Rule
-    public ArcTestContainer container = new ArcTestContainer(StringProducer.class, StringObserver.class, ThreadNameProvider.class);
+    public ArcTestContainer container = new ArcTestContainer(StringProducer.class, StringObserver.class,
+            ThreadNameProvider.class);
 
     @Test
     public void testAsyncObservers() throws InterruptedException, ExecutionException, TimeoutException {
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/discovery/ObserverOnClassWithoutBeanDefiningAnnotationTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/discovery/ObserverOnClassWithoutBeanDefiningAnnotationTest.java
index d4b755d95bb2c..096cea0558383 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/discovery/ObserverOnClassWithoutBeanDefiningAnnotationTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/discovery/ObserverOnClassWithoutBeanDefiningAnnotationTest.java
@@ -18,14 +18,12 @@
 
 import static org.junit.Assert.assertEquals;
 
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.test.ArcTestContainer;
 import java.util.List;
 import java.util.concurrent.CopyOnWriteArrayList;
-
 import javax.enterprise.event.Observes;
 import javax.enterprise.inject.spi.BeanManager;
-
-import io.quarkus.arc.Arc;
-import io.quarkus.arc.test.ArcTestContainer;
 import org.junit.Rule;
 import org.junit.Test;
 
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/injection/SimpleObserverInjectionTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/injection/SimpleObserverInjectionTest.java
index abff53ac3c513..8889d024299e1 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/injection/SimpleObserverInjectionTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/injection/SimpleObserverInjectionTest.java
@@ -20,18 +20,16 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
+import io.quarkus.arc.Arc;
+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.Dependent;
 import javax.enterprise.event.Observes;
 import javax.inject.Singleton;
-
-import io.quarkus.arc.Arc;
-import io.quarkus.arc.test.ArcTestContainer;
 import org.junit.Rule;
 import org.junit.Test;
 
@@ -83,4 +81,3 @@ void destroy() {
     }
 
 }
-
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/metadata/EventMetadataTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/metadata/EventMetadataTest.java
new file mode 100644
index 0000000000000..e9bb219c1eb53
--- /dev/null
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/metadata/EventMetadataTest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2018 Red Hat, Inc.
+ *
+ * Licensed 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.arc.test.observers.metadata;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.test.ArcTestContainer;
+import java.math.BigDecimal;
+import java.util.concurrent.atomic.AtomicReference;
+import javax.enterprise.event.Observes;
+import javax.enterprise.inject.Any;
+import javax.enterprise.inject.spi.EventMetadata;
+import javax.inject.Singleton;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class EventMetadataTest {
+
+    @Rule
+    public ArcTestContainer container = new ArcTestContainer(BigDecimalObserver.class);
+
+    @Test
+    public void testMetadata() {
+        Arc.container().beanManager().getEvent().fire(BigDecimal.ONE);
+        EventMetadata metadata = BigDecimalObserver.METADATA.get();
+        assertNotNull(metadata);
+        assertEquals(1, metadata.getQualifiers().size());
+        assertEquals(Any.class, metadata.getQualifiers().iterator().next().annotationType());
+        assertEquals(BigDecimal.class, metadata.getType());
+    }
+
+    @Singleton
+    static class BigDecimalObserver {
+
+        static final AtomicReference METADATA = new AtomicReference();
+
+        void observe(@Observes BigDecimal value, EventMetadata metadata) {
+            METADATA.set(metadata);
+        }
+
+    }
+
+}
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/metadata/EventMetadataWrongInjectionTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/metadata/EventMetadataWrongInjectionTest.java
new file mode 100644
index 0000000000000..4e7cadfe81861
--- /dev/null
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/metadata/EventMetadataWrongInjectionTest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2018 Red Hat, Inc.
+ *
+ * Licensed 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.arc.test.observers.metadata;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import io.quarkus.arc.test.ArcTestContainer;
+import javax.enterprise.inject.spi.DefinitionException;
+import javax.enterprise.inject.spi.DeploymentException;
+import javax.enterprise.inject.spi.EventMetadata;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class EventMetadataWrongInjectionTest {
+
+    @Rule
+    public ArcTestContainer container = ArcTestContainer.builder().beanClasses(WrongBean.class).shouldFail().build();
+
+    @Test
+    public void testMetadata() {
+        Throwable error = container.getFailure();
+        assertNotNull(error);
+        assertTrue(error instanceof DeploymentException);
+        assertNotNull(error.getCause());
+        assertTrue(error.getCause() instanceof DefinitionException);
+    }
+
+    @Singleton
+    static class WrongBean {
+
+        @Inject
+        EventMetadata metadata;
+
+    }
+
+}
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/ordering/ObserverOrderingTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/ordering/ObserverOrderingTest.java
index 32d08434defb9..21139f6651fd5 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/ordering/ObserverOrderingTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/observers/ordering/ObserverOrderingTest.java
@@ -18,9 +18,11 @@
 
 import static org.junit.Assert.assertEquals;
 
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.ArcContainer;
+import io.quarkus.arc.test.ArcTestContainer;
 import java.util.List;
 import java.util.concurrent.CopyOnWriteArrayList;
-
 import javax.annotation.PostConstruct;
 import javax.annotation.Priority;
 import javax.enterprise.context.Dependent;
@@ -28,10 +30,6 @@
 import javax.enterprise.event.Observes;
 import javax.inject.Inject;
 import javax.inject.Singleton;
-
-import io.quarkus.arc.Arc;
-import io.quarkus.arc.ArcContainer;
-import io.quarkus.arc.test.ArcTestContainer;
 import org.junit.Rule;
 import org.junit.Test;
 
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/async/AsyncProducerTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/async/AsyncProducerTest.java
index 36ffd3a85b5da..a94b6b53bcc4f 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/async/AsyncProducerTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/async/AsyncProducerTest.java
@@ -19,18 +19,16 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.test.ArcTestContainer;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CompletionStage;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.atomic.AtomicReference;
-
 import javax.enterprise.context.Dependent;
 import javax.enterprise.inject.Produces;
 import javax.inject.Inject;
 import javax.inject.Singleton;
-
-import io.quarkus.arc.Arc;
-import io.quarkus.arc.test.ArcTestContainer;
 import org.junit.Rule;
 import org.junit.Test;
 
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/dependent/DeclaringBeanTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/dependent/DeclaringBeanTest.java
index c7d3a90ebe67b..b7c9759d92fc7 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/dependent/DeclaringBeanTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/dependent/DeclaringBeanTest.java
@@ -21,18 +21,16 @@
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.test.ArcTestContainer;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
-
 import javax.annotation.PreDestroy;
 import javax.enterprise.context.Dependent;
 import javax.enterprise.inject.Produces;
 import javax.enterprise.util.TypeLiteral;
 import javax.inject.Singleton;
-
-import io.quarkus.arc.Arc;
-import io.quarkus.arc.test.ArcTestContainer;
 import org.junit.Rule;
 import org.junit.Test;
 
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/discovery/ProducerOnClassWithoutBeanDefiningAnnotationTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/discovery/ProducerOnClassWithoutBeanDefiningAnnotationTest.java
index 87732815cd867..f29d7e4131519 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/discovery/ProducerOnClassWithoutBeanDefiningAnnotationTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/discovery/ProducerOnClassWithoutBeanDefiningAnnotationTest.java
@@ -18,10 +18,9 @@
 
 import static org.junit.Assert.assertEquals;
 
-import javax.enterprise.inject.Produces;
-
 import io.quarkus.arc.Arc;
 import io.quarkus.arc.test.ArcTestContainer;
+import javax.enterprise.inject.Produces;
 import org.junit.Rule;
 import org.junit.Test;
 
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/disposer/DisposerTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/disposer/DisposerTest.java
index 5d59dad5298e1..d42aa827f6306 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/disposer/DisposerTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/disposer/DisposerTest.java
@@ -19,10 +19,13 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.InstanceHandle;
+import io.quarkus.arc.test.ArcTestContainer;
+import io.quarkus.arc.test.MyQualifier;
 import java.math.BigDecimal;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
-
 import javax.annotation.PreDestroy;
 import javax.enterprise.context.ApplicationScoped;
 import javax.enterprise.context.Dependent;
@@ -30,11 +33,6 @@
 import javax.enterprise.inject.Produces;
 import javax.enterprise.util.TypeLiteral;
 import javax.inject.Singleton;
-
-import io.quarkus.arc.Arc;
-import io.quarkus.arc.InstanceHandle;
-import io.quarkus.arc.test.ArcTestContainer;
-import io.quarkus.arc.test.MyQualifier;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.RuleChain;
@@ -58,7 +56,7 @@ public void evaluate() throws Throwable {
                 }
             };
         }
-    }).around(new ArcTestContainer(StringProducer.class, LongProducer.class, BigDecimalProducer.class));
+    }).around(new ArcTestContainer(StringProducer.class, LongProducer.class, BigDecimalProducer.class, MyQualifier.class));
 
     @Test
     public void testDisposers() {
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/generic/Claim.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/generic/Claim.java
index b4c7b1cd702bd..265eb339397aa 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/generic/Claim.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/generic/Claim.java
@@ -1,20 +1,21 @@
 package io.quarkus.arc.test.producer.generic;
 
-import javax.enterprise.util.Nonbinding;
-import javax.inject.Qualifier;
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
+import javax.enterprise.util.Nonbinding;
+import javax.inject.Qualifier;
 
 @Qualifier
 @Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE})
+@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE })
 public @interface Claim {
     /**
      * The value specifies the id name the claim to inject
+     * 
      * @return the claim name
      */
     @Nonbinding
     String value() default "";
-}
\ No newline at end of file
+}
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/generic/ErasedGenericTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/generic/ErasedGenericTest.java
index 54abfc645e06c..cccf536b15ac0 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/generic/ErasedGenericTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/generic/ErasedGenericTest.java
@@ -1,21 +1,20 @@
 package io.quarkus.arc.test.producer.generic;
 
+import static org.junit.Assert.assertEquals;
+
 import io.quarkus.arc.Arc;
 import io.quarkus.arc.ArcContainer;
 import io.quarkus.arc.test.ArcTestContainer;
-import org.junit.Rule;
-import org.junit.Test;
-
+import java.lang.annotation.Annotation;
+import java.util.Optional;
 import javax.enterprise.context.Dependent;
 import javax.enterprise.inject.Produces;
 import javax.enterprise.inject.spi.InjectionPoint;
 import javax.inject.Inject;
 import javax.inject.Named;
 import javax.inject.Singleton;
-import java.lang.annotation.Annotation;
-import java.util.Optional;
-
-import static org.junit.Assert.assertEquals;
+import org.junit.Rule;
+import org.junit.Test;
 
 /**
  * Test for https://github.com/quarkus-project/quarkus/issues/120
@@ -41,6 +40,7 @@ public String getSomething() {
             return optionalString.get();
         }
     }
+
     @Dependent
     static class ErasedTypeProducer {
         @Produces
@@ -50,6 +50,7 @@ public Optional getStringValue(InjectionPoint ip) {
             Optional value = Optional.of(name);
             return value;
         }
+
         @Produces
         @Claim("")
         @Named("RawClaimTypeProducer#getOptionalValue")
@@ -58,6 +59,7 @@ public Optional getOptionalValue(InjectionPoint ip) {
             Optional value = Optional.of(name);
             return value;
         }
+
         String getName(InjectionPoint ip) {
             String name = null;
             for (Annotation ann : ip.getQualifiers()) {
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/primitive/PrimitiveProducerTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/primitive/PrimitiveProducerTest.java
index 32f12fa100502..bfccee507bdba 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/primitive/PrimitiveProducerTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/primitive/PrimitiveProducerTest.java
@@ -18,28 +18,30 @@
 
 import static org.junit.Assert.assertEquals;
 
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.test.ArcTestContainer;
 import javax.enterprise.context.Dependent;
 import javax.enterprise.inject.Produces;
 import javax.inject.Inject;
 import javax.inject.Singleton;
-
-import io.quarkus.arc.Arc;
-import io.quarkus.arc.test.ArcTestContainer;
 import org.junit.Rule;
 import org.junit.Test;
 
 public class PrimitiveProducerTest {
 
     @Rule
-    public ArcTestContainer container = new ArcTestContainer(IntProducer.class, LongProducer.class, PrimitiveConsumer.class);
+    public ArcTestContainer container = new ArcTestContainer(IntProducer.class, LongProducer.class, StringArrayProducer.class,
+            PrimitiveConsumer.class);
 
     @Test
     public void testPrimitiveProducers() {
         assertEquals(Long.valueOf(10), Arc.container().instance(Long.class).get());
         assertEquals(Integer.valueOf(10), Arc.container().instance(Integer.class).get());
         PrimitiveConsumer consumer = Arc.container().instance(PrimitiveConsumer.class).get();
-        assertEquals(10, consumer.getIntFoo());
-        assertEquals(10l, consumer.getLongFoo());
+        assertEquals(10, consumer.intFoo);
+        assertEquals(10l, consumer.longFoo);
+        assertEquals(2, consumer.strings.length);
+        assertEquals("foo", consumer.strings[0]);
     }
 
     @Dependent
@@ -59,23 +61,26 @@ long foo() {
         }
 
     }
-    
+
+    @Dependent
+    static class StringArrayProducer {
+
+        @Produces
+        String[] strings = { "foo", "bar" };
+
+    }
+
     @Singleton
     static class PrimitiveConsumer {
-        
+
         @Inject
         int intFoo;
-        
+
         @Inject
         long longFoo;
 
-        int getIntFoo() {
-            return intFoo;
-        }
+        @Inject
+        String[] strings;
 
-        long getLongFoo() {
-            return longFoo;
-        }
-        
     }
 }
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/privatemember/PrivateProducerFieldTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/privatemember/PrivateProducerFieldTest.java
index 2657f6df71b66..13a2929ae1b69 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/privatemember/PrivateProducerFieldTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/privatemember/PrivateProducerFieldTest.java
@@ -18,11 +18,10 @@
 
 import static org.junit.Assert.assertEquals;
 
-import javax.enterprise.context.ApplicationScoped;
-import javax.enterprise.inject.Produces;
-
 import io.quarkus.arc.Arc;
 import io.quarkus.arc.test.ArcTestContainer;
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.inject.Produces;
 import org.junit.Rule;
 import org.junit.Test;
 
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/privatemember/PrivateProducerMethodTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/privatemember/PrivateProducerMethodTest.java
index 8d56d14148538..73e896b2cc1fb 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/privatemember/PrivateProducerMethodTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/producer/privatemember/PrivateProducerMethodTest.java
@@ -18,12 +18,11 @@
 
 import static org.junit.Assert.assertEquals;
 
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.test.ArcTestContainer;
 import javax.annotation.PostConstruct;
 import javax.enterprise.context.ApplicationScoped;
 import javax.enterprise.inject.Produces;
-
-import io.quarkus.arc.Arc;
-import io.quarkus.arc.test.ArcTestContainer;
 import org.junit.Rule;
 import org.junit.Test;
 
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/requestcontext/ContextObserver.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/requestcontext/ContextObserver.java
new file mode 100644
index 0000000000000..58b91b4e20f22
--- /dev/null
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/requestcontext/ContextObserver.java
@@ -0,0 +1,34 @@
+package io.quarkus.arc.test.requestcontext;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.context.BeforeDestroyed;
+import javax.enterprise.context.Destroyed;
+import javax.enterprise.context.Initialized;
+import javax.enterprise.context.RequestScoped;
+import javax.enterprise.event.Observes;
+
+@ApplicationScoped
+public class ContextObserver {
+
+    public static int initializedObserved = 0;
+    public static int beforeDestroyedObserved = 0;
+    public static int destroyedObserved = 0;
+
+    public static void reset() {
+        initializedObserved = 0;
+        beforeDestroyedObserved = 0;
+        destroyedObserved = 0;
+    }
+
+    public void observeContextInit(@Observes @Initialized(RequestScoped.class) Object event) {
+        initializedObserved++;
+    }
+
+    public void observeContextBeforeDestroyed(@Observes @BeforeDestroyed(RequestScoped.class) Object event) {
+        beforeDestroyedObserved++;
+    }
+
+    public void observeContextDestroyed(@Observes @Destroyed(RequestScoped.class) Object event) {
+        destroyedObserved++;
+    }
+}
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/requestcontext/Controller.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/requestcontext/Controller.java
index 6d8a0b96cd508..ce86ef7563dc1 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/requestcontext/Controller.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/requestcontext/Controller.java
@@ -18,7 +18,6 @@
 
 import java.util.UUID;
 import java.util.concurrent.atomic.AtomicBoolean;
-
 import javax.annotation.PostConstruct;
 import javax.annotation.PreDestroy;
 import javax.enterprise.context.RequestScoped;
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/requestcontext/RequestContextTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/requestcontext/RequestContextTest.java
index bda9b4104c333..49284f898b1f2 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/requestcontext/RequestContextTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/requestcontext/RequestContextTest.java
@@ -22,23 +22,23 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
-import javax.enterprise.context.ContextNotActiveException;
-
 import io.quarkus.arc.Arc;
 import io.quarkus.arc.ArcContainer;
 import io.quarkus.arc.ManagedContext;
 import io.quarkus.arc.test.ArcTestContainer;
+import javax.enterprise.context.ContextNotActiveException;
+import javax.enterprise.context.control.RequestContextController;
 import org.junit.Rule;
 import org.junit.Test;
 
 public class RequestContextTest {
 
     @Rule
-    public ArcTestContainer container = new ArcTestContainer(Controller.class, ControllerClient.class);
+    public ArcTestContainer container = new ArcTestContainer(Controller.class, ControllerClient.class, ContextObserver.class);
 
     @Test
     public void testRequestContext() {
-
+        Controller.DESTROYED.set(false);
         ArcContainer arc = Arc.container();
         ManagedContext requestContext = arc.requestContext();
 
@@ -83,4 +83,119 @@ public void testRequestContext() {
         assertTrue(Controller.DESTROYED.get());
     }
 
+    @Test
+    public void testRequestContextController() {
+        Controller.DESTROYED.set(false);
+        ArcContainer arc = Arc.container();
+        RequestContextController controller = Arc.container().instance(RequestContextController.class).get();
+
+        try {
+            arc.instance(Controller.class).get().getId();
+            fail();
+        } catch (ContextNotActiveException expected) {
+        }
+
+        controller.activate();
+        assertFalse(Controller.DESTROYED.get());
+        Controller controller1 = arc.instance(Controller.class).get();
+        Controller controller2 = arc.instance(Controller.class).get();
+        String controller2Id = controller2.getId();
+        assertEquals(controller1.getId(), controller2Id);
+        controller.deactivate();
+        assertTrue(Controller.DESTROYED.get());
+
+        try {
+            arc.instance(Controller.class).get().getId();
+            fail();
+        } catch (ContextNotActiveException expected) {
+        }
+
+        // Id must be different in a different request
+        Controller.DESTROYED.set(false);
+        controller.activate();
+        assertNotEquals(controller2Id, arc.instance(Controller.class).get().getId());
+        controller.deactivate();
+        assertTrue(Controller.DESTROYED.get());
+
+        Controller.DESTROYED.set(false);
+        controller.activate();
+        assertNotEquals(controller2Id, arc.instance(Controller.class).get().getId());
+        controller.deactivate();
+        assertTrue(Controller.DESTROYED.get());
+
+        // @ActivateRequestContext
+        Controller.DESTROYED.set(false);
+        ControllerClient client = arc.instance(ControllerClient.class).get();
+        assertNotEquals(controller2Id, client.getControllerId());
+        assertTrue(Controller.DESTROYED.get());
+    }
+
+    @Test
+    public void testRequestContextEvents() {
+        // reset counters since other tests might have triggered it already
+        ContextObserver.reset();
+
+        // firstly test manual activation
+        ArcContainer arc = Arc.container();
+        ManagedContext requestContext = arc.requestContext();
+
+        try {
+            arc.instance(Controller.class).get().getId();
+            fail();
+        } catch (ContextNotActiveException expected) {
+        }
+
+        requestContext.activate();
+        assertEquals(1, ContextObserver.initializedObserved);
+        assertEquals(0, ContextObserver.beforeDestroyedObserved);
+        assertEquals(0, ContextObserver.destroyedObserved);
+
+        // dummy check that bean is available
+        arc.instance(Controller.class).get().getId();
+
+        requestContext.terminate();
+        assertEquals(1, ContextObserver.initializedObserved);
+        assertEquals(1, ContextObserver.beforeDestroyedObserved);
+        assertEquals(1, ContextObserver.destroyedObserved);
+
+        try {
+            arc.instance(Controller.class).get().getId();
+            fail();
+        } catch (ContextNotActiveException expected) {
+        }
+
+        // now test the same but activate context via interceptor (@ActivateRequestContext)
+        arc.instance(ControllerClient.class).get().getControllerId();
+        assertEquals(2, ContextObserver.initializedObserved);
+        assertEquals(2, ContextObserver.beforeDestroyedObserved);
+        assertEquals(2, ContextObserver.destroyedObserved);
+
+        // lastly, use RequestContextController bean to handle the context
+        try {
+            arc.instance(Controller.class).get().getId();
+            fail();
+        } catch (ContextNotActiveException expected) {
+        }
+
+        RequestContextController controller = arc.instance(RequestContextController.class).get();
+        controller.activate();
+        assertEquals(3, ContextObserver.initializedObserved);
+        assertEquals(2, ContextObserver.beforeDestroyedObserved);
+        assertEquals(2, ContextObserver.destroyedObserved);
+
+        // dummy check that bean is available
+        arc.instance(Controller.class).get().getId();
+
+        controller.deactivate();
+        assertEquals(3, ContextObserver.initializedObserved);
+        assertEquals(3, ContextObserver.beforeDestroyedObserved);
+        assertEquals(3, ContextObserver.destroyedObserved);
+
+        try {
+            arc.instance(Controller.class).get().getId();
+            fail();
+        } catch (ContextNotActiveException expected) {
+        }
+    }
+
 }
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/requestcontext/propagation/RequestContextPropagationTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/requestcontext/propagation/RequestContextPropagationTest.java
index 180203e6381d2..9f0b0208f06f9 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/requestcontext/propagation/RequestContextPropagationTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/requestcontext/propagation/RequestContextPropagationTest.java
@@ -22,16 +22,14 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
-import java.util.Collection;
-
-import javax.enterprise.context.ContextNotActiveException;
-
 import io.quarkus.arc.Arc;
 import io.quarkus.arc.ArcContainer;
 import io.quarkus.arc.ContextInstanceHandle;
 import io.quarkus.arc.InstanceHandle;
 import io.quarkus.arc.ManagedContext;
 import io.quarkus.arc.test.ArcTestContainer;
+import java.util.Collection;
+import javax.enterprise.context.ContextNotActiveException;
 import org.junit.Rule;
 import org.junit.Test;
 
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/requestcontext/propagation/SuperButton.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/requestcontext/propagation/SuperButton.java
index d0bc82479f9db..7b33340510b76 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/requestcontext/propagation/SuperButton.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/requestcontext/propagation/SuperButton.java
@@ -17,7 +17,6 @@
 package io.quarkus.arc.test.requestcontext.propagation;
 
 import java.util.concurrent.atomic.AtomicBoolean;
-
 import javax.annotation.PreDestroy;
 import javax.enterprise.context.Dependent;
 
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/requestcontext/propagation/SuperController.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/requestcontext/propagation/SuperController.java
index 0023cd3a1ee06..e24f63a7bb86f 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/requestcontext/propagation/SuperController.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/requestcontext/propagation/SuperController.java
@@ -18,7 +18,6 @@
 
 import java.util.UUID;
 import java.util.concurrent.atomic.AtomicBoolean;
-
 import javax.annotation.PostConstruct;
 import javax.annotation.PreDestroy;
 import javax.enterprise.context.RequestScoped;
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/resolution/RuntimeResolutionTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/resolution/RuntimeResolutionTest.java
index 90fbf46f539fe..05c703c72d4c6 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/resolution/RuntimeResolutionTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/resolution/RuntimeResolutionTest.java
@@ -19,17 +19,15 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.ArcContainer;
+import io.quarkus.arc.InstanceHandle;
+import io.quarkus.arc.test.ArcTestContainer;
 import java.io.IOException;
 import java.util.AbstractList;
 import java.util.List;
-
 import javax.enterprise.util.TypeLiteral;
 import javax.inject.Singleton;
-
-import io.quarkus.arc.Arc;
-import io.quarkus.arc.ArcContainer;
-import io.quarkus.arc.InstanceHandle;
-import io.quarkus.arc.test.ArcTestContainer;
 import org.junit.Rule;
 import org.junit.Test;
 
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/resolution/TypedTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/resolution/TypedTest.java
new file mode 100644
index 0000000000000..3adbc5441f116
--- /dev/null
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/resolution/TypedTest.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2018 Red Hat, Inc.
+ *
+ * Licensed 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.arc.test.resolution;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.ArcContainer;
+import io.quarkus.arc.InstanceHandle;
+import io.quarkus.arc.test.ArcTestContainer;
+import java.io.IOException;
+import java.util.concurrent.atomic.AtomicReference;
+import javax.enterprise.event.Observes;
+import javax.enterprise.inject.Produces;
+import javax.enterprise.inject.Typed;
+import javax.inject.Singleton;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class TypedTest {
+
+    static final AtomicReference EVENT = new AtomicReference();
+
+    @Rule
+    public ArcTestContainer container = new ArcTestContainer(MyBean.class, MyOtherBean.class, Stage.class);
+
+    @Test
+    public void testEmptyTyped() throws IOException {
+        ArcContainer container = Arc.container();
+        assertFalse(container.instance(MyBean.class).isAvailable());
+        assertNull(EVENT.get());
+        container.beanManager().getEvent().fire("foo");
+        assertEquals("foo", EVENT.get());
+        InstanceHandle stage = container.instance(Stage.class);
+        assertTrue(stage.isAvailable());
+        assertEquals("produced", stage.get().id);
+    }
+
+    @Typed // -> bean types = { Object.class }
+    @Singleton
+    static class MyBean {
+
+        void myObserver(@Observes String event) {
+            EVENT.set(event);
+        }
+
+    }
+
+    @Singleton
+    static class MyOtherBean {
+
+        @Produces
+        Stage myStage() {
+            return new Stage("produced");
+        }
+
+    }
+
+    @Typed
+    static class Stage {
+
+        final String id;
+
+        public Stage(String id) {
+            this.id = id;
+        }
+
+    }
+
+}
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/stereotypes/SimpleBinding.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/stereotypes/SimpleBinding.java
index 6727ca019dfa5..85ea110f300ac 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/stereotypes/SimpleBinding.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/stereotypes/SimpleBinding.java
@@ -23,7 +23,6 @@
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
-
 import javax.interceptor.InterceptorBinding;
 
 @Target({ TYPE, METHOD })
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/stereotypes/StereotypeAlternativeTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/stereotypes/StereotypeAlternativeTest.java
index 0c9168b58d4fb..235bf43b86b17 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/stereotypes/StereotypeAlternativeTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/stereotypes/StereotypeAlternativeTest.java
@@ -18,21 +18,19 @@
 
 import static org.junit.Assert.assertEquals;
 
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.test.ArcTestContainer;
 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 java.util.UUID;
-
 import javax.annotation.PostConstruct;
 import javax.annotation.Priority;
 import javax.enterprise.context.Dependent;
 import javax.enterprise.inject.Alternative;
 import javax.enterprise.inject.Stereotype;
-
-import io.quarkus.arc.Arc;
-import io.quarkus.arc.test.ArcTestContainer;
 import org.junit.Rule;
 import org.junit.Test;
 
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/stereotypes/StereotypeInterceptorTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/stereotypes/StereotypeInterceptorTest.java
index bc47011a2a6d1..939eadb8fefe6 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/stereotypes/StereotypeInterceptorTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/stereotypes/StereotypeInterceptorTest.java
@@ -18,23 +18,22 @@
 
 import static org.junit.Assert.assertEquals;
 
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.test.ArcTestContainer;
 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.enterprise.inject.Stereotype;
-
-import io.quarkus.arc.Arc;
-import io.quarkus.arc.test.ArcTestContainer;
 import org.junit.Rule;
 import org.junit.Test;
 
 public class StereotypeInterceptorTest {
 
     @Rule
-    public ArcTestContainer container = new ArcTestContainer(BeIntercepted.class, IamIntercepted.class, SimpleBinding.class, SimpleInterceptor.class);
+    public ArcTestContainer container = new ArcTestContainer(BeIntercepted.class, IamIntercepted.class, SimpleBinding.class,
+            SimpleInterceptor.class);
 
     @Test
     public void testStereotype() {
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/stereotypes/StereotypeNamedTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/stereotypes/StereotypeNamedTest.java
index 525c148b6c8a0..31a0d71c904a1 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/stereotypes/StereotypeNamedTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/stereotypes/StereotypeNamedTest.java
@@ -18,17 +18,15 @@
 
 import static org.junit.Assert.assertTrue;
 
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.test.ArcTestContainer;
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
-
 import javax.enterprise.context.ApplicationScoped;
 import javax.enterprise.inject.Stereotype;
 import javax.inject.Named;
-
-import io.quarkus.arc.Arc;
-import io.quarkus.arc.test.ArcTestContainer;
 import org.junit.Rule;
 import org.junit.Test;
 
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/stereotypes/StereotypeScopeTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/stereotypes/StereotypeScopeTest.java
index 89785d728834a..b119ef25ecb36 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/stereotypes/StereotypeScopeTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/stereotypes/StereotypeScopeTest.java
@@ -19,16 +19,14 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.ArcContainer;
+import io.quarkus.arc.test.ArcTestContainer;
 import java.util.UUID;
-
 import javax.annotation.PostConstruct;
 import javax.enterprise.context.ApplicationScoped;
 import javax.enterprise.inject.Model;
 import javax.enterprise.inject.Typed;
-
-import io.quarkus.arc.Arc;
-import io.quarkus.arc.ArcContainer;
-import io.quarkus.arc.test.ArcTestContainer;
 import org.junit.Rule;
 import org.junit.Test;
 
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/unused/RemoveUnusedBeansTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/unused/RemoveUnusedBeansTest.java
index 1799aea460df4..6c6886ab8389d 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/unused/RemoveUnusedBeansTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/unused/RemoveUnusedBeansTest.java
@@ -4,8 +4,10 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import io.quarkus.arc.Arc;
+import io.quarkus.arc.ArcContainer;
+import io.quarkus.arc.test.ArcTestContainer;
 import java.math.BigDecimal;
-
 import javax.annotation.PostConstruct;
 import javax.annotation.Priority;
 import javax.enterprise.context.Dependent;
@@ -17,10 +19,6 @@
 import javax.inject.Named;
 import javax.inject.Provider;
 import javax.inject.Singleton;
-
-import io.quarkus.arc.Arc;
-import io.quarkus.arc.ArcContainer;
-import io.quarkus.arc.test.ArcTestContainer;
 import org.junit.Rule;
 import org.junit.Test;
 
@@ -28,7 +26,8 @@ public class RemoveUnusedBeansTest {
 
     @Rule
     public ArcTestContainer container = ArcTestContainer.builder()
-            .beanClasses(HasObserver.class, Foo.class, FooAlternative.class, HasName.class, UnusedProducers.class, InjectedViaInstance.class, InjectedViaProvider.class, Excluded.class, UsedProducers.class)
+            .beanClasses(HasObserver.class, Foo.class, FooAlternative.class, HasName.class, UnusedProducers.class,
+                    InjectedViaInstance.class, InjectedViaProvider.class, Excluded.class, UsedProducers.class)
             .removeUnusedBeans(true)
             .addRemovalExclusion(b -> b.getBeanClass().toString().equals(Excluded.class.getName()))
             .build();
@@ -68,7 +67,7 @@ static class HasName {
 
     @Dependent
     static class Foo {
-        
+
         @Inject
         Provider provider;
 
@@ -95,17 +94,17 @@ static class FooAlternative extends Foo {
     static class InjectedViaInstance {
 
     }
-    
+
     @Singleton
     static class InjectedViaProvider {
-        
+
         private boolean isValid;
-        
+
         @PostConstruct
         void init() {
             isValid = true;
         }
-        
+
         boolean isValid() {
             return isValid;
         }
@@ -121,7 +120,7 @@ BigDecimal unusedNumber() {
         }
 
     }
-    
+
     @Singleton
     static class UsedProducers {
 
@@ -131,14 +130,14 @@ String usedString() {
         }
 
     }
-    
+
     @Singleton
     static class Excluded {
-        
+
         String ping() {
             return "pong";
         }
-        
+
     }
 
 }
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/validation/BoundInterceptorFinalTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/validation/BoundInterceptorFinalTest.java
index 3b4749fc33a5d..57995daade7ea 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/validation/BoundInterceptorFinalTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/validation/BoundInterceptorFinalTest.java
@@ -3,18 +3,18 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
+import io.quarkus.arc.test.ArcTestContainer;
 import javax.enterprise.context.Dependent;
 import javax.enterprise.inject.spi.DefinitionException;
 import javax.enterprise.inject.spi.DeploymentException;
-
-import io.quarkus.arc.test.ArcTestContainer;
 import org.junit.Rule;
 import org.junit.Test;
 
 public class BoundInterceptorFinalTest {
 
     @Rule
-    public ArcTestContainer container = ArcTestContainer.builder().beanClasses(Unproxyable.class, Simple.class, SimpleInterceptor.class).shouldFail().build();
+    public ArcTestContainer container = ArcTestContainer.builder()
+            .beanClasses(Unproxyable.class, Simple.class, SimpleInterceptor.class).shouldFail().build();
 
     @Test
     public void testFailure() {
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/validation/BoundInterceptorPrivateNoArgsConstructorTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/validation/BoundInterceptorPrivateNoArgsConstructorTest.java
index d70c9351d5fa8..e06f67e95658f 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/validation/BoundInterceptorPrivateNoArgsConstructorTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/validation/BoundInterceptorPrivateNoArgsConstructorTest.java
@@ -3,18 +3,18 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
+import io.quarkus.arc.test.ArcTestContainer;
 import javax.enterprise.context.Dependent;
 import javax.enterprise.inject.spi.DefinitionException;
 import javax.enterprise.inject.spi.DeploymentException;
-
-import io.quarkus.arc.test.ArcTestContainer;
 import org.junit.Rule;
 import org.junit.Test;
 
 public class BoundInterceptorPrivateNoArgsConstructorTest {
 
     @Rule
-    public ArcTestContainer container = ArcTestContainer.builder().beanClasses(Unproxyable.class, Simple.class, SimpleInterceptor.class).shouldFail().build();
+    public ArcTestContainer container = ArcTestContainer.builder()
+            .beanClasses(Unproxyable.class, Simple.class, SimpleInterceptor.class).shouldFail().build();
 
     @Test
     public void testFailure() {
@@ -31,7 +31,7 @@ static class Unproxyable {
 
         private Unproxyable() {
         }
-        
+
         void ping() {
         }
 
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/validation/ClassBeanMultipleScopesTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/validation/ClassBeanMultipleScopesTest.java
new file mode 100644
index 0000000000000..44e26054c17cc
--- /dev/null
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/validation/ClassBeanMultipleScopesTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2019 Red Hat, Inc.
+ *
+ * Licensed 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.arc.test.validation;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import io.quarkus.arc.test.ArcTestContainer;
+import javax.enterprise.context.Dependent;
+import javax.enterprise.inject.spi.DefinitionException;
+import javax.inject.Singleton;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class ClassBeanMultipleScopesTest {
+
+    @Rule
+    public ArcTestContainer container = ArcTestContainer.builder().beanClasses(Alpha.class).shouldFail().build();
+
+    @Test
+    public void testFailure() {
+        Throwable error = container.getFailure();
+        assertNotNull(error);
+        assertTrue(error instanceof DefinitionException);
+    }
+
+    @Singleton
+    @Dependent
+    static class Alpha {
+    }
+
+}
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/validation/NormalScopedConstructorTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/validation/NormalScopedConstructorTest.java
index 9755757e970a1..697f650ba3a88 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/validation/NormalScopedConstructorTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/validation/NormalScopedConstructorTest.java
@@ -3,13 +3,12 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
+import io.quarkus.arc.test.ArcTestContainer;
 import javax.enterprise.context.ApplicationScoped;
 import javax.enterprise.inject.Instance;
 import javax.enterprise.inject.spi.DefinitionException;
 import javax.enterprise.inject.spi.DeploymentException;
 import javax.inject.Inject;
-
-import io.quarkus.arc.test.ArcTestContainer;
 import org.junit.Rule;
 import org.junit.Test;
 
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/validation/NormalScopedFinalTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/validation/NormalScopedFinalTest.java
index c97931e873b4e..156d6b53d06a1 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/validation/NormalScopedFinalTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/validation/NormalScopedFinalTest.java
@@ -3,11 +3,10 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
+import io.quarkus.arc.test.ArcTestContainer;
 import javax.enterprise.context.ApplicationScoped;
 import javax.enterprise.inject.spi.DefinitionException;
 import javax.enterprise.inject.spi.DeploymentException;
-
-import io.quarkus.arc.test.ArcTestContainer;
 import org.junit.Rule;
 import org.junit.Test;
 
@@ -30,7 +29,7 @@ static final class Unproxyable {
 
         void ping() {
         }
-        
+
     }
 
 }
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/validation/NormalScopedPrivateNoArgsConstructorTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/validation/NormalScopedPrivateNoArgsConstructorTest.java
index 3cb6a79bd289a..1e881e167c3bc 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/validation/NormalScopedPrivateNoArgsConstructorTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/validation/NormalScopedPrivateNoArgsConstructorTest.java
@@ -3,11 +3,10 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
+import io.quarkus.arc.test.ArcTestContainer;
 import javax.enterprise.context.ApplicationScoped;
 import javax.enterprise.inject.spi.DefinitionException;
 import javax.enterprise.inject.spi.DeploymentException;
-
-import io.quarkus.arc.test.ArcTestContainer;
 import org.junit.Rule;
 import org.junit.Test;
 
@@ -30,7 +29,7 @@ static class Unproxyable {
 
         private Unproxyable() {
         }
-        
+
     }
 
 }
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/validation/ProducerFieldMultipleScopesTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/validation/ProducerFieldMultipleScopesTest.java
new file mode 100644
index 0000000000000..7732f5cd74809
--- /dev/null
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/validation/ProducerFieldMultipleScopesTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2019 Red Hat, Inc.
+ *
+ * Licensed 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.arc.test.validation;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import io.quarkus.arc.test.ArcTestContainer;
+import java.util.List;
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.context.Dependent;
+import javax.enterprise.context.RequestScoped;
+import javax.enterprise.inject.Produces;
+import javax.enterprise.inject.spi.DefinitionException;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class ProducerFieldMultipleScopesTest {
+
+    @Rule
+    public ArcTestContainer container = ArcTestContainer.builder().beanClasses(Alpha.class).shouldFail().build();
+
+    @Test
+    public void testFailure() {
+        Throwable error = container.getFailure();
+        assertNotNull(error);
+        assertTrue(error instanceof DefinitionException);
+    }
+
+    @Dependent
+    static class Alpha {
+
+        @Produces
+        @ApplicationScoped
+        @RequestScoped
+        List produced;
+
+    }
+
+}
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/validation/ProducerMethodMultipleScopesTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/validation/ProducerMethodMultipleScopesTest.java
new file mode 100644
index 0000000000000..2c579bd5c2641
--- /dev/null
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/validation/ProducerMethodMultipleScopesTest.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2019 Red Hat, Inc.
+ *
+ * Licensed 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.arc.test.validation;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import io.quarkus.arc.test.ArcTestContainer;
+import java.util.List;
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.context.Dependent;
+import javax.enterprise.context.RequestScoped;
+import javax.enterprise.inject.Produces;
+import javax.enterprise.inject.spi.DefinitionException;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class ProducerMethodMultipleScopesTest {
+
+    @Rule
+    public ArcTestContainer container = ArcTestContainer.builder().beanClasses(Alpha.class).shouldFail().build();
+
+    @Test
+    public void testFailure() {
+        Throwable error = container.getFailure();
+        assertNotNull(error);
+        assertTrue(error instanceof DefinitionException);
+    }
+
+    @Dependent
+    static class Alpha {
+
+        @Produces
+        @ApplicationScoped
+        @RequestScoped
+        List produce() {
+            return null;
+        }
+
+    }
+
+}
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/validation/Simple.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/validation/Simple.java
index 845a738a679d2..b443abe4a811f 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/validation/Simple.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/validation/Simple.java
@@ -23,7 +23,6 @@
 import java.lang.annotation.Documented;
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
-
 import javax.interceptor.InterceptorBinding;
 
 @Target({ TYPE, METHOD })
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/validation/SimpleInterceptor.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/validation/SimpleInterceptor.java
index 9d8ce3acac66c..0287f1579cb72 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/validation/SimpleInterceptor.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/validation/SimpleInterceptor.java
@@ -16,14 +16,13 @@
 
 package io.quarkus.arc.test.validation;
 
+import io.quarkus.arc.InvocationContextImpl;
 import javax.annotation.Priority;
 import javax.inject.Inject;
 import javax.interceptor.AroundInvoke;
 import javax.interceptor.Interceptor;
 import javax.interceptor.InvocationContext;
 
-import io.quarkus.arc.InvocationContextImpl;
-
 @Simple
 @Priority(1)
 @Interceptor
diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/vetoed/VetoedTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/vetoed/VetoedTest.java
index c78d03ba70407..65381de56ec00 100644
--- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/vetoed/VetoedTest.java
+++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/vetoed/VetoedTest.java
@@ -20,14 +20,12 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
-import java.util.AbstractList;
-
-import javax.enterprise.context.Dependent;
-import javax.enterprise.inject.Vetoed;
-
 import io.quarkus.arc.Arc;
 import io.quarkus.arc.ArcContainer;
 import io.quarkus.arc.test.ArcTestContainer;
+import java.util.AbstractList;
+import javax.enterprise.context.Dependent;
+import javax.enterprise.inject.Vetoed;
 import org.junit.Rule;
 import org.junit.Test;
 
diff --git a/independent-projects/bootstrap/core/pom.xml b/independent-projects/bootstrap/core/pom.xml
new file mode 100644
index 0000000000000..18ba70eee3dfb
--- /dev/null
+++ b/independent-projects/bootstrap/core/pom.xml
@@ -0,0 +1,89 @@
+
+
+
+    
+        quarkus-bootstrap-parent
+        io.quarkus
+        999-SNAPSHOT
+        ../pom.xml
+    
+    4.0.0
+
+    quarkus-bootstrap-core
+    Quarkus - Bootstrap - Core
+
+    
+        
+            org.apache.maven
+            maven-embedder
+        
+        
+            org.apache.maven
+            maven-settings-builder
+        
+        
+            org.apache.maven
+            maven-resolver-provider
+        
+        
+            org.apache.maven.resolver
+            maven-resolver-connector-basic
+        
+        
+            org.apache.maven.resolver
+            maven-resolver-transport-file
+        
+        
+            org.apache.maven.resolver
+            maven-resolver-transport-http
+        
+        
+            org.jboss.logging
+            jboss-logging
+        
+
+        
+            junit
+            junit
+            test
+        
+    
+
+    
+        
+            
+                src/main/resources
+                true
+            
+        
+        
+            
+                org.apache.maven.plugins
+                maven-jar-plugin
+                
+                    
+                        
+                            test-jar
+                        
+                    
+                
+            
+        
+    
+
diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/BootstrapClassLoaderFactory.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/BootstrapClassLoaderFactory.java
new file mode 100644
index 0000000000000..1b76c49020945
--- /dev/null
+++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/BootstrapClassLoaderFactory.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright 2019 Red Hat, Inc.
+ *
+ * Licensed 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.bootstrap;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.jboss.logging.Logger;
+
+import io.quarkus.bootstrap.model.AppDependency;
+import io.quarkus.bootstrap.model.AppModel;
+import io.quarkus.bootstrap.resolver.AppModelResolverException;
+import io.quarkus.bootstrap.resolver.BootstrapAppModelResolver;
+import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver;
+import io.quarkus.bootstrap.resolver.maven.workspace.LocalProject;
+
+/**
+ *
+ * @author Alexey Loubyansky
+ */
+public class BootstrapClassLoaderFactory {
+
+    private static final String QUARKUS = "quarkus";
+    private static final String BOOTSTRAP = "bootstrap";
+    private static final String DEPLOYMENT_CP = "deployment.cp";
+
+    public static final String PROP_CP_CACHE = "quarkus-classpath-cache";
+    public static final String PROP_WS_DISCOVERY = "quarkus-workspace-discovery";
+    public static final String PROP_OFFLINE = "quarkus-bootstrap-offline";
+
+    private static final int CP_CACHE_FORMAT_ID = 1;
+
+    private static final Logger log = Logger.getLogger(BootstrapClassLoaderFactory.class);
+
+    public static BootstrapClassLoaderFactory newInstance() {
+        return new BootstrapClassLoaderFactory();
+    }
+
+    private static URL[] toURLs(List deps, List extraPaths) {
+        final URL[] urls = new URL[deps.size() + extraPaths.size()];
+        try {
+            int i = 0;
+            while (i < deps.size()) {
+                urls[i] = deps.get(i).getArtifact().getPath().toUri().toURL();
+                ++i;
+            }
+            for(Path p : extraPaths) {
+                if(p == null) {
+                    continue;
+                }
+                urls[i++] = p.toUri().toURL();
+            }
+            return i != urls.length ? Arrays.copyOf(urls, i) : urls;
+        } catch (MalformedURLException e) {
+            throw new IllegalStateException("Failed to create a URL", e);
+        }
+    }
+
+    private static Path resolveCachedCpPath(LocalProject project) {
+        return project.getOutputDir().resolve(QUARKUS).resolve(BOOTSTRAP).resolve(DEPLOYMENT_CP);
+    }
+
+    private static void persistCp(LocalProject project, URL[] urls, Path p) {
+        try {
+            Files.createDirectories(p.getParent());
+            try (BufferedWriter writer = Files.newBufferedWriter(p)) {
+                writer.write(Integer.toString(CP_CACHE_FORMAT_ID));
+                writer.newLine();
+                writer.write(Integer.toString(project.getWorkspace().getId()));
+                writer.newLine();
+                for (URL url : urls) {
+                    writer.write(url.toExternalForm());
+                    writer.newLine();
+                }
+            }
+            debug("Deployment classpath for %s was cached in %s", project.getAppArtifact(), p);
+        } catch (IOException e) {
+            log.warn("Failed to persist deployment classpath cache in " + p + " for " + project.getAppArtifact(), e);
+        }
+    }
+
+    private ClassLoader parent;
+    private Path appClasses;
+    private List appCp = new ArrayList<>(1);
+    private boolean localProjectsDiscovery;
+    private Boolean offline;
+    private boolean enableClasspathCache;
+
+    private BootstrapClassLoaderFactory() {
+    }
+
+    public BootstrapClassLoaderFactory setParent(ClassLoader parent) {
+        this.parent = parent;
+        return this;
+    }
+
+    public BootstrapClassLoaderFactory setAppClasses(Path appClasses) {
+        this.appClasses = appClasses;
+        addToClassPath(appClasses);
+        return this;
+    }
+
+    public BootstrapClassLoaderFactory addToClassPath(Path path) {
+        this.appCp.add(path);
+        return this;
+    }
+
+    public BootstrapClassLoaderFactory setLocalProjectsDiscovery(boolean localProjectsDiscovery) {
+        this.localProjectsDiscovery = localProjectsDiscovery;
+        return this;
+    }
+
+    public BootstrapClassLoaderFactory setOffline(Boolean offline) {
+        this.offline = offline;
+        return this;
+    }
+
+    public BootstrapClassLoaderFactory setEnableClasspathCache(boolean enable) {
+        this.enableClasspathCache = enable;
+        return this;
+    }
+
+    /**
+     * WARNING: this method is creating a classloader by resolving all the dependencies on every call,
+     * without consulting the cache.
+     *
+     * @param hierarchical  whether the deployment classloader should use the classloader built using
+     * the user-defined application dependencies as its parent or all the dependencies should be loaded
+     * by the same classloader
+     * @return  classloader that is able to load both user-defined and deployment dependencies
+     * @throws BootstrapException  in case of a failure
+     */
+    public URLClassLoader newAllInclusiveClassLoader(boolean hierarchical) throws BootstrapException {
+        if (appClasses == null) {
+            throw new IllegalArgumentException("Application classes path has not been set");
+        }
+        try {
+            final MavenArtifactResolver.Builder mvnBuilder = MavenArtifactResolver.builder();
+            if(offline != null) {
+                mvnBuilder.setOffline(offline);
+            }
+            final LocalProject localProject;
+            if (localProjectsDiscovery) {
+                localProject = LocalProject.loadWorkspace(appClasses);
+                mvnBuilder.setWorkspace(localProject.getWorkspace());
+            } else {
+                localProject = LocalProject.load(appClasses);
+            }
+            final AppModel appModel = new BootstrapAppModelResolver(mvnBuilder.build()).resolveModel(localProject.getAppArtifact());
+            if (hierarchical) {
+                final URLClassLoader cl = new URLClassLoader(toURLs(appModel.getUserDependencies(), appCp), parent);
+                try {
+                    return new URLClassLoader(toURLs(appModel.getDeploymentDependencies(), Collections.emptyList()), cl);
+                } catch (Throwable e) {
+                    try {
+                        cl.close();
+                    } catch (IOException e1) {
+                        e1.printStackTrace();
+                    }
+                    throw e;
+                }
+            }
+            return new URLClassLoader(toURLs(appModel.getAllDependencies(), appCp), parent);
+        } catch (AppModelResolverException e) {
+            throw new BootstrapException("Failed to init application classloader", e);
+        }
+    }
+
+    public URLClassLoader newDeploymentClassLoader() throws BootstrapException {
+        if (appClasses == null) {
+            throw new IllegalArgumentException("Application classes path has not been set");
+        }
+        final URLClassLoader ucl;
+        Path cachedCpPath = null;
+        final LocalProject localProject = localProjectsDiscovery || enableClasspathCache
+                ? LocalProject.loadWorkspace(appClasses)
+                : LocalProject.load(appClasses);
+        try {
+            if (enableClasspathCache) {
+                cachedCpPath = resolveCachedCpPath(localProject);
+                if (Files.exists(cachedCpPath)) {
+                    try (BufferedReader reader = Files.newBufferedReader(cachedCpPath)) {
+                        if (matchesInt(reader.readLine(), CP_CACHE_FORMAT_ID)) {
+                            if (matchesInt(reader.readLine(), localProject.getWorkspace().getId())) {
+                                final List urls = new ArrayList<>();
+                                String line = reader.readLine();
+                                while (line != null) {
+                                    urls.add(new URL(line));
+                                    line = reader.readLine();
+                                }
+                                debug("Deployment classloader for %s was re-created from the classpath cache",
+                                        localProject.getAppArtifact());
+                                return new URLClassLoader(urls.toArray(new URL[urls.size()]), parent);
+                            } else {
+                                debug("Cached deployment classpath has expired for %s", localProject.getAppArtifact());
+                            }
+                        } else {
+                            debug("Unsupported classpath cache format in %s for %s", cachedCpPath,
+                                    localProject.getAppArtifact());
+                        }
+                    } catch (IOException e) {
+                        log.warn("Failed to read deployment classpath cache from " + cachedCpPath + " for " + localProject.getAppArtifact(), e);
+                    }
+                }
+            }
+            final MavenArtifactResolver.Builder mvn = MavenArtifactResolver.builder()
+                    .setWorkspace(localProject.getWorkspace());
+            if (offline != null) {
+                mvn.setOffline(offline);
+            }
+            final URL[] urls = toURLs(new BootstrapAppModelResolver(mvn.build()).resolveModel(localProject.getAppArtifact()).getDeploymentDependencies(), Collections.emptyList());
+            if(cachedCpPath != null) {
+                persistCp(localProject, urls, cachedCpPath);
+            }
+            ucl = new URLClassLoader(urls, parent);
+        } catch (AppModelResolverException e) {
+            throw new BootstrapException("Failed to create the deployment classloader for " + localProject.getAppArtifact(), e);
+        }
+        return ucl;
+    }
+
+    private static boolean matchesInt(String line, int value) {
+        if(line == null) {
+            return false;
+        }
+        try {
+            return Integer.parseInt(line) == value;
+        } catch(NumberFormatException e) {
+            // does not match
+        }
+        return false;
+    }
+
+    private static void debug(String msg, Object... args) {
+        if(log.isDebugEnabled()) {
+            log.debug(String.format(msg, args));
+        }
+    }
+}
diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/BootstrapConstants.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/BootstrapConstants.java
new file mode 100644
index 0000000000000..44647f8db9d3e
--- /dev/null
+++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/BootstrapConstants.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2019 Red Hat, Inc.
+ *
+ * Licensed 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.bootstrap;
+
+/**
+ *
+ * @author Alexey Loubyansky
+ */
+public interface BootstrapConstants {
+
+    String DESCRIPTOR_FILE_NAME = "quarkus-extension.properties";
+
+    String META_INF = "META-INF";
+
+    String DESCRIPTOR_PATH = META_INF + '/' + DESCRIPTOR_FILE_NAME;
+
+    String PROP_DEPLOYMENT_ARTIFACT = "deployment-artifact";
+
+    String EMPTY = "";
+    String JAR = "jar";
+    String POM = "pom";
+}
diff --git a/core/creator/src/main/java/io/quarkus/creator/demo/RunnerJarOutcomeDemo.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/BootstrapDependencyProcessingException.java
similarity index 53%
rename from core/creator/src/main/java/io/quarkus/creator/demo/RunnerJarOutcomeDemo.java
rename to independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/BootstrapDependencyProcessingException.java
index 5e8d0aa334844..a6768e050bf9e 100644
--- a/core/creator/src/main/java/io/quarkus/creator/demo/RunnerJarOutcomeDemo.java
+++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/BootstrapDependencyProcessingException.java
@@ -1,6 +1,5 @@
 /*
- * Copyright 2018 Red Hat, Inc. and/or its affiliates
- * and other contributors as indicated by the @author tags.
+ * Copyright 2019 Red Hat, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -15,23 +14,24 @@
  * limitations under the License.
  */
 
-package io.quarkus.creator.demo;
-
-import io.quarkus.creator.AppCreator;
-import io.quarkus.creator.phase.runnerjar.RunnerJarOutcome;
+package io.quarkus.bootstrap;
 
 /**
  *
  * @author Alexey Loubyansky
  */
-public class RunnerJarOutcomeDemo extends ConfigDemoBase {
+public class BootstrapDependencyProcessingException extends BootstrapException {
+
+    /**
+     *
+     */
+    private static final long serialVersionUID = 1L;
 
-    public static void main(String[] args) throws Exception {
-        new RunnerJarOutcomeDemo().run();
+    public BootstrapDependencyProcessingException(String message, Throwable cause) {
+        super(message, cause);
     }
 
-    @Override
-    public void demo(AppCreator creator) throws Exception {
-        creator.resolveOutcome(RunnerJarOutcome.class);
+    public BootstrapDependencyProcessingException(String message) {
+        super(message);
     }
 }
diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/BootstrapException.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/BootstrapException.java
new file mode 100644
index 0000000000000..a7b7500d3e163
--- /dev/null
+++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/BootstrapException.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2019 Red Hat, Inc.
+ *
+ * Licensed 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.bootstrap;
+
+/**
+ *
+ * @author Alexey Loubyansky
+ */
+public class BootstrapException extends Exception {
+
+    /**
+     *
+     */
+    private static final long serialVersionUID = 1L;
+
+    public BootstrapException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public BootstrapException(String message) {
+        super(message);
+    }
+}
diff --git a/core/creator/src/main/java/io/quarkus/creator/resolver/aether/DisabledDependencySelector.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/model/AppArtifact.java
similarity index 51%
rename from core/creator/src/main/java/io/quarkus/creator/resolver/aether/DisabledDependencySelector.java
rename to independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/model/AppArtifact.java
index e4308617bbc2d..765a22e78c2a0 100644
--- a/core/creator/src/main/java/io/quarkus/creator/resolver/aether/DisabledDependencySelector.java
+++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/model/AppArtifact.java
@@ -14,27 +14,37 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package io.quarkus.creator.resolver.aether;
 
-import org.eclipse.aether.collection.DependencyCollectionContext;
-import org.eclipse.aether.collection.DependencySelector;
-import org.eclipse.aether.graph.Dependency;
+package io.quarkus.bootstrap.model;
+
+import java.nio.file.Path;
 
 /**
+ * Represents an application (or its dependency) artifact.
  *
  * @author Alexey Loubyansky
  */
-class DisabledDependencySelector implements DependencySelector {
+public class AppArtifact extends AppArtifactCoords {
+
+    protected Path path;
+
+    public AppArtifact(String groupId, String artifactId, String version) {
+        super(groupId, artifactId, version);
+    }
 
-    static DisabledDependencySelector INSTANCE = new DisabledDependencySelector();
+    public AppArtifact(String groupId, String artifactId, String classifier, String type, String version) {
+        super(groupId, artifactId, classifier, type, version);
+    }
+
+    public Path getPath() {
+        return path;
+    }
 
-    @Override
-    public boolean selectDependency(Dependency dependency) {
-        return false;
+    public void setPath(Path path) {
+        this.path = path;
     }
 
-    @Override
-    public DependencySelector deriveChildSelector(DependencyCollectionContext context) {
-        return this;
+    public boolean isResolved() {
+        return path != null;
     }
-}
\ No newline at end of file
+}
diff --git a/core/creator/src/main/java/io/quarkus/creator/AppArtifact.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/model/AppArtifactCoords.java
similarity index 55%
rename from core/creator/src/main/java/io/quarkus/creator/AppArtifact.java
rename to independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/model/AppArtifactCoords.java
index 33515816eb9e0..cff48a6d09f5e 100644
--- a/core/creator/src/main/java/io/quarkus/creator/AppArtifact.java
+++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/model/AppArtifactCoords.java
@@ -1,6 +1,5 @@
 /*
- * Copyright 2018 Red Hat, Inc. and/or its affiliates
- * and other contributors as indicated by the @author tags.
+ * Copyright 2019 Red Hat, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -15,39 +14,62 @@
  * limitations under the License.
  */
 
-package io.quarkus.creator;
+package io.quarkus.bootstrap.model;
 
-import java.nio.file.Path;
+import io.quarkus.bootstrap.BootstrapConstants;
 
 /**
- * Represents an application (or its dependency) artifact.
+ * GroupId, artifactId, classifier, type, version
  *
  * @author Alexey Loubyansky
  */
-public class AppArtifact {
+public class AppArtifactCoords {
 
-    private static final String CLASSIFIER_NONE = "";
-    private static final String TYPE_JAR = "jar";
+    public static final String TYPE_JAR = BootstrapConstants.JAR;
+    public static final String TYPE_POM = BootstrapConstants.POM;
+
+    public static AppArtifactCoords fromString(String str) {
+        return new AppArtifactCoords(split(str, new String[5]));
+    }
+
+    protected static String[] split(String str, String[] parts) {
+        final int versionSep = str.lastIndexOf(':');
+        if(versionSep <= 0 || versionSep == str.length() - 1) {
+            throw new IllegalArgumentException("One of type, version or separating them ':' is missing from '" + str + "'");
+        }
+        parts[4] = str.substring(versionSep + 1);
+        AppArtifactKey.split(str, parts, versionSep);
+        return parts;
+    }
 
     protected final String groupId;
     protected final String artifactId;
     protected final String classifier;
     protected final String type;
     protected final String version;
-    protected Path path;
 
-    public AppArtifact(String groupId, String artifactId, String version) {
-        this(groupId, artifactId, CLASSIFIER_NONE, TYPE_JAR, version);
+    protected AppArtifactKey key;
+
+    protected AppArtifactCoords(String[] parts) {
+        groupId = parts[0];
+        artifactId = parts[1];
+        classifier = parts[2];
+        type = parts[3] == null ? TYPE_JAR : parts[3];
+        version = parts[4];
     }
 
-    public AppArtifact(String groupId, String artifactId, String classifier, String version) {
-        this(groupId, artifactId, classifier, TYPE_JAR, version);
+    public AppArtifactCoords(String groupId, String artifactId, String version) {
+        this(groupId, artifactId, BootstrapConstants.EMPTY, TYPE_JAR, version);
     }
 
-    public AppArtifact(String groupId, String artifactId, String classifier, String type, String version) {
+    public AppArtifactCoords(String groupId, String artifactId, String type, String version) {
+        this(groupId, artifactId, "", type, version);
+    }
+
+    public AppArtifactCoords(String groupId, String artifactId, String classifier, String type, String version) {
         this.groupId = groupId;
         this.artifactId = artifactId;
-        this.classifier = classifier == null ? CLASSIFIER_NONE : classifier;
+        this.classifier = classifier == null ? "" : classifier;
         this.type = type;
         this.version = version;
     }
@@ -64,10 +86,6 @@ public String getClassifier() {
         return classifier;
     }
 
-    public boolean hasClassifier() {
-        return !classifier.isEmpty();
-    }
-
     public String getType() {
         return type;
     }
@@ -76,16 +94,8 @@ public String getVersion() {
         return version;
     }
 
-    public Path getPath() {
-        return path;
-    }
-
-    protected void setPath(Path path) {
-        this.path = path;
-    }
-
-    public boolean isResolved() {
-        return path != null;
+    public AppArtifactKey getKey() {
+        return key == null ? key = new AppArtifactKey(groupId, artifactId, classifier, type) : key;
     }
 
     @Override
@@ -108,7 +118,7 @@ public boolean equals(Object obj) {
             return false;
         if (getClass() != obj.getClass())
             return false;
-        AppArtifact other = (AppArtifact) obj;
+        AppArtifactCoords other = (AppArtifactCoords) obj;
         if (artifactId == null) {
             if (other.artifactId != null)
                 return false;
@@ -139,9 +149,25 @@ public boolean equals(Object obj) {
 
     @Override
     public String toString() {
-        final StringBuilder buf = new StringBuilder(128);
-        buf.append(groupId).append(':').append(artifactId).append(':').append(classifier).append(':').append(type).append(':')
-                .append(version);
+        final StringBuilder buf = new StringBuilder();
+        append(buf);
         return buf.toString();
     }
+
+    protected StringBuilder append(final StringBuilder buf) {
+        buf.append(groupId).append(':').append(artifactId).append(':');
+        if(!classifier.isEmpty()) {
+            buf.append(classifier);
+        }
+        return buf.append(':').append(type).append(':').append(version);
+    }
+
+    public static void main(String[] args) {
+        AppArtifactCoords ga = fromString("g:a:v");
+        System.out.println(ga.getGroupId());
+        System.out.println(ga.getArtifactId());
+        System.out.println("'" + ga.getClassifier() + "'");
+        System.out.println(ga.getType());
+        System.out.println(ga.getVersion());
+    }
 }
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
new file mode 100644
index 0000000000000..22bb465f6b464
--- /dev/null
+++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/model/AppArtifactKey.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright 2019 Red Hat, Inc.
+ *
+ * Licensed 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.bootstrap.model;
+
+/**
+ * GroupId, artifactId and classifier
+ *
+ * @author Alexey Loubyansky
+ */
+public class AppArtifactKey {
+
+    public static AppArtifactKey fromString(String str) {
+        return new AppArtifactKey(split(str, new String[4], str.length()));
+    }
+
+    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) + "'");
+        }
+        parts[3] = str.substring(i + 1, fromIndex);
+        fromIndex = i;
+        i = str.lastIndexOf(':', fromIndex - 1);
+        if(i < 0) {
+            parts[0] = str.substring(0, fromIndex);
+            if((parts[1] = parts[3]).isEmpty()) {
+                throw new IllegalArgumentException("ArtifactId is empty in `" + str + "`");
+            }
+            parts[2] = "";
+            parts[3] = null;
+            return parts;
+        }
+        if(i == 0) {
+            throw new IllegalArgumentException("One of groupId or artifactId is missing from '" + str.substring(0, fromIndex) + "'");
+        }
+        if(i == fromIndex - 1) {
+            parts[2] = "";
+        } else {
+            parts[2] = str.substring(i + 1, fromIndex);
+        }
+
+        fromIndex = i;
+        i = str.lastIndexOf(':', fromIndex - 1);
+        if(i < 0) {
+            parts[0] = str.substring(0, fromIndex);
+            if((parts[1] = parts[2]).isEmpty()) {
+                throw new IllegalArgumentException("ArtifactId is empty in `" + str + "`");
+            }
+            parts[2] = parts[3];
+            parts[3] = null;
+            return parts;
+        }
+        if(i == 0 || i == fromIndex - 1) {
+            throw new IllegalArgumentException("One of groupId or artifactId is missing from '" + str.substring(0, fromIndex) + "'");
+        }
+
+        parts[0] = str.substring(0, i);
+        parts[1] = str.substring(i + 1, fromIndex);
+        if(parts[3].isEmpty()) {
+            parts[3] = null;
+        }
+        return parts;
+    }
+
+    protected final String groupId;
+    protected final String artifactId;
+    protected final String classifier;
+    protected final String type;
+
+    protected AppArtifactKey(String[] parts) {
+        this.groupId = parts[0];
+        this.artifactId = parts[1];
+        this.classifier = parts[2];
+        this.type = parts[3];
+    }
+
+    public AppArtifactKey(String groupId, String artifactId) {
+        this(groupId, artifactId, null);
+    }
+
+    public AppArtifactKey(String groupId, String artifactId, String classifier) {
+        this(groupId, artifactId, classifier, null);
+    }
+
+    public AppArtifactKey(String groupId, String artifactId, String classifier, String type) {
+        this.groupId = groupId;
+        this.artifactId = artifactId;
+        this.classifier = classifier == null ? "" : classifier;
+        this.type = type;
+    }
+
+    public String getGroupId() {
+        return groupId;
+    }
+
+    public String getArtifactId() {
+        return artifactId;
+    }
+
+    public String getClassifier() {
+        return classifier;
+    }
+
+
+    public String getType() {
+        return type;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((artifactId == null) ? 0 : artifactId.hashCode());
+        result = prime * result + ((classifier == null) ? 0 : classifier.hashCode());
+        result = prime * result + ((groupId == null) ? 0 : groupId.hashCode());
+        result = prime * result + ((type == null) ? 0 : type.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;
+        AppArtifactKey other = (AppArtifactKey) obj;
+        if (artifactId == null) {
+            if (other.artifactId != null)
+                return false;
+        } else if (!artifactId.equals(other.artifactId))
+            return false;
+        if (classifier == null) {
+            if (other.classifier != null)
+                return false;
+        } else if (!classifier.equals(other.classifier))
+            return false;
+        if (groupId == null) {
+            if (other.groupId != null)
+                return false;
+        } else if (!groupId.equals(other.groupId))
+            return false;
+        if (type == null) {
+            if (other.type != null)
+                return false;
+        } else if (!type.equals(other.type))
+            return false;
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder buf = new StringBuilder();
+        buf.append(groupId).append(':').append(artifactId);
+        if(!classifier.isEmpty()) {
+            buf.append(':').append(classifier);
+        } else if(type != null) {
+            buf.append(':');
+        }
+        if(type != null) {
+            buf.append(':').append(type);
+        }
+        return buf.toString();
+    }
+
+    public static void main(String[] args) {
+        AppArtifactKey ga = fromString("g:a:c:t");
+        System.out.println(ga.getGroupId());
+        System.out.println(ga.getArtifactId());
+        System.out.println("'" + ga.getClassifier() + "'");
+        System.out.println(ga.getType());
+    }
+}
diff --git a/core/creator/src/main/java/io/quarkus/creator/AppDependency.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/model/AppDependency.java
similarity index 76%
rename from core/creator/src/main/java/io/quarkus/creator/AppDependency.java
rename to independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/model/AppDependency.java
index 97c52d76cbb5d..22ec46c339009 100644
--- a/core/creator/src/main/java/io/quarkus/creator/AppDependency.java
+++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/model/AppDependency.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package io.quarkus.creator;
+package io.quarkus.bootstrap.model;
 
 /**
  * Represents an application artifact dependency.
@@ -26,10 +26,16 @@ public class AppDependency {
 
     private final AppArtifact artifact;
     private final String scope;
+    private final boolean optional;
 
     public AppDependency(AppArtifact artifact, String scope) {
+        this(artifact, scope, false);
+    }
+
+    public AppDependency(AppArtifact artifact, String scope, boolean optional) {
         this.artifact = artifact;
         this.scope = scope;
+        this.optional = optional;
     }
 
     public AppArtifact getArtifact() {
@@ -40,11 +46,16 @@ public String getScope() {
         return scope;
     }
 
+    public boolean isOptional() {
+        return optional;
+    }
+
     @Override
     public int hashCode() {
         final int prime = 31;
         int result = 1;
         result = prime * result + ((artifact == null) ? 0 : artifact.hashCode());
+        result = prime * result + (optional ? 1231 : 1237);
         result = prime * result + ((scope == null) ? 0 : scope.hashCode());
         return result;
     }
@@ -63,6 +74,8 @@ public boolean equals(Object obj) {
                 return false;
         } else if (!artifact.equals(other.artifact))
             return false;
+        if (optional != other.optional)
+            return false;
         if (scope == null) {
             if (other.scope != null)
                 return false;
@@ -73,6 +86,11 @@ public boolean equals(Object obj) {
 
     @Override
     public String toString() {
-        return artifact.toString() + '(' + scope + ')';
+        final StringBuilder buf = new StringBuilder();
+        artifact.append(buf).append('(').append(scope);
+        if(optional) {
+            buf.append(" optional");
+        }
+        return buf.append(')').toString();
     }
 }
diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/model/AppModel.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/model/AppModel.java
new file mode 100644
index 0000000000000..58f6c3f5f9f0a
--- /dev/null
+++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/model/AppModel.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2019 Red Hat, Inc.
+ *
+ * Licensed 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.bootstrap.model;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import io.quarkus.bootstrap.BootstrapDependencyProcessingException;
+
+/**
+ *
+ * @author Alexey Loubyansky
+ */
+public class AppModel {
+
+    private final AppArtifact appArtifact;
+    private final List deploymentDeps;
+    private final List userDeps;
+    private List allDeps;
+
+    public AppModel(AppArtifact appArtifact, List userDeps, List deploymentDeps) {
+        this.appArtifact = appArtifact;
+        this.userDeps = userDeps;
+        this.deploymentDeps = deploymentDeps;
+    }
+
+    public List getAllDependencies() throws BootstrapDependencyProcessingException {
+        if(allDeps == null) {
+            allDeps = new ArrayList<>(userDeps.size() + deploymentDeps.size());
+            allDeps.addAll(userDeps);
+            allDeps.addAll(deploymentDeps);
+        }
+        return allDeps;
+    }
+
+    public AppArtifact getAppArtifact() {
+        return appArtifact;
+    }
+
+    public List getUserDependencies() throws BootstrapDependencyProcessingException {
+            return userDeps;
+    }
+
+    public List getDeploymentDependencies() throws BootstrapDependencyProcessingException {
+        return deploymentDeps;
+    }
+}
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
new file mode 100644
index 0000000000000..b6a990235c47e
--- /dev/null
+++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/AppModelResolver.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2018 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed 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.bootstrap.resolver;
+
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.List;
+
+import io.quarkus.bootstrap.model.AppArtifact;
+import io.quarkus.bootstrap.model.AppDependency;
+import io.quarkus.bootstrap.model.AppModel;
+
+/**
+ * Application model resolver used to resolve application and/or its dependency artifacts.
+ *
+ * @author Alexey Loubyansky
+ */
+public interface AppModelResolver {
+
+    /**
+     * (Re-)links an artifact to a path.
+     *
+     * @param appArtifact  an artifact to (re-)link to the path
+     * @param localPath  local path to the artifact
+     * @throws AppModelResolverException  in case of a failure
+     */
+    void relink(AppArtifact appArtifact, Path localPath) throws AppModelResolverException;
+
+    /**
+     * Resolves an artifact.
+     *
+     * @param artifact  artifact to resolve
+     * @return  local path
+     * @throws AppModelResolverException  in case of a failure
+     */
+    Path resolve(AppArtifact artifact) throws AppModelResolverException;
+
+    /**
+     * Resolve application direct and transitive dependencies configured by the user.
+     *
+     * Note that deployment dependencies are not included in the result.
+     *
+     * @param artifact  application artifact
+     * @return  the list of dependencies configured by the user
+     * @throws AppModelResolverException  in case of a failure
+     */
+    default List resolveUserDependencies(AppArtifact artifact) throws AppModelResolverException {
+        return resolveUserDependencies(artifact, Collections.emptyList());
+    }
+
+    /**
+     * Resolve application direct and transitive dependencies configured by the user,
+     * given the specific versions of the direct dependencies.
+     *
+     * Note that deployment dependencies are not included in the result.
+     *
+     * @param artifact  application artifact
+     * @param deps  some or all of the direct dependencies that should be used in place of the original ones
+     * @return  the list of dependencies configured by the user
+     * @throws AppModelResolverException  in case of a failure
+     */
+    List resolveUserDependencies(AppArtifact artifact, List deps) throws AppModelResolverException;
+
+    /**
+     * Resolve dependencies that are required at runtime, excluding test and optional dependencies.
+     *
+     * @param artifact
+     * @return
+     * @throws AppModelResolverException
+     */
+    AppModel resolveModel(AppArtifact artifact) throws AppModelResolverException;
+
+    /**
+     * Resolve artifact dependencies given the specific versions of the direct dependencies
+     *
+     * @param root  root artifact
+     * @param deps  some or all of the direct dependencies that should be used in place of the original ones
+     * @return  collected dependencies
+     * @throws AppModelResolverException  in case of a failure
+     */
+    AppModel resolveModel(AppArtifact root, List deps) throws AppModelResolverException;
+
+    /**
+     * Lists versions released later than the version of the artifact up to the version
+     * specified or all the later versions in case the up-to-version is not provided.
+     *
+     * @param artifact  artifact to list the versions for
+     * @return  the list of versions released later than the version of the artifact
+     * @throws AppModelResolverException  in case of a failure
+     */
+    List listLaterVersions(AppArtifact artifact, String upToVersion, boolean inclusive) throws AppModelResolverException;
+
+    /**
+     * Returns the next version of the artifact from the specified range.
+     * In case the next version is not available, the method returns null.
+     *
+     * @param artifact  artifact
+     * @param fromVersion  the lowest version of the range
+     * @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
+     * @throws AppModelResolverException  in case of a failure
+     */
+    String getNextVersion(AppArtifact artifact, String fromVersion, boolean fromVersionIncluded, String upToVersion, boolean upToVersionIncluded) throws AppModelResolverException;
+
+    /**
+     * Returns the latest version for the artifact up to the version specified.
+     * In case there is no later version available, the artifact's version is returned.
+     *
+     * @param artifact  artifact
+     * @param upToVersion  max version boundary
+     * @param inclusive  whether the upToVersion should be included in the range or not
+     * @return  the latest version up to specified boundary
+     * @throws AppModelResolverException  in case of a failure
+     */
+    String getLatestVersion(AppArtifact artifact, String upToVersion, boolean inclusive) throws AppModelResolverException;
+}
diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/AppModelResolverException.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/AppModelResolverException.java
new file mode 100644
index 0000000000000..d22d44c2cc39e
--- /dev/null
+++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/AppModelResolverException.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2019 Red Hat, Inc.
+ *
+ * Licensed 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.bootstrap.resolver;
+
+/**
+ *
+ * @author Alexey Loubyansky
+ */
+public class AppModelResolverException extends Exception {
+
+    /**
+     *
+     */
+    private static final long serialVersionUID = 1L;
+
+    public AppModelResolverException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public AppModelResolverException(String message) {
+        super(message);
+    }
+}
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
new file mode 100644
index 0000000000000..8216cfcf60d6e
--- /dev/null
+++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/BootstrapAppModelResolver.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright 2018 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed 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.bootstrap.resolver;
+
+import java.io.File;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Consumer;
+
+import org.eclipse.aether.RepositoryException;
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.artifact.DefaultArtifact;
+import org.eclipse.aether.collection.DependencyGraphTransformationContext;
+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.resolution.ArtifactRequest;
+import org.eclipse.aether.resolution.ArtifactResult;
+import org.eclipse.aether.resolution.VersionRangeResult;
+import org.eclipse.aether.util.graph.transformer.ConflictIdSorter;
+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.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;
+
+/**
+ *
+ * @author Alexey Loubyansky
+ */
+public class BootstrapAppModelResolver implements AppModelResolver {
+
+    protected final MavenArtifactResolver mvn;
+    protected Consumer buildTreeConsumer;
+    protected boolean devmode;
+
+    public BootstrapAppModelResolver(MavenArtifactResolver mvn) throws AppModelResolverException {
+        this.mvn = mvn;
+    }
+
+    public void setBuildTreeLogger(Consumer buildTreeConsumer) {
+        this.buildTreeConsumer = buildTreeConsumer;
+    }
+
+    /**
+     * Indicates whether application should be resolved to set up the dev mode.
+     * The important difference between the dev mode and the usual build is that
+     * in the dev mode the user application will have to be compiled, so the classpath
+     * will have to include dependencies of scope provided.
+     *
+     * @param devmode  whether the resolver is going to be used to set up the dev mode
+     */
+    public BootstrapAppModelResolver setDevMode(boolean devmode) {
+        this.devmode = devmode;
+        return this;
+    }
+
+    public void addRemoteRepositories(List repos) {
+        mvn.addRemoteRepositories(repos);
+    }
+
+    @Override
+    public void relink(AppArtifact artifact, Path path) throws AppModelResolverException {
+        if(mvn.getLocalRepositoryManager() == null) {
+            return;
+        }
+        mvn.getLocalRepositoryManager().relink(artifact.getGroupId(), artifact.getArtifactId(), artifact.getClassifier(), artifact.getType(), artifact.getVersion(), path);
+        artifact.setPath(path);
+    }
+
+    @Override
+    public Path resolve(AppArtifact artifact) throws AppModelResolverException {
+        if(artifact.isResolved()) {
+            return artifact.getPath();
+        }
+        final Path path = mvn.resolve(toAetherArtifact(artifact)).getArtifact().getFile().toPath();
+        artifact.setPath(path);
+        return path;
+    }
+
+    @Override
+    public List resolveUserDependencies(AppArtifact appArtifact, List deps) throws AppModelResolverException {
+        final List mvnDeps;
+        if(deps.isEmpty()) {
+            mvnDeps = Collections.emptyList();
+        } else {
+            mvnDeps = new ArrayList<>(deps.size());
+            for (AppDependency dep : deps) {
+                mvnDeps.add(new Dependency(toAetherArtifact(dep.getArtifact()), dep.getScope()));
+            }
+        }
+        final List result = new ArrayList<>();
+        final TreeDependencyVisitor visitor = new TreeDependencyVisitor(new DependencyVisitor() {
+            @Override
+            public boolean visitEnter(DependencyNode node) {
+                return true;
+            }
+
+            @Override
+            public boolean visitLeave(DependencyNode node) {
+                final Dependency dep = node.getDependency();
+                if(dep != null) {
+                    result.add(new AppDependency(toAppArtifact(dep.getArtifact()), dep.getScope(), dep.isOptional()));
+                }
+                return true;
+            }});
+        mvn.resolveDependencies(toAetherArtifact(appArtifact), mvnDeps).getRoot().accept(visitor);
+        return result;
+    }
+
+    @Override
+    public AppModel resolveModel(AppArtifact appArtifact) throws AppModelResolverException {
+        return devmode ? injectDeploymentDependencies(appArtifact,
+                mvn.resolveDependencies(toAetherArtifact(appArtifact), "test").getRoot())
+                : doResolveModel(appArtifact, Collections.emptyList());
+    }
+
+    @Override
+    public AppModel resolveModel(AppArtifact appArtifact, List directDeps) throws AppModelResolverException {
+        final List mvnDeps;
+        if(directDeps.isEmpty()) {
+            mvnDeps = Collections.emptyList();
+        } else {
+            mvnDeps = new ArrayList<>(directDeps.size());
+            for (AppDependency dep : directDeps) {
+                mvnDeps.add(new Dependency(toAetherArtifact(dep.getArtifact()), dep.getScope()));
+            }
+        }
+        return doResolveModel(appArtifact, mvnDeps);
+    }
+
+    private AppModel doResolveModel(AppArtifact appArtifact, final List directMvnDeps) throws AppModelResolverException {
+        return injectDeploymentDependencies(appArtifact, mvn.resolveDependencies(toAetherArtifact(appArtifact), directMvnDeps).getRoot());
+    }
+
+    @Override
+    public List listLaterVersions(AppArtifact appArtifact, String upToVersion, boolean inclusive) throws AppModelResolverException {
+        final VersionRangeResult rangeResult = resolveVersionRangeResult(appArtifact, appArtifact.getVersion(), false, upToVersion, inclusive);
+        final List resolvedVersions = rangeResult.getVersions();
+        final List versions = new ArrayList<>(resolvedVersions.size());
+        for (Version v : resolvedVersions) {
+            versions.add(v.toString());
+        }
+        return versions;
+    }
+
+    @Override
+    public String getNextVersion(AppArtifact appArtifact, String fromVersion, boolean fromVersionIncluded, String upToVersion, boolean upToVersionInclusive) throws AppModelResolverException {
+        final VersionRangeResult rangeResult = resolveVersionRangeResult(appArtifact, fromVersion, fromVersionIncluded, upToVersion, upToVersionInclusive);
+        final List versions = rangeResult.getVersions();
+        if(versions.isEmpty()) {
+            return null;
+        }
+        Version next = versions.get(0);
+        for(int i = 1; i < versions.size(); ++i) {
+            final Version candidate = versions.get(i);
+            if(next.compareTo(candidate) > 0) {
+                next = candidate;
+            }
+        }
+        return next.toString();
+    }
+
+    @Override
+    public String getLatestVersion(AppArtifact appArtifact, String upToVersion, boolean inclusive) throws AppModelResolverException {
+        final VersionRangeResult rangeResult = resolveVersionRangeResult(appArtifact, appArtifact.getVersion(), false, upToVersion, inclusive);
+        final List versions = rangeResult.getVersions();
+        if(versions.isEmpty()) {
+            return appArtifact.getVersion();
+        }
+        Version latest = versions.get(0);
+        for(int i = 1; i < versions.size(); ++i) {
+            final Version candidate = versions.get(i);
+            if(latest.compareTo(candidate) < 0) {
+                latest = candidate;
+            }
+        }
+        return latest.toString();
+    }
+
+    public List resolveArtifactRepos(AppArtifact appArtifact) throws AppModelResolverException {
+        return mvn.resolveDescriptor(new DefaultArtifact(appArtifact.getGroupId(), appArtifact.getArtifactId(), appArtifact.getClassifier(), appArtifact.getType(), appArtifact.getVersion())).getRepositories();
+    }
+
+    public void install(AppArtifact appArtifact, Path localPath) throws AppModelResolverException {
+        mvn.install(new DefaultArtifact(appArtifact.getGroupId(), appArtifact.getArtifactId(), appArtifact.getClassifier(),
+                appArtifact.getType(), appArtifact.getVersion(), Collections.emptyMap(), localPath.toFile()));
+    }
+
+    private AppModel injectDeploymentDependencies(AppArtifact appArtifact, DependencyNode root) throws AppModelResolverException {
+
+        final Set appDeps = new HashSet<>();
+        final List userDeps = new ArrayList<>();
+        final TreeDependencyVisitor visitor = new TreeDependencyVisitor(new DependencyVisitor() {
+            @Override
+            public boolean visitEnter(DependencyNode node) {
+                return true;
+            }
+
+            @Override
+            public boolean visitLeave(DependencyNode node) {
+                final Dependency dep = node.getDependency();
+                if(dep != null) {
+                    final AppArtifact appArtifact = toAppArtifact(dep.getArtifact());
+                    appDeps.add(appArtifact.getKey());
+                    userDeps.add(new AppDependency(appArtifact, dep.getScope(), dep.isOptional()));
+                }
+                return true;
+            }});
+        for(DependencyNode child : root.getChildren()) {
+            child.accept(visitor);
+        }
+
+        final DeploymentInjectingDependencyVisitor deploymentInjector = new DeploymentInjectingDependencyVisitor(mvn);
+        try {
+            root.accept(new TreeDependencyVisitor(deploymentInjector));
+        } catch (DeploymentInjectionException e) {
+            throw new AppModelResolverException("Failed to inject extension deployment dependencies for " + root.getArtifact(), e.getCause());
+        }
+
+        List deploymentDeps = Collections.emptyList();
+        if(deploymentInjector.isInjectedDeps()) {
+            final DependencyGraphTransformationContext context = new SimpleDependencyGraphTransformationContext(mvn.getSession());
+            try {
+                // add conflict IDs to the added deployments
+                root = new ConflictMarker().transformGraph(root, context);
+                // resolves version conflicts
+                root = new ConflictIdSorter().transformGraph(root, context);
+                root = mvn.getSession().getDependencyGraphTransformer().transformGraph(root, context);
+            } catch (RepositoryException e) {
+                throw new AppModelResolverException("Failed to normalize the dependency graph", e);
+            }
+            final BuildDependencyGraphVisitor buildDepsVisitor = new BuildDependencyGraphVisitor(appDeps, buildTreeConsumer);
+            buildDepsVisitor.visit(root);
+            final List requests = buildDepsVisitor.getArtifactRequests();
+            if(!requests.isEmpty()) {
+                final List results = mvn.resolve(requests);
+                // update the artifacts in the graph
+                for (ArtifactResult result : results) {
+                    final Artifact artifact = result.getArtifact();
+                    if (artifact != null) {
+                        result.getRequest().getDependencyNode().setArtifact(artifact);
+                    }
+                }
+                final List deploymentDepNodes = buildDepsVisitor.getDeploymentNodes();
+                deploymentDeps = new ArrayList<>(deploymentDepNodes.size());
+                for (DependencyNode dep : deploymentDepNodes) {
+                    deploymentDeps.add(new AppDependency(BootstrapAppModelResolver.toAppArtifact(dep.getArtifact()),
+                            dep.getDependency().getScope(), dep.getDependency().isOptional()));
+                }
+            }
+        }
+
+        return new AppModel(appArtifact, userDeps, deploymentDeps);
+    }
+
+    private VersionRangeResult resolveVersionRangeResult(AppArtifact appArtifact, String fromVersion, boolean fromVersionIncluded, String upToVersion, boolean upToVersionIncluded)
+            throws AppModelResolverException {
+        return mvn.resolveVersionRange(new DefaultArtifact(appArtifact.getGroupId(),
+                appArtifact.getArtifactId(), appArtifact.getType(),
+                (fromVersionIncluded ? '[' : '(')
+                + fromVersion + ','
+                + (upToVersion == null ? ')' : upToVersion + (upToVersionIncluded ? ']' : ')'))));
+    }
+
+    static List toAppDepList(DependencyNode rootNode) {
+        final List depNodes = rootNode.getChildren();
+        if(depNodes.isEmpty()) {
+            return Collections.emptyList();
+        }
+        final List appDeps =  new ArrayList<>();
+        collect(depNodes, appDeps);
+        return appDeps;
+    }
+
+    private static void collect(List nodes, List appDeps) {
+        for(DependencyNode node : nodes) {
+            collect(node.getChildren(), appDeps);
+            final Dependency dep = node.getDependency();
+            appDeps.add(new AppDependency(toAppArtifact(node.getArtifact()), dep.getScope(), dep.isOptional()));
+        }
+    }
+
+    private static Artifact toAetherArtifact(AppArtifact artifact) {
+        return new DefaultArtifact(artifact.getGroupId(), artifact.getArtifactId(), artifact.getClassifier(), artifact.getType(), artifact.getVersion());
+    }
+
+    private static AppArtifact toAppArtifact(Artifact artifact) {
+        final AppArtifact appArtifact = new AppArtifact(artifact.getGroupId(), artifact.getArtifactId(), artifact.getClassifier(), artifact.getExtension(), artifact.getVersion());
+        final File file = artifact.getFile();
+        if(file != null) {
+            appArtifact.setPath(file.toPath());
+        }
+        return appArtifact;
+    }
+}
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
new file mode 100644
index 0000000000000..28ffcdfb21c6a
--- /dev/null
+++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/BuildDependencyGraphVisitor.java
@@ -0,0 +1,149 @@
+/**
+ *
+ */
+package io.quarkus.bootstrap.resolver.maven;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Consumer;
+
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.graph.Dependency;
+import org.eclipse.aether.graph.DependencyNode;
+import org.eclipse.aether.resolution.ArtifactRequest;
+
+import io.quarkus.bootstrap.model.AppArtifactKey;
+
+public class BuildDependencyGraphVisitor {
+
+    private final Set appDeps;
+    private final StringBuilder buf;
+    private final Consumer buildTreeConsumer;
+    private final List depth;
+
+    private DependencyNode deploymentNode;
+    private DependencyNode runtimeNode;
+    private Artifact runtimeArtifact;
+
+    private final List deploymentDepNodes = new ArrayList<>();
+    private final List requests = new ArrayList<>();
+
+
+    public BuildDependencyGraphVisitor(Set appDeps, Consumer buildTreeConsumer) {
+        this.appDeps = appDeps;
+        this.buildTreeConsumer = buildTreeConsumer;
+        if(buildTreeConsumer == null) {
+            buf = null;
+            depth = null;
+        } else {
+            buf = new StringBuilder();
+            depth = new ArrayList<>();
+        }
+    }
+
+    public List getDeploymentNodes() {
+        return deploymentDepNodes;
+    }
+
+    public List getArtifactRequests() {
+        return requests;
+    }
+
+    public void visit(DependencyNode node) {
+        if(depth != null) {
+            buf.setLength(0);
+            if (!depth.isEmpty()) {
+                for (int i = 0; i < depth.size() - 1; ++i) {
+                    if (depth.get(i)) {
+                        //buf.append("|  ");
+                        buf.append('\u2502').append("  ");
+                    } else {
+                        buf.append("   ");
+                    }
+                }
+                if (depth.get(depth.size() - 1)) {
+                    //buf.append("|- ");
+                    buf.append('\u251c').append('\u2500').append(' ');
+                } else {
+                    //buf.append("\\- ");
+                    buf.append('\u2514').append('\u2500').append(' ');
+                }
+            }
+            buf.append(node.getArtifact());
+            if(!depth.isEmpty()) {
+                buf.append(" (").append(node.getDependency().getScope());
+                if(node.getDependency().isOptional()) {
+                    buf.append(" optional");
+                }
+                buf.append(')');
+            }
+            buildTreeConsumer.accept(buf.toString());
+        }
+        visitEnter(node);
+        final List children = node.getChildren();
+        if(!children.isEmpty()) {
+            final int childrenTotal = children.size();
+            if(childrenTotal == 1) {
+                if(depth != null) {
+                    depth.add(false);
+                }
+                visit(children.get(0));
+            } else {
+                if(depth != null) {
+                    depth.add(true);
+                }
+                int i = 0;
+                while(true) {
+                    visit(children.get(i++));
+                    if(i < childrenTotal - 1) {
+                        continue;
+                    } else if(i == childrenTotal) {
+                        break;
+                    } else if(depth != null) {
+                        depth.set(depth.size() - 1, false);
+                    }
+                }
+            }
+            if(depth != null) {
+                depth.remove(depth.size() - 1);
+            }
+        }
+        visitLeave(node);
+    }
+
+    private void visitEnter(DependencyNode node) {
+        final Dependency dep = node.getDependency();
+        if (deploymentNode == null) {
+            runtimeArtifact = DeploymentInjectingDependencyVisitor.getInjectedDependency(node);
+            if (runtimeArtifact != null) {
+                deploymentNode = node;
+            }
+        } else if (runtimeArtifact != null && runtimeNode == null && runtimeArtifact.equals(dep.getArtifact())) {
+            runtimeNode = node;
+        }
+    }
+
+    private void visitLeave(DependencyNode node) {
+        final Dependency dep = node.getDependency();
+        if(dep == null) {
+            return;
+        }
+        final Artifact artifact = dep.getArtifact();
+        if (artifact.getFile() == null) {
+            requests.add(new ArtifactRequest(node));
+        }
+        if (deploymentNode != null) {
+            if (runtimeNode == null && !appDeps.contains(new AppArtifactKey(artifact.getGroupId(),
+                    artifact.getArtifactId(), artifact.getClassifier(), artifact.getExtension()))) {
+                deploymentDepNodes.add(node);
+            } else if (runtimeNode == node) {
+                runtimeNode = null;
+                runtimeArtifact = null;
+            }
+            if (deploymentNode == node) {
+                deploymentNode = null;
+            }
+        }
+    }
+}
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
new file mode 100644
index 0000000000000..5c9ddd66c0ed7
--- /dev/null
+++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/DeploymentInjectingDependencyVisitor.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright 2019 Red Hat, Inc.
+ *
+ * Licensed 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.bootstrap.resolver.maven;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.FileSystem;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.Properties;
+
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.artifact.DefaultArtifact;
+import org.eclipse.aether.graph.DependencyNode;
+import org.eclipse.aether.graph.DependencyVisitor;
+import org.jboss.logging.Logger;
+import io.quarkus.bootstrap.BootstrapConstants;
+import io.quarkus.bootstrap.BootstrapDependencyProcessingException;
+import io.quarkus.bootstrap.resolver.AppModelResolverException;
+import io.quarkus.bootstrap.util.ZipUtils;
+
+/**
+ *
+ * @author Alexey Loubyansky
+ */
+public class DeploymentInjectingDependencyVisitor implements DependencyVisitor {
+
+    private static final Logger log = Logger.getLogger(DeploymentInjectingDependencyVisitor.class);
+
+    static final String INJECTED_DEPENDENCY = "injected.dep";
+
+    public static Artifact getInjectedDependency(DependencyNode dep) {
+        return (Artifact) dep.getData().get(DeploymentInjectingDependencyVisitor.INJECTED_DEPENDENCY);
+    }
+
+    private final MavenArtifactResolver resolver;
+    private DependencyNode node;
+
+    boolean injectedDeps;
+
+    public DeploymentInjectingDependencyVisitor(MavenArtifactResolver resolver) {
+        this.resolver = resolver;
+    }
+
+    public boolean isInjectedDeps() {
+        return injectedDeps;
+    }
+
+    @Override
+    public boolean visitEnter(DependencyNode node) {
+        final Artifact artifact = node.getArtifact();
+        if(!artifact.getExtension().equals("jar")) {
+            return true;
+        }
+        this.node = node;
+
+        boolean processChildren = true;
+        final Path path = resolve(artifact);
+        try {
+            if (Files.isDirectory(path)) {
+                processChildren = !processMetaInfDir(path.resolve(BootstrapConstants.META_INF));
+            } else {
+                try (FileSystem artifactFs = ZipUtils.newFileSystem(path)) {
+                    processChildren = !processMetaInfDir(artifactFs.getPath(BootstrapConstants.META_INF));
+                }
+            }
+        } catch (Throwable t) {
+            throw new DeploymentInjectionException("Failed to inject extension deplpyment dependencies", t);
+        }
+        return processChildren;
+    }
+
+    @Override
+    public boolean visitLeave(DependencyNode node) {
+        return true;
+    }
+
+    private boolean processMetaInfDir(Path metaInfDir) throws BootstrapDependencyProcessingException {
+        if (!Files.exists(metaInfDir)) {
+            return false;
+        }
+        final Path p = metaInfDir.resolve(BootstrapConstants.DESCRIPTOR_FILE_NAME);
+        if (!Files.exists(p)) {
+            return false;
+        }
+        processPlatformArtifact(p);
+        return true;
+    }
+
+    private void processPlatformArtifact(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);
+        if(value != null) {
+            replaceWith(collectDependencies(toArtifact(value)));
+        }
+    }
+
+    private void replaceWith(DependencyNode depNode) throws BootstrapDependencyProcessingException {
+        List children = depNode.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);
+        injectedDeps = true;
+    }
+
+    private DependencyNode collectDependencies(Artifact artifact) throws BootstrapDependencyProcessingException {
+        if(artifact.getVersion().isEmpty()) {
+            artifact = artifact.setVersion(node.getArtifact().getVersion());
+        }
+        try {
+            return resolver.collectDependencies(artifact).getRoot();
+        } catch (AppModelResolverException e) {
+            throw new DeploymentInjectionException(e);
+        }
+    }
+
+    private Path resolve(Artifact artifact) {
+        File file = artifact.getFile();
+        if(file != null) {
+            return file.toPath();
+        }
+        try {
+            return resolver.resolve(artifact).getArtifact().getFile().toPath();
+        } catch (AppModelResolverException e) {
+            throw new DeploymentInjectionException(e);
+        }
+    }
+
+    private Properties resolveDescriptor(final Path path) throws BootstrapDependencyProcessingException {
+        final Properties rtProps;
+        if (!Files.exists(path)) {
+            // not a platform artifact
+            return null;
+        }
+        rtProps = new Properties();
+        try (BufferedReader reader = Files.newBufferedReader(path)) {
+            rtProps.load(reader);
+        } catch (IOException e) {
+            throw new BootstrapDependencyProcessingException("Failed to load " + path, e);
+        }
+        return rtProps;
+    }
+
+    public static Artifact toArtifact(String str) {
+        return toArtifact(str, 0);
+    }
+
+    private static Artifact toArtifact(String str, int offset) {
+        String groupId = null;
+        String artifactId = null;
+        String classifier = "";
+        String type = "jar";
+        String version = null;
+
+        int colon = str.indexOf(':', offset);
+        final int length = str.length();
+        if(colon < offset + 1 || colon == length - 1) {
+            illegalDependencyFormat(str);
+        }
+        groupId = str.substring(offset, colon);
+        offset = colon + 1;
+        colon = str.indexOf(':', offset);
+        if(colon < 0) {
+            artifactId = str.substring(offset, length);
+        } else {
+            if(colon == length - 1) {
+                illegalDependencyFormat(str);
+            }
+            artifactId = str.substring(offset, colon);
+            offset = colon + 1;
+            colon = str.indexOf(':', offset);
+            if(colon < 0) {
+                version = str.substring(offset, length);
+            } else {
+                if(colon == length - 1) {
+                    illegalDependencyFormat(str);
+                }
+                type = str.substring(offset, colon);
+                offset = colon + 1;
+                colon = str.indexOf(':', offset);
+                if(colon < 0) {
+                    version = str.substring(offset, length);
+                } else {
+                    if (colon == length - 1) {
+                        illegalDependencyFormat(str);
+                    }
+                    classifier = type;
+                    type = str.substring(offset, colon);
+                    version = str.substring(colon + 1);
+                }
+            }
+        }
+        return new DefaultArtifact(groupId, artifactId, classifier, type, version);
+    }
+
+    private static void illegalDependencyFormat(String str) {
+        throw new IllegalArgumentException("Bad artifact coordinates " + str
+                + ", expected format is :[:[:]]:");
+    }
+}
diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/DeploymentInjectionException.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/DeploymentInjectionException.java
new file mode 100644
index 0000000000000..d1badcd0a147b
--- /dev/null
+++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/DeploymentInjectionException.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2019 Red Hat, Inc.
+ *
+ * Licensed 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.bootstrap.resolver.maven;
+
+/**
+ *
+ * @author Alexey Loubyansky
+ */
+public class DeploymentInjectionException extends RuntimeException {
+
+    /**
+     *
+     */
+    private static final long serialVersionUID = 1L;
+
+    public DeploymentInjectionException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public DeploymentInjectionException(Throwable cause) {
+        super(cause);
+    }
+
+    public DeploymentInjectionException(String message) {
+        super(message);
+    }
+}
diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/MavenArtifactResolver.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/MavenArtifactResolver.java
new file mode 100644
index 0000000000000..15fc8b166d9f5
--- /dev/null
+++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/MavenArtifactResolver.java
@@ -0,0 +1,271 @@
+/**
+ *
+ */
+package io.quarkus.bootstrap.resolver.maven;
+
+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.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.aether.DefaultRepositoryCache;
+import org.eclipse.aether.DefaultRepositorySystemSession;
+import org.eclipse.aether.RepositorySystem;
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.collection.CollectRequest;
+import org.eclipse.aether.collection.CollectResult;
+import org.eclipse.aether.collection.DependencyCollectionException;
+import org.eclipse.aether.graph.Dependency;
+import org.eclipse.aether.installation.InstallRequest;
+import org.eclipse.aether.installation.InstallationException;
+import org.eclipse.aether.repository.LocalRepository;
+import org.eclipse.aether.repository.RemoteRepository;
+import org.eclipse.aether.resolution.ArtifactDescriptorException;
+import org.eclipse.aether.resolution.ArtifactDescriptorRequest;
+import org.eclipse.aether.resolution.ArtifactDescriptorResult;
+import org.eclipse.aether.resolution.ArtifactRequest;
+import org.eclipse.aether.resolution.ArtifactResolutionException;
+import org.eclipse.aether.resolution.ArtifactResult;
+import org.eclipse.aether.resolution.DependencyRequest;
+import org.eclipse.aether.resolution.DependencyResolutionException;
+import org.eclipse.aether.resolution.DependencyResult;
+import org.eclipse.aether.resolution.VersionRangeRequest;
+import org.eclipse.aether.resolution.VersionRangeResolutionException;
+import org.eclipse.aether.resolution.VersionRangeResult;
+import org.eclipse.aether.util.artifact.JavaScopes;
+import io.quarkus.bootstrap.resolver.AppModelResolverException;
+import io.quarkus.bootstrap.resolver.maven.workspace.LocalWorkspace;
+
+/**
+ *
+ * @author Alexey Loubyansky
+ */
+public class MavenArtifactResolver {
+
+    public static class Builder {
+
+        private Path repoHome;
+        private RepositorySystem repoSystem;
+        private RepositorySystemSession repoSession;
+        private List remoteRepos = null;
+        private Boolean offline;
+        private LocalWorkspace workspace;
+
+        private Builder() {
+        }
+
+        public Builder setRepoHome(Path home) {
+            this.repoHome = home;
+            return this;
+        }
+
+        public Builder setRepositorySystem(RepositorySystem system) {
+            this.repoSystem = system;
+            return this;
+        }
+
+        public Builder setRepositorySystemSession(RepositorySystemSession session) {
+            this.repoSession = session;
+            return this;
+        }
+
+        public Builder setRemoteRepositories(List repos) {
+            this.remoteRepos = repos;
+            return this;
+        }
+
+        public Builder setOffline(boolean offline) {
+            this.offline = offline;
+            return this;
+        }
+
+        public Builder setWorkspace(LocalWorkspace workspace) {
+            this.workspace = workspace;
+            return this;
+        }
+
+        public MavenArtifactResolver build() throws AppModelResolverException {
+            return new MavenArtifactResolver(this);
+        }
+    }
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    protected final RepositorySystem repoSystem;
+    protected final RepositorySystemSession repoSession;
+    protected final List remoteRepos;
+    protected final MavenLocalRepositoryManager localRepoManager;
+
+    private MavenArtifactResolver(Builder builder) throws AppModelResolverException {
+
+
+        this.repoSystem = builder.repoSystem == null ? MavenRepoInitializer.getRepositorySystem(
+                (builder.offline == null
+                        ? (builder.repoSession == null ? MavenRepoInitializer.getSettings().isOffline()
+                                : builder.repoSession.isOffline())
+                        : builder.offline),
+                builder.workspace) : builder.repoSystem;
+        final DefaultRepositorySystemSession newSession = builder.repoSession == null ? MavenRepoInitializer.newSession(repoSystem) : new DefaultRepositorySystemSession(builder.repoSession);
+        if(builder.offline != null) {
+            newSession.setOffline(builder.offline);
+        }
+
+        if(builder.repoHome != null) {
+            final MavenLocalRepositoryManager appCreatorLocalRepoManager = new MavenLocalRepositoryManager(
+                    repoSystem.newLocalRepositoryManager(newSession, new LocalRepository(builder.repoHome.toString())),
+                    Paths.get(MavenRepoInitializer.getLocalRepo(MavenRepoInitializer.getSettings())));
+            newSession.setLocalRepositoryManager(appCreatorLocalRepoManager);
+            localRepoManager = appCreatorLocalRepoManager;
+        } else {
+            localRepoManager = null;
+        }
+
+        if(newSession.getCache() == null) {
+            newSession.setCache(new DefaultRepositoryCache());
+        }
+
+        if (builder.workspace != null) {
+            newSession.setWorkspaceReader(builder.workspace);
+        }
+
+        this.repoSession = newSession;
+        this.remoteRepos = builder.remoteRepos == null ? MavenRepoInitializer.getRemoteRepos() : builder.remoteRepos;
+    }
+
+    public MavenLocalRepositoryManager getLocalRepositoryManager() {
+        return localRepoManager;
+    }
+
+    public RepositorySystem getSystem() {
+        return repoSystem;
+    }
+
+    public RepositorySystemSession getSession() {
+        return repoSession;
+    }
+
+    public List getRepositories() {
+        return remoteRepos;
+    }
+
+    public void addRemoteRepositories(List repos) {
+        remoteRepos.addAll(repos);
+    }
+
+    public ArtifactResult resolve(Artifact artifact) throws AppModelResolverException {
+        try {
+            return repoSystem.resolveArtifact(repoSession,
+                    new ArtifactRequest()
+                    .setArtifact(artifact)
+                    .setRepositories(remoteRepos));
+        } catch (ArtifactResolutionException e) {
+            throw new AppModelResolverException("Failed to resolve artifact " + artifact, e);
+        }
+    }
+
+    public List resolve(List artifacts) throws AppModelResolverException {
+        try {
+            return repoSystem.resolveArtifacts(repoSession, artifacts);
+        } catch (ArtifactResolutionException e) {
+            throw new AppModelResolverException("Failed to resolve artifacts", e);
+        }
+    }
+
+    public ArtifactDescriptorResult resolveDescriptor(final Artifact artifact)
+            throws AppModelResolverException {
+        try {
+            return repoSystem.readArtifactDescriptor(repoSession,
+                    new ArtifactDescriptorRequest()
+                    .setArtifact(artifact));
+        } catch (ArtifactDescriptorException e) {
+            throw new AppModelResolverException("Failed to read descriptor of " + artifact, e);
+        }
+    }
+
+    public VersionRangeResult resolveVersionRange(Artifact artifact) throws AppModelResolverException {
+        try {
+            return repoSystem.resolveVersionRange(repoSession,
+                    new VersionRangeRequest()
+                    .setArtifact(artifact)
+                    .setRepositories(remoteRepos));
+        } catch (VersionRangeResolutionException ex) {
+            throw new AppModelResolverException("Failed to resolve version range for " + artifact, ex);
+        }
+    }
+
+    public CollectResult collectDependencies(Artifact artifact) throws AppModelResolverException {
+        return collectDependencies(artifact, Collections.emptyList());
+    }
+
+    public DependencyResult resolveDependencies(Artifact artifact) throws AppModelResolverException {
+        return resolveDependencies(artifact, Collections.emptyList());
+    }
+
+    public CollectResult collectDependencies(Artifact artifact, List deps) throws AppModelResolverException {
+        final CollectRequest request = newCollectRequest(artifact);
+        request.setDependencies(deps);
+        try {
+            return repoSystem.collectDependencies(repoSession, request);
+        } catch (DependencyCollectionException e) {
+            throw new AppModelResolverException("Failed to collect dependencies for " + artifact, e);
+        }
+    }
+
+    public DependencyResult resolveDependencies(Artifact artifact, List deps) throws AppModelResolverException {
+        final CollectRequest request = newCollectRequest(artifact);
+        request.setDependencies(deps);
+        try {
+            return repoSystem.resolveDependencies(repoSession,
+                    new DependencyRequest().setCollectRequest(request));
+        } catch (DependencyResolutionException e) {
+            throw new AppModelResolverException("Failed to resolve dependencies for " + artifact, e);
+        }
+    }
+
+    public DependencyResult resolveDependencies(Artifact artifact, String... excludedScopes) throws AppModelResolverException {
+        final ArtifactDescriptorResult descr = resolveDescriptor(artifact);
+        List deps = descr.getDependencies();
+        if(excludedScopes.length > 0) {
+            final Set excluded = new HashSet<>(Arrays.asList(excludedScopes));
+            deps = new ArrayList<>(deps.size());
+            for(Dependency dep : descr.getDependencies()) {
+                if(excluded.contains(dep.getScope())) {
+                    continue;
+                }
+                deps.add(dep);
+            }
+        }
+        try {
+            return repoSystem.resolveDependencies(repoSession,
+                    new DependencyRequest().setCollectRequest(
+                            new CollectRequest()
+                            .setRootArtifact(artifact)
+                            .setDependencies(deps)
+                            .setManagedDependencies(descr.getManagedDependencies())
+                            .setRepositories(descr.getRepositories())));
+        } catch (DependencyResolutionException e) {
+            throw new AppModelResolverException("Failed to resolve dependencies for " + artifact, e);
+        }
+    }
+
+    public void install(Artifact artifact) throws AppModelResolverException {
+        try {
+            repoSystem.install(repoSession, new InstallRequest().addArtifact(artifact));
+        } catch (InstallationException ex) {
+            throw new AppModelResolverException("Failed to install " + artifact, ex);
+        }
+    }
+
+    private CollectRequest newCollectRequest(Artifact artifact) throws AppModelResolverException {
+        return new CollectRequest()
+                .setRoot(new Dependency(artifact, JavaScopes.RUNTIME))
+                .setRepositories(remoteRepos);
+    }
+}
diff --git a/core/creator/src/main/java/io/quarkus/creator/resolver/aether/AppCreatorLocalRepositoryManager.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/MavenLocalRepositoryManager.java
similarity index 85%
rename from core/creator/src/main/java/io/quarkus/creator/resolver/aether/AppCreatorLocalRepositoryManager.java
rename to independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/MavenLocalRepositoryManager.java
index 92dbeb704e21b..aad07e7e6b325 100644
--- a/core/creator/src/main/java/io/quarkus/creator/resolver/aether/AppCreatorLocalRepositoryManager.java
+++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/MavenLocalRepositoryManager.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package io.quarkus.creator.resolver.aether;
+package io.quarkus.bootstrap.resolver.maven;
 
 import java.io.IOException;
 import java.nio.file.Files;
@@ -33,26 +33,24 @@
 import org.eclipse.aether.repository.LocalRepository;
 import org.eclipse.aether.repository.LocalRepositoryManager;
 import org.eclipse.aether.repository.RemoteRepository;
-
-import io.quarkus.creator.util.IoUtils;
+import io.quarkus.bootstrap.util.IoUtils;
 
 /**
  *
  * @author Alexey Loubyansky
  */
-public class AppCreatorLocalRepositoryManager implements LocalRepositoryManager {
+public class MavenLocalRepositoryManager implements LocalRepositoryManager {
 
     private final LocalRepositoryManager delegate;
     private final Path userLocalRepo;
     private final Path appCreatorRepo;
     private final boolean relinkResolvedArtifacts;
 
-    public AppCreatorLocalRepositoryManager(LocalRepositoryManager delegate, Path userLocalRepo) {
+    public MavenLocalRepositoryManager(LocalRepositoryManager delegate, Path userLocalRepo) {
         this(delegate, userLocalRepo, false);
     }
 
-    public AppCreatorLocalRepositoryManager(LocalRepositoryManager delegate, Path userLocalRepo,
-            boolean relinkResolvedArtifacts) {
+    public MavenLocalRepositoryManager(LocalRepositoryManager delegate, Path userLocalRepo, boolean relinkResolvedArtifacts) {
         this.delegate = delegate;
         this.userLocalRepo = userLocalRepo;
         this.appCreatorRepo = delegate.getRepository().getBasedir().toPath();
@@ -84,7 +82,7 @@ public String getPathForRemoteMetadata(Metadata metadata, RemoteRepository repos
         return delegate.getPathForRemoteMetadata(metadata, repository, context);
     }
 
-    void relink(String groupId, String artifactId, String classifier, String type, String version, Path p) {
+    public void relink(String groupId, String artifactId, String classifier, String type, String version, Path p) {
         final Path creatorRepoPath = getLocalPath(appCreatorRepo, groupId, artifactId, classifier, type, version);
         try {
             IoUtils.copy(p, creatorRepoPath);
@@ -96,16 +94,15 @@ void relink(String groupId, String artifactId, String classifier, String type, S
     @Override
     public LocalArtifactResult find(RepositorySystemSession session, LocalArtifactRequest request) {
         final LocalArtifactResult result = delegate.find(session, request);
-        if (result.isAvailable()) {
+        if(result.isAvailable()) {
             return result;
         }
         final Artifact artifact = request.getArtifact();
-        final Path userRepoPath = getLocalPath(userLocalRepo, artifact.getGroupId(), artifact.getArtifactId(),
-                artifact.getClassifier(), artifact.getExtension(), artifact.getVersion());
-        if (!Files.exists(userRepoPath)) {
+        final Path userRepoPath = getLocalPath(userLocalRepo, artifact.getGroupId(), artifact.getArtifactId(), artifact.getClassifier(), artifact.getExtension(), artifact.getVersion());
+        if(!Files.exists(userRepoPath)) {
             return result;
         }
-        if (relinkResolvedArtifacts) {
+        if(relinkResolvedArtifacts) {
             final Path creatorRepoPath = getLocalPath(appCreatorRepo, artifact.getGroupId(), artifact.getArtifactId(),
                     artifact.getClassifier(), artifact.getExtension(), artifact.getVersion());
             try {
@@ -130,13 +127,12 @@ public void add(RepositorySystemSession session, LocalArtifactRegistration reque
     @Override
     public LocalMetadataResult find(RepositorySystemSession session, LocalMetadataRequest request) {
         final LocalMetadataResult result = delegate.find(session, request);
-        if (result.getFile() != null && result.getFile().exists()) {
+        if(result.getFile() != null && result.getFile().exists()) {
             return result;
         }
         final Metadata metadata = request.getMetadata();
-        final Path userRepoPath = getMetadataPath(userLocalRepo, metadata.getGroupId(), metadata.getArtifactId(),
-                metadata.getType(), metadata.getVersion());
-        if (!Files.exists(userRepoPath)) {
+        final Path userRepoPath = getMetadataPath(userLocalRepo, metadata.getGroupId(), metadata.getArtifactId(), metadata.getType(), metadata.getVersion());
+        if(!Files.exists(userRepoPath)) {
             return result;
         }
         if (relinkResolvedArtifacts) {
@@ -166,17 +162,16 @@ private Path getMetadataPath(Path repoHome, String groupId, String artifactId, S
         for (String part : groupParts) {
             p = p.resolve(part);
         }
-        if (artifactId != null) {
+        if(artifactId != null) {
             p = p.resolve(artifactId);
         }
-        if (version != null) {
+        if(version != null) {
             p = p.resolve(version);
         }
         return p.resolve("maven-metadata-local.xml");
     }
 
-    private Path getLocalPath(Path repoHome, String groupId, String artifactId, String classifier, String type,
-            String version) {
+    private Path getLocalPath(Path repoHome, String groupId, String artifactId, String classifier, String type, String version) {
         Path p = repoHome;
         final String[] groupParts = groupId.split("\\.");
         for (String part : groupParts) {
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
new file mode 100644
index 0000000000000..d5ce56df6ac03
--- /dev/null
+++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/MavenModelBuilder.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2019 Red Hat, Inc.
+ *
+ * Licensed 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.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.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;
+
+/**
+ *
+ * @author Alexey Loubyansky
+ */
+public class MavenModelBuilder implements ModelBuilder {
+
+    private final ModelBuilder builder;
+    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) {
+                    }
+                });
+
+        modelResolver = wsModelResolver;
+    }
+
+    @Override
+    public ModelBuildingResult build(ModelBuildingRequest request) throws ModelBuildingException {
+        if(modelResolver != null) {
+            request.setWorkspaceModelResolver(modelResolver);
+        }
+        return builder.build(request);
+    }
+
+    @Override
+    public ModelBuildingResult build(ModelBuildingRequest request, ModelBuildingResult result) throws ModelBuildingException {
+        return builder.build(request, result);
+    }
+
+    @Override
+    public Result buildRawModel(File pomFile, int validationLevel, boolean locationTracking) {
+        return builder.buildRawModel(pomFile, validationLevel, locationTracking);
+    }
+
+}
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
new file mode 100644
index 0000000000000..6a2cf203719ca
--- /dev/null
+++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/MavenRepoInitializer.java
@@ -0,0 +1,423 @@
+/*
+ * Copyright 2018 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed 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.bootstrap.resolver.maven;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+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;
+import org.apache.maven.model.path.DefaultPathTranslator;
+import org.apache.maven.model.profile.DefaultProfileActivationContext;
+import org.apache.maven.model.profile.DefaultProfileSelector;
+import org.apache.maven.model.profile.activation.FileProfileActivator;
+import org.apache.maven.model.profile.activation.JdkVersionProfileActivator;
+import org.apache.maven.model.profile.activation.OperatingSystemProfileActivator;
+import org.apache.maven.model.profile.activation.PropertyProfileActivator;
+import org.apache.maven.model.resolution.WorkspaceModelResolver;
+import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
+import org.apache.maven.settings.Mirror;
+import org.apache.maven.settings.Profile;
+import org.apache.maven.settings.Repository;
+import org.apache.maven.settings.Settings;
+import org.apache.maven.settings.SettingsUtils;
+import org.apache.maven.settings.building.DefaultSettingsBuilderFactory;
+import org.apache.maven.settings.building.DefaultSettingsBuildingRequest;
+import org.apache.maven.settings.building.SettingsBuildingException;
+import org.apache.maven.settings.building.SettingsBuildingResult;
+import org.apache.maven.settings.building.SettingsProblem;
+import org.eclipse.aether.DefaultRepositorySystemSession;
+import org.eclipse.aether.RepositorySystem;
+import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory;
+import org.eclipse.aether.impl.DefaultServiceLocator;
+import org.eclipse.aether.repository.ArtifactRepository;
+import org.eclipse.aether.repository.Authentication;
+import org.eclipse.aether.repository.LocalRepository;
+import org.eclipse.aether.repository.Proxy;
+import org.eclipse.aether.repository.RemoteRepository;
+import org.eclipse.aether.repository.RepositoryPolicy;
+import org.eclipse.aether.spi.connector.RepositoryConnectorFactory;
+import org.eclipse.aether.spi.connector.transport.TransporterFactory;
+import org.eclipse.aether.transport.file.FileTransporterFactory;
+import org.eclipse.aether.transport.http.HttpTransporterFactory;
+import org.eclipse.aether.util.repository.AuthenticationBuilder;
+import org.eclipse.aether.util.repository.DefaultMirrorSelector;
+import org.eclipse.aether.util.repository.DefaultProxySelector;
+import org.jboss.logging.Logger;
+import io.quarkus.bootstrap.resolver.AppModelResolverException;
+import io.quarkus.bootstrap.util.PropertyUtils;
+
+/**
+ *
+ * @author Alexey Loubyansky
+ */
+public class MavenRepoInitializer {
+
+    private static final String DEFAULT_REMOTE_REPO_ID = "central";
+    private static final String DEFAULT_REMOTE_REPO_URL = "https://repo.maven.apache.org/maven2";
+
+    private static final String BASEDIR = "basedir";
+    private static final String MAVEN_CMD_LINE_ARGS = "MAVEN_CMD_LINE_ARGS";
+    private static final String DOT_M2 = ".m2";
+    private static final String MAVEN_HOME = "maven.home";
+    private static final String M2_HOME = "M2_HOME";
+    private static final String SETTINGS_XML = "settings.xml";
+
+    private static final String userHome = PropertyUtils.getUserHome();
+    private static final File userMavenConfigurationHome = new File(userHome, DOT_M2);
+    private static final String envM2Home = System.getenv(M2_HOME);
+    private static final File USER_SETTINGS_FILE;
+    private static final File GLOBAL_SETTINGS_FILE;
+
+    private static final CommandLine 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;
+        }
+
+        File f = userSettings != null ? resolveUserSettings(userSettings) : new File(userMavenConfigurationHome, SETTINGS_XML);
+        USER_SETTINGS_FILE = f != null && f.exists() ? f : null;
+
+        f = globalSettings != null ? resolveUserSettings(globalSettings) : new File(PropertyUtils.getProperty(MAVEN_HOME, envM2Home != null ? envM2Home : ""), "conf/settings.xml");
+        GLOBAL_SETTINGS_FILE = f != null && f.exists() ? f : null;
+    }
+
+    private static File resolveUserSettings(String settingsArg) {
+        File userSettings = new File(settingsArg);
+        if(userSettings.exists()) {
+            return userSettings;
+        }
+        String base = System.getenv("MAVEN_PROJECTBASEDIR"); // Root project base dir
+        if(base != null) {
+            userSettings = new File(base, settingsArg);
+            if(userSettings.exists()) {
+                return userSettings;
+            }
+        }
+        base = PropertyUtils.getProperty(BASEDIR); // current module project base dir
+        if(base != null) {
+            userSettings = new File(base, settingsArg);
+            if(userSettings.exists()) {
+                return userSettings;
+            }
+        }
+        userSettings = new File(userHome, settingsArg);
+        if(userSettings.exists()) {
+            return userSettings;
+        }
+        return null;
+    }
+
+    private static List remoteRepos;
+    private static Settings settings;
+
+    private static final Logger log = Logger.getLogger(MavenRepoInitializer.class);
+
+    public static RepositorySystem getRepositorySystem() {
+        return getRepositorySystem(false, null);
+    }
+
+    public static RepositorySystem getRepositorySystem(boolean offline, WorkspaceModelResolver wsModelResolver) {
+
+        final DefaultServiceLocator locator = MavenRepositorySystemUtils.newServiceLocator();
+        if(!offline) {
+            locator.addService(RepositoryConnectorFactory.class, BasicRepositoryConnectorFactory.class);
+            locator.addService(TransporterFactory.class, FileTransporterFactory.class);
+            locator.addService(TransporterFactory.class, HttpTransporterFactory.class);
+        }
+        locator.setServices(ModelBuilder.class, new MavenModelBuilder(wsModelResolver));
+        locator.setErrorHandler(new DefaultServiceLocator.ErrorHandler() {
+            @Override
+            public void serviceCreationFailed(Class type, Class impl, Throwable exception) {
+                log.error("Failed to initialize " + impl.getName() + " as a service implementing " + type.getName(), exception);
+            }
+        });
+
+        return locator.getService(RepositorySystem.class);
+    }
+
+    public static DefaultRepositorySystemSession newSession(RepositorySystem system) throws AppModelResolverException {
+        return newSession(system, getSettings());
+    }
+
+    public static DefaultRepositorySystemSession newSession(RepositorySystem system, Settings settings) {
+        final DefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession();
+
+        final org.apache.maven.settings.Proxy proxy = settings.getActiveProxy();
+        if (proxy != null) {
+            Authentication auth = null;
+            if(proxy.getUsername() != null) {
+                auth = new AuthenticationBuilder()
+                        .addUsername(proxy.getUsername())
+                        .addPassword(proxy.getPassword())
+                        .build();
+            }
+            session.setProxySelector(new DefaultProxySelector()
+                    .add(new Proxy(proxy.getProtocol(), proxy.getHost(), proxy.getPort(), auth), proxy.getNonProxyHosts()));
+        }
+
+        final List mirrors = settings.getMirrors();
+        if(mirrors != null && !mirrors.isEmpty()) {
+            final DefaultMirrorSelector ms = new DefaultMirrorSelector();
+            for(Mirror m : mirrors) {
+                ms.add(m.getId(), m.getUrl(), m.getLayout(), false, m.getMirrorOf(), m.getMirrorOfLayouts());
+            }
+            session.setMirrorSelector(ms);
+        }
+        final String localRepoPath = getLocalRepo(settings);
+        session.setLocalRepositoryManager(system.newLocalRepositoryManager(session, new LocalRepository(localRepoPath)));
+
+        session.setOffline(settings.isOffline());
+
+        if(mvnArgs != null) {
+            if(!session.isOffline() && mvnArgs.hasOption(CLIManager.OFFLINE)) {
+                session.setOffline(true);
+            }
+            if(mvnArgs.hasOption(CLIManager.SUPRESS_SNAPSHOT_UPDATES)) {
+                session.setUpdatePolicy(RepositoryPolicy.UPDATE_POLICY_NEVER);
+            } else if(mvnArgs.hasOption(CLIManager.UPDATE_SNAPSHOTS)) {
+                session.setUpdatePolicy(RepositoryPolicy.UPDATE_POLICY_ALWAYS);
+            }
+            if(mvnArgs.hasOption(CLIManager.CHECKSUM_FAILURE_POLICY)) {
+                session.setChecksumPolicy(RepositoryPolicy.CHECKSUM_POLICY_FAIL);
+            } else if(mvnArgs.hasOption(CLIManager.CHECKSUM_WARNING_POLICY)) {
+                session.setChecksumPolicy(RepositoryPolicy.CHECKSUM_POLICY_WARN);
+            }
+        }
+
+        return session;
+    }
+
+    public static List getRemoteRepos() throws AppModelResolverException {
+        if(remoteRepos != null) {
+            return remoteRepos;
+        }
+        return remoteRepos = Collections.unmodifiableList(getRemoteRepos(getSettings()));
+    }
+
+    public static List getRemoteRepos(Settings settings) throws AppModelResolverException {
+        final List remotes = new ArrayList<>();
+
+        final int profilesTotal = settings.getProfiles().size();
+        if(profilesTotal > 0) {
+            List modelProfiles = new ArrayList<>(profilesTotal);
+            for (Profile profile : settings.getProfiles()) {
+                modelProfiles.add(SettingsUtils.convertFromSettingsProfile(profile));
+            }
+
+            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 basedir = PropertyUtils.getProperty(BASEDIR);
+            final DefaultProfileActivationContext context = new DefaultProfileActivationContext()
+                    .setActiveProfileIds(activeProfiles)
+                    .setInactiveProfileIds(inactiveProfiles)
+                    .setSystemProperties(System.getProperties())
+                    .setProjectDirectory(basedir == null ? new File("") : new File(basedir));
+            final DefaultProfileSelector profileSelector = new DefaultProfileSelector()
+                    .addProfileActivator(new PropertyProfileActivator())
+                    .addProfileActivator(new JdkVersionProfileActivator())
+                    .addProfileActivator(new OperatingSystemProfileActivator())
+                    .addProfileActivator(new FileProfileActivator().setPathTranslator(new DefaultPathTranslator()));
+            modelProfiles = profileSelector.getActiveProfiles(modelProfiles, context, new ModelProblemCollector() {
+                public void add(ModelProblemCollectorRequest req) {
+                    log.error("Failed to activate a Maven profile: " + req.getMessage());
+                }
+            });
+            for(org.apache.maven.model.Profile modelProfile : modelProfiles) {
+                addProfileRepos(modelProfile, remotes);
+            }
+        }
+
+        // then it's the ones under active profiles
+        final List activeProfiles = settings.getActiveProfiles();
+        if (!activeProfiles.isEmpty()) {
+            for (String profileName : activeProfiles) {
+                final Profile profile = getProfile(profileName, settings);
+                if(profile != null) {
+                    addProfileRepos(profile, remotes);
+                }
+            }
+        }
+        // central must be there
+        if (remotes.isEmpty() || !includesDefaultRepo(remotes)) {
+            remotes.add(new RemoteRepository.Builder(DEFAULT_REMOTE_REPO_ID, "default", DEFAULT_REMOTE_REPO_URL)
+                    .setReleasePolicy(new RepositoryPolicy(true, RepositoryPolicy.UPDATE_POLICY_DAILY, RepositoryPolicy.CHECKSUM_POLICY_WARN))
+                    .setSnapshotPolicy(new RepositoryPolicy(false, RepositoryPolicy.UPDATE_POLICY_DAILY, RepositoryPolicy.CHECKSUM_POLICY_WARN))
+                    .build());
+        }
+
+        return remotes;
+    }
+
+    private static Profile getProfile(String name, Settings settings) throws AppModelResolverException {
+        final Profile profile = settings.getProfilesAsMap().get(name);
+        if(profile == null) {
+            unrecognizedProfile(name, true);
+        }
+        return profile;
+    }
+
+    private static void unrecognizedProfile(String name, boolean activate) {
+        final StringBuilder buf = new StringBuilder();
+        buf.append("The requested Maven profile \"").append(name).append("\" could not be ");
+        if(!activate) {
+            buf.append("de");
+        }
+        buf.append("activated because it does not exist.");
+        log.warn(buf.toString());
+    }
+
+    private static void addProfileRepos(final org.apache.maven.model.Profile profile, final List all) {
+        final List repositories = profile.getRepositories();
+        for (org.apache.maven.model.Repository repo : repositories) {
+            final RemoteRepository.Builder repoBuilder = new RemoteRepository.Builder(repo.getId(), repo.getLayout(), repo.getUrl());
+            org.apache.maven.model.RepositoryPolicy policy = repo.getReleases();
+            if (policy != null) {
+                repoBuilder.setReleasePolicy(toAetherRepoPolicy(policy));
+            }
+            policy = repo.getSnapshots();
+            if (policy != null) {
+                repoBuilder.setSnapshotPolicy(toAetherRepoPolicy(policy));
+            }
+            all.add(repoBuilder.build());
+        }
+    }
+
+    private static void addProfileRepos(final Profile profile, final List all) {
+        final List repositories = profile.getRepositories();
+        for (Repository repo : repositories) {
+            final RemoteRepository.Builder repoBuilder = new RemoteRepository.Builder(repo.getId(), repo.getLayout(), repo.getUrl());
+            org.apache.maven.settings.RepositoryPolicy policy = repo.getReleases();
+            if (policy != null) {
+                repoBuilder.setReleasePolicy(toAetherRepoPolicy(policy));
+            }
+            policy = repo.getSnapshots();
+            if (policy != null) {
+                repoBuilder.setSnapshotPolicy(toAetherRepoPolicy(policy));
+            }
+            all.add(repoBuilder.build());
+        }
+    }
+
+    public static Settings getSettings() throws AppModelResolverException {
+        if(settings != null) {
+            return settings;
+        }
+        final Settings effectiveSettings;
+        try {
+            final SettingsBuildingResult result = new DefaultSettingsBuilderFactory()
+                    .newInstance().build(new DefaultSettingsBuildingRequest()
+                            .setSystemProperties(System.getProperties())
+                            .setUserSettingsFile(USER_SETTINGS_FILE)
+                            .setGlobalSettingsFile(GLOBAL_SETTINGS_FILE));
+            final List problems = result.getProblems();
+            if(!problems.isEmpty()) {
+                for(SettingsProblem problem : problems) {
+                    switch(problem.getSeverity()) {
+                        case ERROR:
+                        case FATAL:
+                            throw new AppModelResolverException("Settings problem encountered at " + problem.getLocation(), problem.getException());
+                        default:
+                            log.warn("Settings problem encountered at " + problem.getLocation(), problem.getException());
+                    }
+                }
+            }
+            effectiveSettings = result.getEffectiveSettings();
+        } catch (SettingsBuildingException e) {
+            throw new AppModelResolverException("Failed to initialize Maven repository settings", e);
+        }
+
+        return settings = effectiveSettings;
+    }
+
+    public static String getLocalRepo(Settings settings) {
+        final String localRepo = settings.getLocalRepository();
+        return localRepo == null ? getDefaultLocalRepo() : localRepo;
+    }
+
+    private static String getDefaultLocalRepo() {
+        return new File(userMavenConfigurationHome, "repository").getAbsolutePath();
+    }
+
+    private static boolean includesDefaultRepo(List repositories) {
+        for (ArtifactRepository repository : repositories) {
+            if(repository.getId().equals(DEFAULT_REMOTE_REPO_ID)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static RepositoryPolicy toAetherRepoPolicy(org.apache.maven.model.RepositoryPolicy modelPolicy) {
+        return new RepositoryPolicy(modelPolicy.isEnabled(),
+                isEmpty(modelPolicy.getUpdatePolicy()) ? RepositoryPolicy.UPDATE_POLICY_DAILY : modelPolicy.getUpdatePolicy(),
+                        isEmpty(modelPolicy.getChecksumPolicy()) ? RepositoryPolicy.CHECKSUM_POLICY_WARN : modelPolicy.getChecksumPolicy());
+    }
+
+    private static RepositoryPolicy toAetherRepoPolicy(org.apache.maven.settings.RepositoryPolicy settingsPolicy) {
+        return new RepositoryPolicy(settingsPolicy.isEnabled(),
+                isEmpty(settingsPolicy.getUpdatePolicy()) ? RepositoryPolicy.UPDATE_POLICY_DAILY : settingsPolicy.getUpdatePolicy(),
+                        isEmpty(settingsPolicy.getChecksumPolicy()) ? RepositoryPolicy.CHECKSUM_POLICY_WARN : settingsPolicy.getChecksumPolicy());
+    }
+
+    private static boolean isEmpty(String str) {
+        return str == null || str.isEmpty();
+    }
+}
diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/SimpleDependencyGraphTransformationContext.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/SimpleDependencyGraphTransformationContext.java
new file mode 100644
index 0000000000000..985f3f4195424
--- /dev/null
+++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/SimpleDependencyGraphTransformationContext.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2019 Red Hat, Inc.
+ *
+ * Licensed 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.bootstrap.resolver.maven;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.collection.DependencyGraphTransformationContext;
+
+/**
+ *
+ * @author Alexey Loubyansky
+ */
+public class SimpleDependencyGraphTransformationContext implements DependencyGraphTransformationContext {
+
+    private final RepositorySystemSession session;
+    private final Map map = new HashMap<>(3);
+
+    public SimpleDependencyGraphTransformationContext(RepositorySystemSession session) {
+        this.session = session;
+    }
+
+    @Override
+    public RepositorySystemSession getSession() {
+        return session;
+    }
+
+    @Override
+    public Object get(Object key) {
+        return map.get(key);
+    }
+
+    @Override
+    public Object put(Object key, Object value) {
+        return map.put(key, value);
+    }
+
+}
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
new file mode 100644
index 0000000000000..254de4bf024f6
--- /dev/null
+++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/LocalProject.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright 2019 Red Hat, Inc.
+ *
+ * Licensed 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.bootstrap.resolver.maven.workspace;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.maven.model.Dependency;
+import org.apache.maven.model.Model;
+import org.apache.maven.model.Parent;
+import org.apache.maven.model.Resource;
+
+import io.quarkus.bootstrap.BootstrapConstants;
+import io.quarkus.bootstrap.BootstrapException;
+import io.quarkus.bootstrap.model.AppArtifact;
+import io.quarkus.bootstrap.model.AppArtifactKey;
+
+/**
+ *
+ * @author Alexey Loubyansky
+ */
+public class LocalProject {
+
+    public static final String PROJECT_GROUPID = "${project.groupId}";
+
+    private static final String PROJECT_BASEDIR = "${project.basedir}";
+    private static final String POM_XML = "pom.xml";
+
+    public static LocalProject load(Path path) throws BootstrapException {
+        return new LocalProject(readModel(locateCurrentProjectDir(path).resolve(POM_XML)), null);
+    }
+
+    public static LocalProject loadWorkspace(Path path) throws BootstrapException {
+        final Path currentProjectDir = locateCurrentProjectDir(path);
+        final LocalWorkspace ws = new LocalWorkspace();
+        final LocalProject project = load(ws, null, loadRootModel(currentProjectDir), currentProjectDir);
+        return project == null ? load(ws, null, readModel(currentProjectDir.resolve(POM_XML)), currentProjectDir) : project;
+    }
+
+    private static LocalProject load(LocalWorkspace workspace, LocalProject parent, Model model, Path currentProjectDir) throws BootstrapException {
+        final LocalProject project = new LocalProject(model, workspace);
+        if(parent != null) {
+            parent.modules.add(project);
+        }
+        LocalProject result = currentProjectDir == null || !currentProjectDir.equals(project.getDir()) ? null : project;
+        final List modules = project.getRawModel().getModules();
+        if (!modules.isEmpty()) {
+            Path dirArg = result == null ? currentProjectDir : null;
+            for (String module : modules) {
+                final LocalProject loaded = load(workspace, project, readModel(project.getDir().resolve(module).resolve(POM_XML)), dirArg);
+                if(loaded != null && result == null) {
+                    result = loaded;
+                    dirArg = null;
+                }
+            }
+        }
+        return result;
+    }
+
+    private static Model loadRootModel(Path currentProjectDir) throws BootstrapException {
+        Path pomXml = currentProjectDir.resolve(POM_XML);
+        Model model = readModel(pomXml);
+        Parent parent = model.getParent();
+        while(parent != null) {
+            if(parent.getRelativePath() != null) {
+                pomXml = pomXml.getParent().resolve(parent.getRelativePath()).normalize();
+                if(!Files.exists(pomXml)) {
+                    return model;
+                }
+                if(Files.isDirectory(pomXml)) {
+                    pomXml = pomXml.resolve(POM_XML);
+                }
+            } else {
+                pomXml = pomXml.getParent().getParent().resolve(POM_XML);
+                if(!Files.exists(pomXml)) {
+                    return model;
+                }
+            }
+            model = readModel(pomXml);
+            parent = model.getParent();
+        }
+        return model;
+    }
+
+    private static final Model readModel(Path pom) throws BootstrapException {
+        try {
+            final Model model = ModelUtils.readModel(pom);
+            model.setPomFile(pom.toFile());
+            return model;
+        } catch (IOException e) {
+            throw new BootstrapException("Failed to read " + pom, e);
+        }
+    }
+
+    private static Path locateCurrentProjectDir(Path path) throws BootstrapException {
+        Path p = path;
+        while(p != null) {
+            if(Files.exists(p.resolve(POM_XML))) {
+                return p;
+            }
+            p = p.getParent();
+        }
+        throw new BootstrapException("Failed to locate project pom.xml for " + path);
+    }
+
+    private final Model rawModel;
+    private final String groupId;
+    private final String artifactId;
+    private final String version;
+    private final Path dir;
+    private final LocalWorkspace workspace;
+    private final List modules = new ArrayList<>(0);
+
+    private LocalProject(Model rawModel, LocalWorkspace workspace) throws BootstrapException {
+        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.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;
+        }
+        if(workspace != null) {
+            workspace.addProject(this, rawModel.getPomFile().lastModified());
+        }
+    }
+
+    public String getGroupId() {
+        return groupId;
+    }
+
+    public String getArtifactId() {
+        return artifactId;
+    }
+
+    public String getVersion() {
+        return version;
+    }
+
+    public Path getDir() {
+        return dir;
+    }
+
+    public Path getOutputDir() {
+        return dir.resolve("target");
+    }
+
+    public Path getClassesDir() {
+        return getOutputDir().resolve("classes");
+    }
+
+    public Path getSourcesSourcesDir() {
+        if (getRawModel().getBuild() != null && getRawModel().getBuild().getSourceDirectory() != null) {
+            String originalValue = getRawModel().getBuild().getSourceDirectory();
+            return Paths.get(originalValue.startsWith(PROJECT_BASEDIR) ? originalValue.replace(PROJECT_BASEDIR, this.dir.toString()) : originalValue);
+        }
+        return dir.resolve("src/main/java");
+    }
+
+    public Path getResourcesSourcesDir() {
+        if(getRawModel().getBuild() != null && getRawModel().getBuild().getResources() != null) {
+            for (Resource i : getRawModel().getBuild().getResources()) {
+                //todo: support multiple resources dirs for config hot deployment
+                return Paths.get(i.getDirectory());
+            }
+        }
+        return dir.resolve("src/main/resources");
+    }
+
+    public Model getRawModel() {
+        return rawModel;
+    }
+
+    public LocalWorkspace getWorkspace() {
+        return workspace;
+    }
+
+    public AppArtifactKey getKey() {
+        return new AppArtifactKey(groupId, artifactId);
+    }
+
+    public AppArtifact getAppArtifact() {
+        final AppArtifact appArtifact = new AppArtifact(groupId, artifactId, BootstrapConstants.EMPTY, rawModel.getPackaging(), version);
+        appArtifact.setPath(getClassesDir());
+        return appArtifact;
+    }
+
+    public List getSelfWithLocalDeps() {
+        if(workspace == null) {
+            return Collections.singletonList(this);
+        }
+        final List ordered = new ArrayList<>();
+        collectSelfWithLocalDeps(this, new HashSet<>(),  ordered);
+        return ordered;
+    }
+
+    private static void collectSelfWithLocalDeps(LocalProject project, Set addedDeps, List ordered) {
+        if(!project.modules.isEmpty()) {
+            for(LocalProject module : project.modules) {
+                collectSelfWithLocalDeps(module, addedDeps, ordered);
+            }
+        }
+        for(Dependency dep : project.getRawModel().getDependencies()) {
+            final AppArtifactKey depKey = project.getKey(dep);
+            final LocalProject localDep = project.workspace.getProject(depKey);
+            if(localDep == null || addedDeps.contains(depKey)) {
+                continue;
+            }
+            collectSelfWithLocalDeps(localDep, addedDeps, ordered);
+        }
+        if(addedDeps.add(project.getKey())) {
+            ordered.add(project);
+        }
+    }
+
+    private AppArtifactKey getKey(Dependency dep) {
+        return new AppArtifactKey(PROJECT_GROUPID.equals(dep.getGroupId()) ? getGroupId() : dep.getGroupId(), dep.getArtifactId());
+    }
+}
diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/LocalWorkspace.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/LocalWorkspace.java
new file mode 100644
index 0000000000000..30a5979bb5d30
--- /dev/null
+++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/LocalWorkspace.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2019 Red Hat, Inc.
+ *
+ * Licensed 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.bootstrap.resolver.maven.workspace;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.maven.model.Model;
+import org.apache.maven.model.resolution.UnresolvableModelException;
+import org.apache.maven.model.resolution.WorkspaceModelResolver;
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.repository.WorkspaceReader;
+import org.eclipse.aether.repository.WorkspaceRepository;
+
+import io.quarkus.bootstrap.model.AppArtifactCoords;
+import io.quarkus.bootstrap.model.AppArtifactKey;
+
+/**
+ *
+ * @author Alexey Loubyansky
+ */
+public class LocalWorkspace implements WorkspaceModelResolver, WorkspaceReader {
+
+    private final Map projects = new HashMap<>();
+
+    private final WorkspaceRepository wsRepo = new WorkspaceRepository();
+    private AppArtifactKey lastFindVersionsKey;
+    private List lastFindVersions;
+    private long lastModified;
+    private int id = 1;
+
+    protected void addProject(LocalProject project, long lastModified) {
+        projects.put(project.getKey(), project);
+        if(lastModified > this.lastModified) {
+            this.lastModified = lastModified;
+        }
+        id = 31 * id + (int) (lastModified ^ (lastModified >>> 32));
+    }
+
+    public LocalProject getProject(String groupId, String artifactId) {
+        return getProject(new AppArtifactKey(groupId, artifactId));
+    }
+
+    public LocalProject getProject(AppArtifactKey key) {
+        return projects.get(key);
+    }
+
+    public long getLastModified() {
+        return lastModified;
+    }
+
+    public int getId() {
+        return id;
+    }
+
+    @Override
+    public Model resolveRawModel(String groupId, String artifactId, String versionConstraint)
+            throws UnresolvableModelException {
+        final LocalProject project = getProject(groupId, artifactId);
+        if(project == null || !project.getVersion().equals(versionConstraint)) {
+            return null;
+        }
+        return project.getRawModel();
+    }
+
+    @Override
+    public Model resolveEffectiveModel(String groupId, String artifactId, String versionConstraint)
+            throws UnresolvableModelException {
+        return null;
+    }
+
+    public Map getProjects() {
+        return projects;
+    }
+
+    @Override
+    public WorkspaceRepository getRepository() {
+        return wsRepo;
+    }
+
+    @Override
+    public File findArtifact(Artifact artifact) {
+        final LocalProject lp = getProject(artifact.getGroupId(), artifact.getArtifactId());
+        if (lp == null || !lp.getVersion().equals(artifact.getVersion())) {
+            return null;
+        }
+        final String type = artifact.getExtension();
+        if (type.equals(AppArtifactCoords.TYPE_JAR)) {
+            final File file = lp.getClassesDir().toFile();
+            if (file.exists()) {
+                return file;
+            }
+        } else if (type.equals(AppArtifactCoords.TYPE_POM)) {
+            final File file = lp.getDir().resolve("pom.xml").toFile();
+            if (file.exists()) {
+                return file;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public List findVersions(Artifact artifact) {
+        if (lastFindVersionsKey != null && artifact.getVersion().equals(lastFindVersions.get(0))
+                && lastFindVersionsKey.getArtifactId().equals(artifact.getArtifactId())
+                && lastFindVersionsKey.getGroupId().equals(artifact.getGroupId())) {
+            return lastFindVersions;
+        }
+        lastFindVersionsKey = new AppArtifactKey(artifact.getGroupId(), artifact.getArtifactId());
+        final LocalProject lp = getProject(lastFindVersionsKey);
+        if (lp == null || !lp.getVersion().equals(artifact.getVersion())) {
+            lastFindVersionsKey = null;
+            return Collections.emptyList();
+        }
+        return lastFindVersions = Collections.singletonList(artifact.getVersion());
+    }
+}
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
new file mode 100644
index 0000000000000..eddbd4c6d03e0
--- /dev/null
+++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/resolver/maven/workspace/ModelUtils.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright 2018 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed 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.bootstrap.resolver.maven.workspace;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+import org.apache.maven.model.Dependency;
+import org.apache.maven.model.Model;
+import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
+import org.apache.maven.model.io.xpp3.MavenXpp3Writer;
+import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
+
+import io.quarkus.bootstrap.model.AppArtifact;
+import io.quarkus.bootstrap.model.AppArtifactKey;
+import io.quarkus.bootstrap.model.AppDependency;
+
+
+/**
+ *
+ * @author Alexey Loubyansky
+ */
+public class ModelUtils {
+
+    private static final String STATE_ARTIFACT_INITIAL_VERSION = "1";
+
+    /**
+     * Returns the provisioning state artifact for the given application artifact
+     *
+     * @param appArtifact  application artifact
+     * @return  provisioning state artifact
+     */
+    public static AppArtifact getStateArtifact(AppArtifact appArtifact) {
+        return new AppArtifact(appArtifact.getGroupId() + ".quarkus.curate",
+                appArtifact.getArtifactId(),
+                "",
+                "pom",
+                STATE_ARTIFACT_INITIAL_VERSION);
+    }
+
+    /**
+     * Filters out non-platform from application POM dependencies.
+     *
+     * @param deps  POM model application dependencies
+     * @param appDeps  resolved application dependencies
+     * @return  dependencies that can be checked for updates
+     * @throws AppCreatorException  in case of a failure
+     */
+    public static List getUpdateCandidates(List deps, List appDeps, Set groupIds) throws IOException {
+        final Map appDepMap = new LinkedHashMap<>(appDeps.size());
+        for(AppDependency appDep : appDeps) {
+            final AppArtifact appArt = appDep.getArtifact();
+            appDepMap.put(new AppArtifactKey(appArt.getGroupId(), appArt.getArtifactId(), appArt.getClassifier()), appDep);
+        }
+        final List updateCandidates = new ArrayList<>(deps.size());
+        // it's critical to preserve the order of the dependencies from the pom
+        for(Dependency dep : deps) {
+            if(!groupIds.contains(dep.getGroupId()) || "test".equals(dep.getScope())) {
+                continue;
+            }
+            final AppDependency appDep = appDepMap.remove(new AppArtifactKey(dep.getGroupId(), dep.getArtifactId(), dep.getClassifier()));
+            if(appDep == null) {
+                // This normally would be a dependency that's missing test in the artifact's pom
+                // but is marked as such in one of artifact's parent poms
+                //throw new AppCreatorException("Failed to locate dependency " + new AppArtifact(dep.getGroupId(), dep.getArtifactId(), dep.getClassifier(), dep.getType(), dep.getVersion()) + " present in pom.xml among resolved application dependencies");
+                continue;
+            }
+            updateCandidates.add(appDep);
+        }
+        for(AppDependency appDep : appDepMap.values()) {
+            if(groupIds.contains(appDep.getArtifact().getGroupId())) {
+                updateCandidates.add(appDep);
+            }
+        }
+        return updateCandidates;
+    }
+
+    public static AppArtifact resolveAppArtifact(Path appJar) throws IOException {
+        try (FileSystem fs = FileSystems.newFileSystem(appJar, null)) {
+            final Path metaInfMaven = fs.getPath("META-INF", "maven");
+            if (Files.exists(metaInfMaven)) {
+                try (DirectoryStream groupIds = Files.newDirectoryStream(metaInfMaven)) {
+                    for (Path groupIdPath : groupIds) {
+                        if (!Files.isDirectory(groupIdPath)) {
+                            continue;
+                        }
+                        try (DirectoryStream artifactIds = Files.newDirectoryStream(groupIdPath)) {
+                            for (Path artifactIdPath : artifactIds) {
+                                if (!Files.isDirectory(artifactIdPath)) {
+                                    continue;
+                                }
+                                final Path propsPath = artifactIdPath.resolve("pom.properties");
+                                if (Files.exists(propsPath)) {
+                                    final Properties props = loadPomProps(appJar, artifactIdPath);
+                                    return new AppArtifact(props.getProperty("groupId"), props.getProperty("artifactId"), props.getProperty("version"));
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            throw new IOException("Failed to located META-INF/maven///pom.properties in " + appJar);
+        }
+    }
+
+    public static Model readAppModel(Path appJar, AppArtifact appArtifact) throws IOException {
+        try (FileSystem fs = FileSystems.newFileSystem(appJar, null)) {
+            final Path pomXml = fs.getPath("META-INF", "maven", appArtifact.getGroupId(), appArtifact.getArtifactId(), "pom.xml");
+            if(!Files.exists(pomXml)) {
+                throw new IOException("Failed to located META-INF/maven///pom.xml in " + appJar);
+            }
+            return readModel(pomXml);
+        }
+    }
+
+    static Model readAppModel(Path appJar) throws IOException {
+        try (FileSystem fs = FileSystems.newFileSystem(appJar, null)) {
+            final Path metaInfMaven = fs.getPath("META-INF", "maven");
+            if (Files.exists(metaInfMaven)) {
+                try (DirectoryStream groupIds = Files.newDirectoryStream(metaInfMaven)) {
+                    for (Path groupIdPath : groupIds) {
+                        if (!Files.isDirectory(groupIdPath)) {
+                            continue;
+                        }
+                        try (DirectoryStream artifactIds = Files.newDirectoryStream(groupIdPath)) {
+                            for (Path artifactIdPath : artifactIds) {
+                                if (!Files.isDirectory(artifactIdPath)) {
+                                    continue;
+                                }
+                                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;
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            throw new IOException("Failed to located META-INF/maven///pom.xml in " + appJar);
+        }
+    }
+
+    private static Properties loadPomProps(Path appJar, Path artifactIdPath) throws IOException {
+        final Path propsPath = artifactIdPath.resolve("pom.properties");
+        if(!Files.exists(propsPath)) {
+            throw new IOException("Failed to located META-INF/maven///pom.properties in " + appJar);
+        }
+        final Properties props = new Properties();
+        try(BufferedReader reader = Files.newBufferedReader(propsPath)) {
+            props.load(reader);
+        }
+        return props;
+    }
+
+    public static Model readModel(final Path pomXml) throws IOException {
+        try(BufferedReader reader = Files.newBufferedReader(pomXml)) {
+            return new MavenXpp3Reader().read(reader);
+        } catch (XmlPullParserException e) {
+            throw new IOException("Failed to parse application POM model", e);
+        }
+    }
+
+    public static void persistModel(Path pomFile, Model model) throws IOException {
+        final MavenXpp3Writer xpp3Writer = new MavenXpp3Writer();
+        try (BufferedWriter pomFileWriter = Files.newBufferedWriter(pomFile)) {
+            xpp3Writer.write(pomFileWriter, model);
+        }
+    }
+}
diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/util/BootstrapUtils.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/util/BootstrapUtils.java
new file mode 100644
index 0000000000000..fb2a78c48df03
--- /dev/null
+++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/util/BootstrapUtils.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2019 Red Hat, Inc.
+ *
+ * Licensed 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.bootstrap.util;
+
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
+
+/**
+ *
+ * @author Alexey Loubyansky
+ */
+public class BootstrapUtils {
+
+    public static int logUrls(ClassLoader cl) {
+        int depth = 0;
+        if(cl.getParent() != null) {
+            depth += logUrls(cl.getParent());
+        }
+        final StringBuilder buf = new StringBuilder();
+        final String offset;
+        if(depth == 0) {
+            offset = "";
+        } else {
+            for (int i = 0; i < depth; ++i) {
+                buf.append("  ");
+            }
+            offset = buf.toString();
+        }
+        if(!(cl instanceof java.net.URLClassLoader)) {
+            System.out.println(buf.append(cl.getClass().getName()).toString());
+        } else {
+            final java.net.URL[] urls = ((java.net.URLClassLoader) cl).getURLs();
+            final String[] urlStrs;
+            if (urls.length == 1) {
+                final Path p = Paths.get(urls[0].getFile());
+                if(Files.isDirectory(p)) {
+                    urlStrs = new String[] {urls[0].toExternalForm()};
+                } else {
+                    try (FileSystem fs = FileSystems.newFileSystem(p, null)) {
+                        Path path = fs.getPath("META-INF/MANIFEST.MF");
+                        if (!Files.exists(path)) {
+                            throw new IllegalStateException("Failed to locate the manifest");
+                        }
+
+                        final Manifest manifest;
+                        try (InputStream input = Files.newInputStream(path)) {
+                            manifest = new Manifest(input);
+                        }
+                        Attributes attrs = manifest.getMainAttributes();
+                        urlStrs = attrs.getValue("Class-Path").split("\\s+");
+                    } catch (Exception e1) {
+                        throw new IllegalStateException("Failed to read MANIFEST.MF from " + urls[0]);
+                    }
+                }
+            } else {
+                urlStrs = new String[urls.length];
+                for (int i = 0; i < urls.length; ++i) {
+                    final java.net.URL url = urls[i];
+                    urlStrs[i] = url.toExternalForm();
+                }
+            }
+            java.util.Arrays.sort(urlStrs);
+            int i = 0;
+            System.out.println(offset + cl);
+            while(i < urlStrs.length) {
+                System.out.println(offset + (i + 1) + ") " + urlStrs[i++]);
+            }
+
+        }
+        return depth + 1;
+    }
+
+    public static void logUrlDiff(ClassLoader cl1, String cl1Header, ClassLoader cl2, String cl2Header) {
+
+        final Set cl1Urls = new HashSet<>();
+        collectUrls(cl1, cl1Urls);
+/*
+        URLClassLoader classLoader = (URLClassLoader) cl1;
+        try(FileSystem fs = FileSystems.newFileSystem(Paths.get(classLoader.getURLs()[0].getFile()), null)) {
+            Path path = fs.getPath("META-INF/MANIFEST.MF");
+            if(!Files.exists(path)) {
+                throw new IllegalStateException("Failed to locate the manifest");
+            }
+
+            final Manifest manifest;
+            try(InputStream input = Files.newInputStream(path)) {
+                manifest = new Manifest(input);
+            }
+            Attributes attrs = manifest.getMainAttributes();
+            String[] urlStrs = attrs.getValue("Class-Path").split("\\s+");
+            for(String urlStr : urlStrs) {
+                cl1Urls.add(new URL(urlStr).getFile());
+            }
+        } catch (IOException e1) {
+            // TODO Auto-generated catch block
+            e1.printStackTrace();
+        }
+*/
+        final Set cl2Urls = new HashSet<>();
+        collectUrls(cl2, cl2Urls);
+
+        int commonUrls = 0;
+        Iterator i = cl1Urls.iterator();
+        while(i.hasNext()) {
+            String next = i.next();
+            if(cl2Urls.remove(next)) {
+                i.remove();
+                ++commonUrls;
+            }
+        }
+        System.out.println("URLs not in " + cl2Header + ":");
+        List list = new ArrayList<>(cl1Urls);
+        Collections.sort(list);
+        for(String s: list) {
+            System.out.println(s);
+        }
+
+        System.out.println("URLs not in " + cl1Header + ":");
+        list = new ArrayList<>(cl2Urls);
+        Collections.sort(list);
+        for(String s : list) {
+            System.out.println(s);
+        }
+        System.out.println("Common URLs: " + commonUrls);
+    }
+
+    private static void collectUrls(ClassLoader cl, Set set) {
+        final ClassLoader parent = cl.getParent();
+        if(parent != null) {
+            collectUrls(parent, set);
+        }
+        if(!(cl instanceof URLClassLoader)) {
+            return;
+        }
+        final URL[] urls = ((URLClassLoader)cl).getURLs();
+        for(URL url : urls) {
+            set.add(url.getFile());
+        }
+    }
+}
diff --git a/core/creator/src/main/java/io/quarkus/creator/util/IoUtils.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/util/IoUtils.java
similarity index 93%
rename from core/creator/src/main/java/io/quarkus/creator/util/IoUtils.java
rename to independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/util/IoUtils.java
index 59705591461ec..3053c7407bf3b 100644
--- a/core/creator/src/main/java/io/quarkus/creator/util/IoUtils.java
+++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/util/IoUtils.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package io.quarkus.creator.util;
+package io.quarkus.bootstrap.util;
 
 import java.io.BufferedReader;
 import java.io.IOException;
@@ -36,6 +36,7 @@
 import java.util.EnumSet;
 import java.util.UUID;
 
+
 /**
  *
  * @author Alexey Loubyansky
@@ -86,10 +87,9 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
                     }
                     return FileVisitResult.CONTINUE;
                 }
-
                 @Override
                 public FileVisitResult postVisitDirectory(Path dir, IOException e)
-                        throws IOException {
+                    throws IOException {
                     if (e == null) {
                         try {
                             Files.delete(dir);
@@ -107,7 +107,7 @@ public FileVisitResult postVisitDirectory(Path dir, IOException e)
     }
 
     public static Path copy(Path source, Path target) throws IOException {
-        if (Files.isDirectory(source)) {
+        if(Files.isDirectory(source)) {
             Files.createDirectories(target);
         } else {
             Files.createDirectories(target.getParent());
@@ -116,22 +116,21 @@ public static Path copy(Path source, Path target) throws IOException {
                 new SimpleFileVisitor() {
                     @Override
                     public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
-                            throws IOException {
-                        final Path targetDir = target.resolve(source.relativize(dir));
+                        throws IOException {
+                        final Path targetDir = target.resolve(source.relativize(dir).toString());
                         try {
                             Files.copy(dir, targetDir);
                         } catch (FileAlreadyExistsException e) {
-                            if (!Files.isDirectory(targetDir)) {
-                                throw e;
-                            }
+                             if (!Files.isDirectory(targetDir)) {
+                                 throw e;
+                             }
                         }
                         return FileVisitResult.CONTINUE;
                     }
-
                     @Override
                     public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
-                            throws IOException {
-                        Files.copy(file, target.resolve(source.relativize(file)), StandardCopyOption.REPLACE_EXISTING);
+                        throws IOException {
+                        Files.copy(file, target.resolve(source.relativize(file).toString()), StandardCopyOption.REPLACE_EXISTING);
                         return FileVisitResult.CONTINUE;
                     }
                 });
diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/util/PropertyUtils.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/util/PropertyUtils.java
new file mode 100644
index 0000000000000..ae8b31dbb388f
--- /dev/null
+++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/util/PropertyUtils.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2018 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed 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.bootstrap.util;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Locale;
+
+/**
+*
+* @author Alexey Loubyansky
+*/
+public class PropertyUtils {
+
+    private static final String OS_NAME = "os.name";
+    private static final String USER_HOME = "user.home";
+    private static final String WINDOWS = "windows";
+
+    private static final String FALSE = "false";
+    private static final String TRUE = "true";
+
+    private PropertyUtils() {
+    }
+
+   public static boolean isWindows() {
+       return getProperty(OS_NAME).toLowerCase(Locale.ENGLISH).indexOf(WINDOWS) >= 0;
+   }
+
+   public static String getUserHome() {
+       return getProperty(USER_HOME);
+   }
+
+   public static String getProperty(final String name, String defValue) {
+       assert name != null : "name is null";
+       final SecurityManager sm = System.getSecurityManager();
+       if(sm != null) {
+           return AccessController.doPrivileged(new PrivilegedAction(){
+               @Override
+               public String run() {
+                   return System.getProperty(name, defValue);
+               }});
+       } else {
+           return System.getProperty(name, defValue);
+       }
+   }
+
+   public static String getProperty(final String name) {
+       assert name != null : "name is null";
+       final SecurityManager sm = System.getSecurityManager();
+       if(sm != null) {
+           return AccessController.doPrivileged(new PrivilegedAction(){
+               @Override
+               public String run() {
+                   return System.getProperty(name);
+               }});
+       } else {
+           return System.getProperty(name);
+       }
+   }
+
+   public static final Boolean getBooleanOrNull(String name) {
+	   final String value = getProperty(name);
+	   return value == null ? null : Boolean.parseBoolean(value);
+   }
+
+   public static final boolean getBoolean(String name, boolean notFoundValue) {
+       final String value = getProperty(name, (notFoundValue ? TRUE : FALSE));
+       return value.isEmpty() ? true : Boolean.parseBoolean(value);
+   }
+}
diff --git a/core/creator/src/main/java/io/quarkus/creator/util/ZipUtils.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/util/ZipUtils.java
similarity index 84%
rename from core/creator/src/main/java/io/quarkus/creator/util/ZipUtils.java
rename to independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/util/ZipUtils.java
index 294ba447f556b..5681715ae92b7 100644
--- a/core/creator/src/main/java/io/quarkus/creator/util/ZipUtils.java
+++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/util/ZipUtils.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package io.quarkus.creator.util;
+package io.quarkus.bootstrap.util;
 
 import java.io.IOException;
 import java.net.URI;
@@ -45,7 +45,9 @@ public class ZipUtils {
 
     public static void unzip(Path zipFile, Path targetDir) throws IOException {
         try {
-            Files.createDirectories(targetDir);
+            if (!Files.exists(targetDir)) {
+                Files.createDirectories(targetDir);
+            }
         } catch (FileAlreadyExistsException fae) {
             throw new IOException("Could not create directory '" + targetDir + "' as a file already exists with the same name");
         }
@@ -71,22 +73,20 @@ public static void copyFromZip(Path source, Path target) throws IOException {
                 new SimpleFileVisitor() {
                     @Override
                     public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
-                            throws IOException {
+                        throws IOException {
                         final Path targetDir = target.resolve(source.relativize(dir).toString());
                         try {
                             Files.copy(dir, targetDir);
                         } catch (FileAlreadyExistsException e) {
-                            if (!Files.isDirectory(targetDir))
-                                throw e;
+                             if (!Files.isDirectory(targetDir))
+                                 throw e;
                         }
                         return FileVisitResult.CONTINUE;
                     }
-
                     @Override
                     public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
-                            throws IOException {
-                        Files.copy(file, target.resolve(source.relativize(file).toString()),
-                                StandardCopyOption.REPLACE_EXISTING);
+                        throws IOException {
+                        Files.copy(file, target.resolve(source.relativize(file).toString()), StandardCopyOption.REPLACE_EXISTING);
                         return FileVisitResult.CONTINUE;
                     }
                 });
@@ -94,9 +94,9 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
 
     public static void zip(Path src, Path zipFile) throws IOException {
         try (FileSystem zipfs = newZip(zipFile)) {
-            if (Files.isDirectory(src)) {
+            if(Files.isDirectory(src)) {
                 try (DirectoryStream stream = Files.newDirectoryStream(src)) {
-                    for (Path srcPath : stream) {
+                    for(Path srcPath : stream) {
                         copyToZip(src, srcPath, zipfs);
                     }
                 }
@@ -115,23 +115,21 @@ private static void copyToZip(Path srcRoot, Path srcPath, FileSystem zipfs) thro
                 new SimpleFileVisitor() {
                     @Override
                     public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
-                            throws IOException {
+                        throws IOException {
                         final Path targetDir = zipfs.getPath(srcRoot.relativize(dir).toString());
                         try {
                             Files.copy(dir, targetDir);
                         } catch (FileAlreadyExistsException e) {
-                            if (!Files.isDirectory(targetDir)) {
-                                throw e;
-                            }
+                             if (!Files.isDirectory(targetDir)) {
+                                 throw e;
+                             }
                         }
                         return FileVisitResult.CONTINUE;
                     }
-
                     @Override
                     public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
-                            throws IOException {
-                        Files.copy(file, zipfs.getPath(srcRoot.relativize(file).toString()),
-                                StandardCopyOption.REPLACE_EXISTING);
+                        throws IOException {
+                        Files.copy(file, zipfs.getPath(srcRoot.relativize(file).toString()), StandardCopyOption.REPLACE_EXISTING);
                         return FileVisitResult.CONTINUE;
                     }
                 });
@@ -140,11 +138,10 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
     /**
      * This call is not thread safe, a single of FileSystem can be created for the
      * profided uri until it is closed.
-     * 
      * @param uri The uri to the zip file.
      * @param env Env map.
      * @return A new FileSystem.
-     * @throws IOException in case of a failure
+     * @throws IOException  in case of a failure
      */
     public static FileSystem newFileSystem(URI uri, Map env) throws IOException {
         // If Multi threading required, logic should be added to wrap this fs
@@ -155,12 +152,11 @@ public static FileSystem newFileSystem(URI uri, Map env) throws IOExc
 
     /**
      * This call is thread safe, a new FS is created for each invocation.
-     * 
      * @param path The zip file.
      * @return A new FileSystem instance
-     * @throws IOException in case of a failure
+     * @throws IOException  in case of a failure
      */
-    public static FileSystem newFileSystem(Path path) throws IOException {
-        return FileSystems.newFileSystem(path, null);
-    }
+     public static FileSystem newFileSystem(Path path) throws IOException {
+         return FileSystems.newFileSystem(path, null);
+     }
 }
diff --git a/core/creator/src/test/java/io/quarkus/creator/resolver/test/CollectDependenciesBase.java b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/CollectDependenciesBase.java
similarity index 55%
rename from core/creator/src/test/java/io/quarkus/creator/resolver/test/CollectDependenciesBase.java
rename to independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/CollectDependenciesBase.java
index 9904eb458306a..0b4c1c60b28dc 100644
--- a/core/creator/src/test/java/io/quarkus/creator/resolver/test/CollectDependenciesBase.java
+++ b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/CollectDependenciesBase.java
@@ -14,17 +14,18 @@
  * limitations under the License.
  */
 
-package io.quarkus.creator.resolver.test;
+package io.quarkus.bootstrap.resolver;
 
 import static org.junit.Assert.assertEquals;
 
+import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
 import org.junit.Test;
 
-import io.quarkus.creator.AppDependency;
+import io.quarkus.bootstrap.model.AppDependency;
 
 /**
  *
@@ -42,20 +43,33 @@ public void setup() throws Exception {
         setupDependencies();
     }
 
-    protected abstract void setupDependencies();
+    protected abstract void setupDependencies() throws Exception;
 
     @Test
     public void testCollectedDependencies() throws Exception {
         install(root);
-        final List resolvedDeps = resolver.collectDependencies(root.toAppArtifact());
+        final List resolvedDeps = resolver.resolveModel(root.toAppArtifact()).getAllDependencies();
         assertEquals(expectedResult, resolvedDeps);
     }
 
-    protected void install(TsArtifact dep, boolean collected) {
-        install(dep);
-        if (collected) {
-            addCollectedDep(dep);
+    protected TsArtifact install(TsArtifact dep, boolean collected) {
+        return install(dep, collected ? "compile" : null);
+    }
+
+    protected TsArtifact install(TsArtifact dep, String collectedInScope) {
+        return install(dep, null, collectedInScope);
+    }
+
+    protected TsArtifact install(TsArtifact dep, Path p, boolean collected) {
+        return install(dep, p, collected ? "compile" : null);
+    }
+
+    protected TsArtifact install(TsArtifact dep, Path p, String collectedInScope) {
+        install(dep, p);
+        if(collectedInScope != null) {
+            addCollectedDep(dep, collectedInScope, false);
         }
+        return dep;
     }
 
     protected void installAsDep(TsArtifact dep) {
@@ -63,31 +77,43 @@ protected void installAsDep(TsArtifact dep) {
     }
 
     protected void installAsDep(TsArtifact dep, boolean collected) {
-        installAsDep(new TsDependency(dep), collected);
+        installAsDep(dep, null, collected);
+    }
+
+    protected void installAsDep(TsArtifact dep, Path p, boolean collected) {
+        installAsDep(new TsDependency(dep), p, collected);
     }
 
     protected void installAsDep(TsDependency dep) {
-        installAsDep(dep, true);
+        installAsDep(dep, null);
+    }
+
+    protected void installAsDep(TsDependency dep, Path p) {
+        installAsDep(dep, p, true);
     }
 
     protected void installAsDep(TsDependency dep, boolean collected) {
+        installAsDep(dep, null, collected);
+    }
+
+    protected void installAsDep(TsDependency dep, Path p, boolean collected) {
         final TsArtifact artifact = dep.artifact;
-        install(artifact);
+        install(artifact, p);
         root.addDependency(dep);
-        if (!collected) {
+        if(!collected) {
             return;
         }
-        addCollectedDep(artifact, dep.scope == null ? "compile" : dep.scope);
+        addCollectedDep(artifact, dep.scope == null ? "compile" : dep.scope, dep.optional);
     }
 
     protected void addCollectedDep(final TsArtifact artifact) {
-        addCollectedDep(artifact, "compile");
+        addCollectedDep(artifact, "compile", false);
     }
 
-    protected void addCollectedDep(final TsArtifact artifact, final String scope) {
-        if (expectedResult.isEmpty()) {
+    protected void addCollectedDep(final TsArtifact artifact, final String scope, boolean optional) {
+        if(expectedResult.isEmpty()) {
             expectedResult = new ArrayList<>();
         }
-        expectedResult.add(new AppDependency(artifact.toAppArtifact(), scope));
+        expectedResult.add(new AppDependency(artifact.toAppArtifact(), scope, optional));
     }
 }
diff --git a/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/PropsBuilder.java b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/PropsBuilder.java
new file mode 100644
index 0000000000000..28568590af318
--- /dev/null
+++ b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/PropsBuilder.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2019 Red Hat, Inc.
+ *
+ * Licensed 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.bootstrap.resolver;
+
+import java.util.Properties;
+
+/**
+ *
+ * @author Alexey Loubyansky
+ */
+public class PropsBuilder {
+
+    public static Properties build(String name, Object value) {
+        return build(name, value.toString());
+    }
+
+    public static Properties build(String name, String value) {
+        final Properties props = new Properties();
+        props.setProperty(name, value);
+        return props;
+    }
+
+    public static PropsBuilder newInstance() {
+        return new PropsBuilder();
+    }
+
+    private final Properties props = new Properties();
+
+    private PropsBuilder() {
+    }
+
+    public PropsBuilder set(String name, String value) {
+        props.setProperty(name, value);
+        return this;
+    }
+
+    public Properties build() {
+        return props;
+    }
+}
diff --git a/core/creator/src/test/java/io/quarkus/creator/resolver/test/ResolverSetupCleanup.java b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/ResolverSetupCleanup.java
similarity index 60%
rename from core/creator/src/test/java/io/quarkus/creator/resolver/test/ResolverSetupCleanup.java
rename to independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/ResolverSetupCleanup.java
index c135f91e91b73..f0fdc39706c85 100644
--- a/core/creator/src/test/java/io/quarkus/creator/resolver/test/ResolverSetupCleanup.java
+++ b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/ResolverSetupCleanup.java
@@ -15,20 +15,18 @@
  * limitations under the License.
  */
 
-package io.quarkus.creator.resolver.test;
+package io.quarkus.bootstrap.resolver;
 
+import java.io.IOException;
 import java.nio.file.Path;
-import java.util.Collections;
-import java.util.List;
+import java.util.UUID;
 
+import io.quarkus.bootstrap.resolver.AppModelResolverException;
+import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver;
+import io.quarkus.bootstrap.util.IoUtils;
 import org.junit.After;
 import org.junit.Before;
 
-import io.quarkus.creator.AppCreatorException;
-import io.quarkus.creator.AppDependency;
-import io.quarkus.creator.resolver.aether.AetherArtifactResolver;
-import io.quarkus.creator.util.IoUtils;
-
 /**
  *
  * @author Alexey Loubyansky
@@ -37,30 +35,42 @@ public class ResolverSetupCleanup {
 
     protected Path workDir;
     protected Path repoHome;
-    protected AetherArtifactResolver resolver;
+    protected BootstrapAppModelResolver resolver;
     protected TsRepoBuilder repo;
 
     @Before
     public void setup() throws Exception {
         workDir = IoUtils.createRandomTmpDir();
         repoHome = IoUtils.mkdirs(workDir.resolve("repo"));
-        resolver = AetherArtifactResolver.getInstance(repoHome, Collections.emptyList());
+        resolver = initResolver();
         repo = TsRepoBuilder.getInstance(resolver, workDir);
     }
 
     @After
     public void cleanup() {
-        if (workDir != null) {
+        if(workDir != null) {
             IoUtils.recursiveDelete(workDir);
         }
     }
 
+    protected BootstrapAppModelResolver initResolver() throws AppModelResolverException {
+        return new BootstrapAppModelResolver(MavenArtifactResolver.builder()
+                .setRepoHome(repoHome)
+                .setOffline(true)
+                .build());
+    }
+
+    protected TsJar newJar() throws IOException {
+        return new TsJar(workDir.resolve(UUID.randomUUID().toString()));
+    }
+
     protected TsArtifact install(TsArtifact artifact) {
         repo.install(artifact);
         return artifact;
     }
 
-    protected List collectDeps(TsArtifact artifact) throws AppCreatorException {
-        return resolver.collectDependencies(artifact.toAppArtifact());
+    protected TsArtifact install(TsArtifact artifact, Path p) {
+        repo.install(artifact, p);
+        return artifact;
     }
 }
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
new file mode 100644
index 0000000000000..6b1e75cd1dead
--- /dev/null
+++ b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/TsArtifact.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright 2019 Red Hat, Inc.
+ *
+ * Licensed 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.bootstrap.resolver;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.maven.model.Model;
+
+import io.quarkus.bootstrap.model.AppArtifact;
+
+/**
+ *
+ * @author Alexey Loubyansky
+ */
+public class TsArtifact {
+
+    public static final String DEFAULT_GROUP_ID = "io.quarkus.bootstrap.test";
+    public static final String DEFAULT_VERSION = "1";
+
+    public static final String TYPE_JAR = "jar";
+    public static final String TYPE_POM = "pom";
+    public static final String TYPE_TXT = "txt";
+
+    public static final String EMPTY = "";
+
+    private static final String MODEL_VERSION = "4.0.0";
+
+    public static TsArtifact ga(String artifactId) {
+        return ga(DEFAULT_GROUP_ID, artifactId);
+    }
+
+    public static TsArtifact ga(String groupId, String artifactId) {
+        return new TsArtifact(groupId, artifactId, null);
+    }
+
+    public static TsArtifact jar(String artifactId) {
+        return jar(artifactId, DEFAULT_VERSION);
+    }
+
+    public static TsArtifact jar(String artifactId, String version) {
+        return new TsArtifact(DEFAULT_GROUP_ID, artifactId, EMPTY, TYPE_JAR, version);
+    }
+
+    interface ContentProvider {
+        Path getPath(Path workDir) throws IOException;
+    }
+
+    protected final String groupId;
+    protected final String artifactId;
+    protected final String classifier;
+    protected final String type;
+    protected final String version;
+
+    private List deps = Collections.emptyList();
+    private List extDeps = Collections.emptyList();
+
+    protected ContentProvider content;
+
+    public String getGroupId() {
+        return groupId;
+    }
+
+    public String getArtifactId() {
+        return artifactId;
+    }
+
+    public String getClassifier() {
+        return classifier;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public String getVersion() {
+        return version;
+    }
+
+    public TsArtifact(String artifactId) {
+        this(artifactId, DEFAULT_VERSION);
+    }
+
+    public TsArtifact(String artifactId, String version) {
+        this(DEFAULT_GROUP_ID, artifactId, EMPTY, TYPE_TXT, version);
+    }
+
+    public TsArtifact(String groupId, String artifactId, String version) {
+        this(groupId, artifactId, EMPTY, TYPE_TXT, version);
+    }
+
+    public TsArtifact(String groupId, String artifactId, String classifier, String type, String version) {
+        this.groupId = groupId;
+        this.artifactId = artifactId;
+        this.classifier = classifier;
+        this.type = type;
+        this.version = version;
+    }
+
+    public TsArtifact setContent(ContentProvider content) {
+        this.content = content;
+        return this;
+    }
+
+    public TsArtifact addDependency(TsArtifact dep) {
+        return addDependency(new TsDependency(dep));
+    }
+
+    public TsArtifact addDependency(TsQuarkusExt dep) {
+        if(extDeps.isEmpty()) {
+            extDeps = new ArrayList<>(1);
+        }
+        extDeps.add(dep);
+        return addDependency(new TsDependency(dep.getRuntime()));
+    }
+
+    public TsArtifact addDependency(TsDependency dep) {
+        if(deps.isEmpty()) {
+            deps = new ArrayList<>();
+        }
+        deps.add(dep);
+        return this;
+    }
+
+    public String getArtifactFileName() {
+        if(artifactId == null) {
+            throw new IllegalArgumentException("artifactId is missing");
+        }
+        if(version == null) {
+            throw new IllegalArgumentException("version is missing");
+        }
+        if(type == null) {
+            throw new IllegalArgumentException("type is missing");
+        }
+        final StringBuilder fileName = new StringBuilder();
+        fileName.append(artifactId).append('-').append(version);
+        if(classifier != null && !classifier.isEmpty()) {
+            fileName.append('-').append(classifier);
+        }
+        fileName.append('.').append(type);
+        return fileName.toString();
+    }
+
+    public TsArtifact toPomArtifact() {
+        return new TsArtifact(groupId, artifactId, EMPTY, TYPE_POM, version);
+    }
+
+    public Model getPomModel() {
+        final Model model = new Model();
+        model.setModelVersion(MODEL_VERSION);
+
+        model.setGroupId(groupId);
+        model.setArtifactId(artifactId);
+        model.setPackaging(type);
+        model.setVersion(version);
+
+        if(!deps.isEmpty()) {
+            for (TsDependency dep : deps) {
+                model.addDependency(dep.toPomDependency());
+            }
+        }
+
+        return model;
+    }
+
+    public AppArtifact toAppArtifact() {
+        return new AppArtifact(groupId, artifactId, classifier, type, version);
+    }
+
+    /**
+     * Installs the artifact including its dependencies.
+     *
+     * @param repoBuilder
+     */
+    public void install(TsRepoBuilder repoBuilder) {
+        if(!deps.isEmpty()) {
+            for(TsDependency dep : deps) {
+                dep.artifact.install(repoBuilder);
+            }
+        }
+        if(!extDeps.isEmpty()) {
+            for(TsQuarkusExt ext : extDeps) {
+                ext.deployment.install(repoBuilder);
+            }
+        }
+        try {
+            repoBuilder.install(this, content == null ? null : content.getPath(repoBuilder.workDir));
+        } catch (IOException e) {
+            throw new IllegalStateException("Failed to install " + this, e);
+        }
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder buf = new StringBuilder(128);
+        buf.append(groupId).append(':').append(artifactId).append(':').append(classifier).append(':').append(type).append(':').append(version);
+        return buf.toString();
+    }
+}
diff --git a/core/creator/src/test/java/io/quarkus/creator/resolver/test/TsDependency.java b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/TsDependency.java
similarity index 88%
rename from core/creator/src/test/java/io/quarkus/creator/resolver/test/TsDependency.java
rename to independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/TsDependency.java
index f9e307be6dfe7..aa4333dc52985 100644
--- a/core/creator/src/test/java/io/quarkus/creator/resolver/test/TsDependency.java
+++ b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/TsDependency.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package io.quarkus.creator.resolver.test;
+package io.quarkus.bootstrap.resolver;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -53,15 +53,15 @@ public TsDependency(TsArtifact artifact, String scope, boolean optional) {
     }
 
     public TsDependency exclude(String artifactId) {
-        return exclude(TsArtifact.getGa(artifactId));
+        return exclude(TsArtifact.ga(artifactId));
     }
 
     public TsDependency exclude(String groupId, String artifactId) {
-        return exclude(TsArtifact.getGa(groupId, artifactId));
+        return exclude(TsArtifact.ga(groupId, artifactId));
     }
 
     public TsDependency exclude(TsArtifact artifact) {
-        if (excluded.isEmpty()) {
+        if(excluded.isEmpty()) {
             excluded = new ArrayList<>();
         }
         excluded.add(artifact);
@@ -78,14 +78,14 @@ public Dependency toPomDependency() {
         }
         dep.setType(artifact.type);
         dep.setVersion(artifact.version);
-        if (scope != null) {
+        if(scope != null) {
             dep.setScope(scope);
         }
-        if (optional) {
+        if(optional) {
             dep.setOptional(optional);
         }
-        if (!excluded.isEmpty()) {
-            for (TsArtifact excluded : excluded) {
+        if(!excluded.isEmpty()) {
+            for(TsArtifact excluded : excluded) {
                 final Exclusion exclusion = new Exclusion();
                 exclusion.setGroupId(excluded.groupId);
                 exclusion.setArtifactId(excluded.artifactId);
diff --git a/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/TsJar.java b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/TsJar.java
new file mode 100644
index 0000000000000..7e9dc58e0f57d
--- /dev/null
+++ b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/TsJar.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2019 Red Hat, Inc.
+ *
+ * Licensed 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.bootstrap.resolver;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.nio.file.FileSystem;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.UUID;
+
+import io.quarkus.bootstrap.util.IoUtils;
+import io.quarkus.bootstrap.util.ZipUtils;
+
+/**
+ *
+ * @author Alexey Loubyansky
+ */
+public class TsJar implements TsArtifact.ContentProvider {
+
+    private Path target;
+    private Map content = Collections.emptyMap();
+    private Map paths = Collections.emptyMap();
+
+    public TsJar(Path target) {
+        this.target = target;
+    }
+
+    public TsJar() {
+    }
+
+    @Override
+    public Path getPath(Path workDir) throws IOException {
+        if (target == null) {
+            target = workDir.resolve(UUID.randomUUID().toString());
+        } else if(Files.exists(target)) {
+            return target;
+        }
+        try (FileSystem zip = openZip()) {
+            if (!content.isEmpty()) {
+                for (Map.Entry entry : content.entrySet()) {
+                    final Path p = zip.getPath(entry.getKey());
+                    Files.createDirectories(p.getParent());
+                    try (BufferedWriter writer = Files.newBufferedWriter(p)) {
+                        writer.write(entry.getValue());
+                    }
+                }
+            }
+            if(!paths.isEmpty()) {
+                for (Map.Entry entry : paths.entrySet()) {
+                    final Path p = zip.getPath(entry.getKey());
+                    Files.createDirectories(p.getParent());
+                    IoUtils.copy(entry.getValue(), p);
+                }
+            }
+        } catch (Throwable t) {
+            throw t;
+        }
+        return target;
+    }
+
+    private String getKey(String... path) {
+        if(path.length == 1) {
+            return path[0];
+        }
+        final StringBuilder buf = new StringBuilder();
+        buf.append(path[0]);
+        for(int i = 1; i < path.length; ++i) {
+            buf.append('/').append(path[i]);
+        }
+        return buf.toString();
+    }
+
+    private void addContent(String content, String... path) {
+        if(this.content.isEmpty()) {
+            this.content = new HashMap<>(1);
+        }
+        this.content.put(getKey(path), content);
+    }
+
+    public TsJar addEntry(Path content, String... path) {
+        if(paths.isEmpty()) {
+            paths = new HashMap<>(1);
+        }
+        paths.put(getKey(path), content);
+        return this;
+    }
+
+    public TsJar addEntry(String content, String... path) {
+        addContent(content, path);
+        return this;
+    }
+
+    public TsJar addEntry(Properties props, String... path) {
+        final StringWriter writer = new StringWriter();
+        try {
+            props.store(writer, "Written by TsJarBuilder");
+        } catch(IOException e) {
+            throw new IllegalStateException("Failed to serialize properties", e);
+        }
+        addContent(writer.getBuffer().toString(), path);
+        return this;
+    }
+
+    public TsJar addMavenMetadata(TsArtifact artifact, Path pomXml) {
+        final Properties props = new Properties();
+        props.setProperty("groupId", artifact.groupId);
+        props.setProperty("artifactId", artifact.artifactId);
+        props.setProperty("version", artifact.version);
+        addEntry(props, "META-INF", "maven", artifact.groupId, artifact.artifactId, "pom.properties");
+        addEntry(pomXml, "META-INF", "maven", artifact.groupId, artifact.artifactId, "pom.xml");
+        return this;
+    }
+
+    private FileSystem openZip() throws IOException {
+        if(Files.exists(target)) {
+            return ZipUtils.newFileSystem(target);
+        }
+        Files.createDirectories(target.getParent());
+        return ZipUtils.newZip(target);
+    }
+}
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
new file mode 100644
index 0000000000000..c8803e494422e
--- /dev/null
+++ b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/TsQuarkusExt.java
@@ -0,0 +1,41 @@
+package io.quarkus.bootstrap.resolver;
+
+import java.io.IOException;
+
+import io.quarkus.bootstrap.BootstrapConstants;
+
+public class TsQuarkusExt {
+
+    protected final TsArtifact runtime;
+    protected final TsArtifact deployment;
+
+    public TsQuarkusExt(String artifactId) {
+        this(artifactId, TsArtifact.DEFAULT_VERSION);
+    }
+
+    public TsQuarkusExt(String artifactId, String version) {
+        runtime = TsArtifact.jar(artifactId, version);
+        deployment = TsArtifact.jar(artifactId + "-deployment", version);
+        deployment.addDependency(runtime);
+        runtime.setContent(new TsJar().addEntry(PropsBuilder.build(BootstrapConstants.PROP_DEPLOYMENT_ARTIFACT, deployment), BootstrapConstants.DESCRIPTOR_PATH));
+    }
+
+    public TsArtifact getRuntime() {
+        return runtime;
+    }
+
+    public TsArtifact getDeployment() {
+        return deployment;
+    }
+
+    public TsQuarkusExt addDependency(TsQuarkusExt ext) {
+        runtime.addDependency(ext.runtime);
+        deployment.addDependency(ext.deployment);
+        return this;
+    }
+
+    public void install(TsRepoBuilder repo) throws IOException {
+        repo.install(deployment);
+        repo.install(runtime);
+    }
+}
diff --git a/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/TsRepoBuilder.java b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/TsRepoBuilder.java
new file mode 100644
index 0000000000000..c1f5219774057
--- /dev/null
+++ b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/TsRepoBuilder.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2019 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed 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.bootstrap.resolver;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.UUID;
+
+import io.quarkus.bootstrap.model.AppArtifact;
+import io.quarkus.bootstrap.resolver.AppModelResolverException;
+import io.quarkus.bootstrap.resolver.maven.workspace.ModelUtils;
+
+/**
+ *
+ * @author Alexey Loubyansky
+ */
+public class TsRepoBuilder {
+
+    private static void error(String message, Throwable t) {
+        throw new IllegalStateException(message, t);
+    }
+
+    public static TsRepoBuilder getInstance(BootstrapAppModelResolver resolver, Path workDir) {
+        return new TsRepoBuilder(resolver, workDir);
+    }
+
+    protected final Path workDir;
+    private final BootstrapAppModelResolver resolver;
+
+    private TsRepoBuilder(BootstrapAppModelResolver resolver, Path workDir) {
+        this.resolver = resolver;
+        this.workDir = workDir;
+    }
+
+    public void install(TsArtifact artifact) {
+        try {
+            install(artifact, artifact.content == null ? null : artifact.content.getPath(workDir));
+        } catch (IOException e) {
+            throw new IllegalStateException("Failed to initialize content for " + artifact, e);
+        }
+    }
+
+    public void install(TsArtifact artifact, Path p) {
+        final Path pomXml = workDir.resolve(artifact.getArtifactFileName() + ".pom");
+        if(Files.exists(pomXml)) {
+            // assume it's already installed
+            return;
+        }
+        try {
+            ModelUtils.persistModel(pomXml, artifact.getPomModel());
+        } catch (Exception e) {
+            error("Failed to persist pom.xml for " + artifact, e);
+        }
+        install(artifact.toPomArtifact().toAppArtifact(), pomXml);
+        if(p == null) {
+            switch(artifact.type) {
+                case TsArtifact.TYPE_JAR:
+                    try {
+                        p = newJar()
+                                .addMavenMetadata(artifact, pomXml)
+                                .getPath(workDir);
+                    } catch (IOException e) {
+                        throw new IllegalStateException("Failed to install " + artifact, e);
+                    }
+                    break;
+                case TsArtifact.TYPE_TXT:
+                    p = newTxt(artifact);
+                    break;
+                default:
+                    throw new IllegalStateException("Unsupported artifact type " + artifact.type);
+            }
+        }
+        install(artifact.toAppArtifact(), p);
+    }
+
+    protected void install(AppArtifact artifact, Path file) {
+        try {
+            resolver.install(artifact, file);
+        } catch (AppModelResolverException e) {
+            error("Failed to install " + artifact, e);
+        }
+    }
+
+    protected Path newTxt(TsArtifact artifact) {
+        final Path tmpFile = workDir.resolve(artifact.getArtifactFileName());
+        if(Files.exists(tmpFile)) {
+            throw new IllegalStateException("File already exists " + tmpFile);
+        }
+        try(BufferedWriter writer = Files.newBufferedWriter(tmpFile)) {
+            writer.write(tmpFile.getFileName().toString());
+        } catch (IOException e) {
+            throw new IllegalStateException("Failed to create file " + tmpFile, e);
+        }
+        return tmpFile;
+    }
+
+    public TsJar newJar() {
+        return new TsJar(workDir.resolve(UUID.randomUUID().toString()));
+    }
+}
diff --git a/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/graph/parser/test/DependencyGraphParserTestCase.java b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/graph/parser/test/DependencyGraphParserTestCase.java
new file mode 100644
index 0000000000000..0a86813e81258
--- /dev/null
+++ b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/graph/parser/test/DependencyGraphParserTestCase.java
@@ -0,0 +1,12 @@
+/**
+ *
+ */
+package io.quarkus.bootstrap.resolver.graph.parser.test;
+
+/**
+ *
+ * @author Alexey Loubyansky
+ */
+public class DependencyGraphParserTestCase {
+
+}
diff --git a/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/replace/test/SimpleReplacedDependencyTestCase.java b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/replace/test/SimpleReplacedDependencyTestCase.java
new file mode 100644
index 0000000000000..f19d672170042
--- /dev/null
+++ b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/replace/test/SimpleReplacedDependencyTestCase.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2019 Red Hat, Inc.
+ *
+ * Licensed 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.bootstrap.resolver.replace.test;
+
+import io.quarkus.bootstrap.BootstrapConstants;
+import io.quarkus.bootstrap.resolver.CollectDependenciesBase;
+import io.quarkus.bootstrap.resolver.PropsBuilder;
+import io.quarkus.bootstrap.resolver.TsArtifact;
+
+/**
+ *
+ * @author Alexey Loubyansky
+ */
+public class SimpleReplacedDependencyTestCase extends CollectDependenciesBase {
+
+    @Override
+    protected void setupDependencies() throws Exception {
+
+        final TsArtifact extension = TsArtifact.jar("extension");
+        final TsArtifact deployment = new TsArtifact("deployment").addDependency(extension);
+
+        installAsDep(
+                extension,
+                newJar().addEntry(
+                        PropsBuilder.build(BootstrapConstants.PROP_DEPLOYMENT_ARTIFACT, deployment.toString()), BootstrapConstants.DESCRIPTOR_PATH)
+                .getPath(workDir),
+                true);
+
+        install(deployment, true);
+    }
+}
diff --git a/core/creator/src/test/java/io/quarkus/creator/resolver/test/DependenciesOnDifferentVersionsOfAnArtifactTestCase.java b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/DependenciesOnDifferentVersionsOfAnArtifactTestCase.java
similarity index 87%
rename from core/creator/src/test/java/io/quarkus/creator/resolver/test/DependenciesOnDifferentVersionsOfAnArtifactTestCase.java
rename to independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/DependenciesOnDifferentVersionsOfAnArtifactTestCase.java
index 33c2ec2e92065..133dceaf2ec4f 100644
--- a/core/creator/src/test/java/io/quarkus/creator/resolver/test/DependenciesOnDifferentVersionsOfAnArtifactTestCase.java
+++ b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/DependenciesOnDifferentVersionsOfAnArtifactTestCase.java
@@ -14,7 +14,10 @@
  * limitations under the License.
  */
 
-package io.quarkus.creator.resolver.test;
+package io.quarkus.bootstrap.resolver.test;
+
+import io.quarkus.bootstrap.resolver.CollectDependenciesBase;
+import io.quarkus.bootstrap.resolver.TsArtifact;
 
 /**
  *
diff --git a/core/creator/src/test/java/io/quarkus/creator/resolver/test/DependencyPresentTwiceInTheGraphInCompileAndProvidedScopesTestCase.java b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/DependencyPresentTwiceInTheGraphInCompileAndProvidedScopesTestCase.java
similarity index 86%
rename from core/creator/src/test/java/io/quarkus/creator/resolver/test/DependencyPresentTwiceInTheGraphInCompileAndProvidedScopesTestCase.java
rename to independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/DependencyPresentTwiceInTheGraphInCompileAndProvidedScopesTestCase.java
index 3a9b9cdd0284d..996f07baee2b1 100644
--- a/core/creator/src/test/java/io/quarkus/creator/resolver/test/DependencyPresentTwiceInTheGraphInCompileAndProvidedScopesTestCase.java
+++ b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/DependencyPresentTwiceInTheGraphInCompileAndProvidedScopesTestCase.java
@@ -14,7 +14,11 @@
  * limitations under the License.
  */
 
-package io.quarkus.creator.resolver.test;
+package io.quarkus.bootstrap.resolver.test;
+
+import io.quarkus.bootstrap.resolver.CollectDependenciesBase;
+import io.quarkus.bootstrap.resolver.TsArtifact;
+import io.quarkus.bootstrap.resolver.TsDependency;
 
 /**
  *
diff --git a/core/creator/src/test/java/io/quarkus/creator/resolver/test/DependencyPresentTwiceInTheGraphInCompileAndTestScopesTestCase.java b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/DependencyPresentTwiceInTheGraphInCompileAndTestScopesTestCase.java
similarity index 84%
rename from core/creator/src/test/java/io/quarkus/creator/resolver/test/DependencyPresentTwiceInTheGraphInCompileAndTestScopesTestCase.java
rename to independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/DependencyPresentTwiceInTheGraphInCompileAndTestScopesTestCase.java
index 25fd014d29859..8d65cd2ad6bb3 100644
--- a/core/creator/src/test/java/io/quarkus/creator/resolver/test/DependencyPresentTwiceInTheGraphInCompileAndTestScopesTestCase.java
+++ b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/DependencyPresentTwiceInTheGraphInCompileAndTestScopesTestCase.java
@@ -14,7 +14,11 @@
  * limitations under the License.
  */
 
-package io.quarkus.creator.resolver.test;
+package io.quarkus.bootstrap.resolver.test;
+
+import io.quarkus.bootstrap.resolver.CollectDependenciesBase;
+import io.quarkus.bootstrap.resolver.TsArtifact;
+import io.quarkus.bootstrap.resolver.TsDependency;
 
 /**
  *
diff --git a/core/creator/src/test/java/io/quarkus/creator/resolver/test/DependencyPresentTwiceInTheGraphTestCase.java b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/DependencyPresentTwiceInTheGraphTestCase.java
similarity index 87%
rename from core/creator/src/test/java/io/quarkus/creator/resolver/test/DependencyPresentTwiceInTheGraphTestCase.java
rename to independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/DependencyPresentTwiceInTheGraphTestCase.java
index e8cc87d55e427..519a3214b6a64 100644
--- a/core/creator/src/test/java/io/quarkus/creator/resolver/test/DependencyPresentTwiceInTheGraphTestCase.java
+++ b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/DependencyPresentTwiceInTheGraphTestCase.java
@@ -14,7 +14,10 @@
  * limitations under the License.
  */
 
-package io.quarkus.creator.resolver.test;
+package io.quarkus.bootstrap.resolver.test;
+
+import io.quarkus.bootstrap.resolver.CollectDependenciesBase;
+import io.quarkus.bootstrap.resolver.TsArtifact;
 
 /**
  *
diff --git a/core/creator/src/test/java/io/quarkus/creator/resolver/test/DependencyPresentTwiceInTheGraphWithDifferentClassifierAndTypeTestCase.java b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/DependencyPresentTwiceInTheGraphWithDifferentClassifierAndTypeTestCase.java
similarity index 87%
rename from core/creator/src/test/java/io/quarkus/creator/resolver/test/DependencyPresentTwiceInTheGraphWithDifferentClassifierAndTypeTestCase.java
rename to independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/DependencyPresentTwiceInTheGraphWithDifferentClassifierAndTypeTestCase.java
index 578fa31233983..791da114d95e3 100644
--- a/core/creator/src/test/java/io/quarkus/creator/resolver/test/DependencyPresentTwiceInTheGraphWithDifferentClassifierAndTypeTestCase.java
+++ b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/DependencyPresentTwiceInTheGraphWithDifferentClassifierAndTypeTestCase.java
@@ -14,7 +14,10 @@
  * limitations under the License.
  */
 
-package io.quarkus.creator.resolver.test;
+package io.quarkus.bootstrap.resolver.test;
+
+import io.quarkus.bootstrap.resolver.CollectDependenciesBase;
+import io.quarkus.bootstrap.resolver.TsArtifact;
 
 /**
  *
@@ -39,6 +42,6 @@ protected void setupDependencies() {
                 .addDependency(commonClient2),
                 true);
 
-        installAsDep(new TsArtifact(TsArtifact.DEFAULT_GROUP_ID, "common", "", "doc", "3"), true);
+        installAsDep(new TsArtifact(TsArtifact.DEFAULT_GROUP_ID, "common", "", "jar", "3"), true);
     }
 }
diff --git a/core/creator/src/test/java/io/quarkus/creator/resolver/test/DirectDependencyVersionOverridesTransitiveVersionTestCase.java b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/DirectDependencyVersionOverridesTransitiveVersionTestCase.java
similarity index 86%
rename from core/creator/src/test/java/io/quarkus/creator/resolver/test/DirectDependencyVersionOverridesTransitiveVersionTestCase.java
rename to independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/DirectDependencyVersionOverridesTransitiveVersionTestCase.java
index f94444c29c0a9..64d7ef66f0db6 100644
--- a/core/creator/src/test/java/io/quarkus/creator/resolver/test/DirectDependencyVersionOverridesTransitiveVersionTestCase.java
+++ b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/DirectDependencyVersionOverridesTransitiveVersionTestCase.java
@@ -14,7 +14,10 @@
  * limitations under the License.
  */
 
-package io.quarkus.creator.resolver.test;
+package io.quarkus.bootstrap.resolver.test;
+
+import io.quarkus.bootstrap.resolver.CollectDependenciesBase;
+import io.quarkus.bootstrap.resolver.TsArtifact;
 
 /**
  *
diff --git a/core/creator/src/test/java/io/quarkus/creator/resolver/test/TransitiveNonOptionalOverridesDirectOptionalVersionTestCase.java b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/DirectOptionalOverridesTransitiveNonOptionalVersionTestCase.java
similarity index 70%
rename from core/creator/src/test/java/io/quarkus/creator/resolver/test/TransitiveNonOptionalOverridesDirectOptionalVersionTestCase.java
rename to independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/DirectOptionalOverridesTransitiveNonOptionalVersionTestCase.java
index 6d3a932381736..65a54010ce1d3 100644
--- a/core/creator/src/test/java/io/quarkus/creator/resolver/test/TransitiveNonOptionalOverridesDirectOptionalVersionTestCase.java
+++ b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/DirectOptionalOverridesTransitiveNonOptionalVersionTestCase.java
@@ -14,23 +14,24 @@
  * limitations under the License.
  */
 
-package io.quarkus.creator.resolver.test;
+package io.quarkus.bootstrap.resolver.test;
+
+import io.quarkus.bootstrap.resolver.CollectDependenciesBase;
+import io.quarkus.bootstrap.resolver.TsArtifact;
+import io.quarkus.bootstrap.resolver.TsDependency;
 
 /**
  *
  * @author Alexey Loubyansky
  */
-public class TransitiveNonOptionalOverridesDirectOptionalVersionTestCase extends CollectDependenciesBase {
+public class DirectOptionalOverridesTransitiveNonOptionalVersionTestCase extends CollectDependenciesBase {
 
     @Override
     protected void setupDependencies() {
 
-        final TsArtifact common1 = new TsArtifact("common", "1");
-        install(common1, true);
-
         installAsDep(new TsArtifact("required-a")
-                .addDependency(common1), true);
+                .addDependency(new TsArtifact("common", "1")), true);
 
-        installAsDep(new TsDependency(new TsArtifact("common", "2"), true), false);
+        installAsDep(new TsDependency(new TsArtifact("common", "2"), true), true);
     }
 }
diff --git a/core/creator/src/test/java/io/quarkus/creator/resolver/test/ExclusionsTestCase.java b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/ExclusionsTestCase.java
similarity index 72%
rename from core/creator/src/test/java/io/quarkus/creator/resolver/test/ExclusionsTestCase.java
rename to independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/ExclusionsTestCase.java
index 8646561656e4b..ee068cc79593b 100644
--- a/core/creator/src/test/java/io/quarkus/creator/resolver/test/ExclusionsTestCase.java
+++ b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/ExclusionsTestCase.java
@@ -14,7 +14,11 @@
  * limitations under the License.
  */
 
-package io.quarkus.creator.resolver.test;
+package io.quarkus.bootstrap.resolver.test;
+
+import io.quarkus.bootstrap.resolver.CollectDependenciesBase;
+import io.quarkus.bootstrap.resolver.TsArtifact;
+import io.quarkus.bootstrap.resolver.TsDependency;
 
 /**
  *
@@ -28,7 +32,8 @@ protected void setupDependencies() {
         final TsArtifact requiredTransitive = new TsArtifact("required-transitive")
                 .addDependency(
                         new TsArtifact("excluded-dep", "2")
-                                .addDependency(new TsArtifact("other-dep")));
+                        .addDependency(new TsArtifact("other-dep"))
+                        );
         install(requiredTransitive, true);
 
         final TsArtifact otherDep2 = new TsArtifact("other-dep", "2");
@@ -40,9 +45,10 @@ protected void setupDependencies() {
 
         installAsDep(
                 new TsArtifact("required-dep1")
-                        .addDependency(
-                                new TsDependency(requiredTransitive)
-                                        .exclude("excluded-dep"))
-                        .addDependency(otherRequiredTransitive));
+                .addDependency(
+                        new TsDependency(requiredTransitive)
+                        .exclude("excluded-dep"))
+                .addDependency(otherRequiredTransitive)
+                );
     }
 }
diff --git a/core/creator/src/test/java/io/quarkus/creator/resolver/test/OptionalDepsNotCollectedTestCase.java b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/OnlyDirectOptionalDepsAreCollectedTestCase.java
similarity index 66%
rename from core/creator/src/test/java/io/quarkus/creator/resolver/test/OptionalDepsNotCollectedTestCase.java
rename to independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/OnlyDirectOptionalDepsAreCollectedTestCase.java
index dcfa974bfb1eb..b6cd2a8e40aed 100644
--- a/core/creator/src/test/java/io/quarkus/creator/resolver/test/OptionalDepsNotCollectedTestCase.java
+++ b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/OnlyDirectOptionalDepsAreCollectedTestCase.java
@@ -14,13 +14,17 @@
  * limitations under the License.
  */
 
-package io.quarkus.creator.resolver.test;
+package io.quarkus.bootstrap.resolver.test;
+
+import io.quarkus.bootstrap.resolver.CollectDependenciesBase;
+import io.quarkus.bootstrap.resolver.TsArtifact;
+import io.quarkus.bootstrap.resolver.TsDependency;
 
 /**
  *
  * @author Alexey Loubyansky
  */
-public class OptionalDepsNotCollectedTestCase extends CollectDependenciesBase {
+public class OnlyDirectOptionalDepsAreCollectedTestCase extends CollectDependenciesBase {
 
     @Override
     protected void setupDependencies() {
@@ -30,13 +34,12 @@ protected void setupDependencies() {
         installAsDep(
                 new TsDependency(
                         new TsArtifact("optional-dep")
-                                .addDependency(new TsArtifact("common", "1")),
+                        .addDependency(new TsDependency(new TsArtifact("common", "1"), true))
+                        .addDependency(new TsDependency(new TsArtifact("other", "1"), true)),
                         true),
-                false);
-
-        final TsArtifact common2 = new TsArtifact("common", "2");
-        install(common2, true);
+                true);
 
+        TsArtifact common2 = install(new TsArtifact("common", "2"), true);
         installAsDep(new TsArtifact("required-dep-c")
                 .addDependency(common2), true);
     }
diff --git a/core/creator/src/test/java/io/quarkus/creator/resolver/test/OnlyDirectProvidedDepsAreCollectedTestCase.java b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/ProvidedScopeDepsAreNotCollectedTestCase.java
similarity index 69%
rename from core/creator/src/test/java/io/quarkus/creator/resolver/test/OnlyDirectProvidedDepsAreCollectedTestCase.java
rename to independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/ProvidedScopeDepsAreNotCollectedTestCase.java
index 68a347a913574..ddb3bf6696799 100644
--- a/core/creator/src/test/java/io/quarkus/creator/resolver/test/OnlyDirectProvidedDepsAreCollectedTestCase.java
+++ b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/ProvidedScopeDepsAreNotCollectedTestCase.java
@@ -14,13 +14,17 @@
  * limitations under the License.
  */
 
-package io.quarkus.creator.resolver.test;
+package io.quarkus.bootstrap.resolver.test;
+
+import io.quarkus.bootstrap.resolver.CollectDependenciesBase;
+import io.quarkus.bootstrap.resolver.TsArtifact;
+import io.quarkus.bootstrap.resolver.TsDependency;
 
 /**
  *
  * @author Alexey Loubyansky
  */
-public class OnlyDirectProvidedDepsAreCollectedTestCase extends CollectDependenciesBase {
+public class ProvidedScopeDepsAreNotCollectedTestCase extends CollectDependenciesBase {
 
     @Override
     protected void setupDependencies() {
@@ -31,7 +35,8 @@ protected void setupDependencies() {
         final TsArtifact common1 = new TsArtifact("common", "1")
                 .addDependency(
                         new TsDependency(
-                                notCollected, "provided"));
+                                notCollected, "provided")
+                        );
         install(common1, true);
 
         installAsDep(new TsArtifact("required-dep")
@@ -41,9 +46,10 @@ protected void setupDependencies() {
         installAsDep(
                 new TsDependency(
                         new TsArtifact("provided-dep")
-                                .addDependency(
-                                        new TsArtifact("common", "2")),
+                        .addDependency(
+                                new TsArtifact("common", "2")
+                                ),
                         "provided"),
-                true);
+                false);
     }
 }
diff --git a/core/creator/src/test/java/io/quarkus/creator/resolver/test/TestScopeNotCollectedTestCase.java b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/TestScopeIsNotAmongRuntimeDependenciesTestCase.java
similarity index 62%
rename from core/creator/src/test/java/io/quarkus/creator/resolver/test/TestScopeNotCollectedTestCase.java
rename to independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/TestScopeIsNotAmongRuntimeDependenciesTestCase.java
index b4554dee6a054..68df26b0b2d18 100644
--- a/core/creator/src/test/java/io/quarkus/creator/resolver/test/TestScopeNotCollectedTestCase.java
+++ b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/TestScopeIsNotAmongRuntimeDependenciesTestCase.java
@@ -14,13 +14,17 @@
  * limitations under the License.
  */
 
-package io.quarkus.creator.resolver.test;
+package io.quarkus.bootstrap.resolver.test;
+
+import io.quarkus.bootstrap.resolver.CollectDependenciesBase;
+import io.quarkus.bootstrap.resolver.TsArtifact;
+import io.quarkus.bootstrap.resolver.TsDependency;
 
 /**
  *
  * @author Alexey Loubyansky
  */
-public class TestScopeNotCollectedTestCase extends CollectDependenciesBase {
+public class TestScopeIsNotAmongRuntimeDependenciesTestCase extends CollectDependenciesBase {
 
     @Override
     protected void setupDependencies() {
@@ -29,17 +33,17 @@ protected void setupDependencies() {
 
         installAsDep(
                 new TsDependency(
-                        new TsArtifact("test-dep")
-                                .addDependency(new TsArtifact("common", "1")),
+                        new TsArtifact("direct-test-dep")
+                        .addDependency(new TsArtifact("common", "1")
+                        .addDependency(new TsDependency(new TsArtifact("not-collected"), "test"))),
                         "test"),
                 false);
 
-        final TsArtifact common2 = new TsArtifact("common", "2");
-        install(common2, true);
 
+        final TsArtifact common = install(new TsArtifact("common", "2"), true);
         installAsDep(
                 new TsArtifact("required-dep-c")
-                        .addDependency(common2),
+                .addDependency(common),
                 true);
     }
 }
diff --git a/core/creator/src/test/java/io/quarkus/creator/resolver/test/TransitiveVersionOverridesTestCase.java b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/TransitiveVersionOverridesTestCase.java
similarity index 89%
rename from core/creator/src/test/java/io/quarkus/creator/resolver/test/TransitiveVersionOverridesTestCase.java
rename to independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/TransitiveVersionOverridesTestCase.java
index b33f46f146402..f43fa7d68d14d 100644
--- a/core/creator/src/test/java/io/quarkus/creator/resolver/test/TransitiveVersionOverridesTestCase.java
+++ b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/resolver/test/TransitiveVersionOverridesTestCase.java
@@ -14,7 +14,10 @@
  * limitations under the License.
  */
 
-package io.quarkus.creator.resolver.test;
+package io.quarkus.bootstrap.resolver.test;
+
+import io.quarkus.bootstrap.resolver.CollectDependenciesBase;
+import io.quarkus.bootstrap.resolver.TsArtifact;
 
 /**
  *
diff --git a/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/workspace/test/LocalWorkspaceDiscoveryTest.java b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/workspace/test/LocalWorkspaceDiscoveryTest.java
new file mode 100644
index 0000000000000..6b1b9f589f0f3
--- /dev/null
+++ b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/workspace/test/LocalWorkspaceDiscoveryTest.java
@@ -0,0 +1,207 @@
+/**
+ *
+ */
+package io.quarkus.bootstrap.workspace.test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.maven.model.Dependency;
+import org.apache.maven.model.Parent;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import io.quarkus.bootstrap.model.AppArtifactKey;
+import io.quarkus.bootstrap.resolver.maven.workspace.LocalProject;
+import io.quarkus.bootstrap.util.IoUtils;
+
+public class LocalWorkspaceDiscoveryTest {
+
+    private static Dependency newDependency(String artifactId) {
+        return newDependency(MvnProjectBuilder.DEFAULT_GROUP_ID, artifactId, MvnProjectBuilder.DEFAULT_VERSION);
+    }
+
+    private static Dependency newDependency(String groupId, String artifactId, String version) {
+        final Dependency dep = new Dependency();
+        dep.setGroupId(groupId);
+        dep.setArtifactId(artifactId);
+        dep.setVersion(version);
+        return dep;
+    }
+
+    protected static Path workDir;
+
+    @BeforeClass
+    public static void setup() throws Exception {
+        workDir = IoUtils.createRandomTmpDir();
+
+        final Parent parent = new Parent();
+        parent.setGroupId(MvnProjectBuilder.DEFAULT_GROUP_ID);
+        parent.setArtifactId("parent");
+        parent.setVersion(MvnProjectBuilder.DEFAULT_VERSION);
+        parent.setRelativePath(null);
+
+        MvnProjectBuilder.forArtifact("root")
+        .setParent(parent)
+
+        .addModule("module1", "root-no-parent-module", false)
+        .addDependency(newDependency("root-module-not-direct-child"))
+        .getParent()
+
+        .addModule("module2", "root-module-with-parent", true)
+        .addDependency(newDependency("root-no-parent-module"))
+        .addDependency(newDependency("external-dep"))
+        .addDependency(newDependency(LocalProject.PROJECT_GROUPID, "root-module-not-direct-child", MvnProjectBuilder.DEFAULT_VERSION))
+        .getParent()
+
+        .addModule("other/module3", "root-module-not-direct-child", true)
+        .getParent()
+
+        .build(workDir.resolve("root"));
+
+        final Parent rootParent = new Parent();
+        rootParent.setGroupId(MvnProjectBuilder.DEFAULT_GROUP_ID);
+        rootParent.setArtifactId("root");
+        rootParent.setVersion(MvnProjectBuilder.DEFAULT_VERSION);
+        rootParent.setRelativePath(null);
+
+        MvnProjectBuilder.forArtifact("non-module-child")
+        .setParent(rootParent)
+        .addModule("module1", "another-child", true)
+        .getParent()
+        .build(workDir.resolve("root").resolve("non-module-child"));
+
+        // independent project in the tree
+        MvnProjectBuilder.forArtifact("independent")
+        .addDependency(newDependency("root-module-with-parent"))
+        .build(workDir.resolve("root").resolve("independent"));
+    }
+
+    @AfterClass
+    public static void cleanup() {
+        IoUtils.recursiveDelete(workDir);
+    }
+
+    @Test
+    public void loadIndependentProjectInTheWorkspaceTree() throws Exception {
+        final LocalProject project = LocalProject.loadWorkspace(workDir.resolve("root").resolve("independent").resolve("target").resolve("classes"));
+        assertNotNull(project);
+        assertNotNull(project.getWorkspace());
+        assertEquals(MvnProjectBuilder.DEFAULT_GROUP_ID, project.getGroupId());
+        assertEquals("independent", project.getArtifactId());
+        assertEquals(MvnProjectBuilder.DEFAULT_VERSION, project.getVersion());
+        final Map projects = project.getWorkspace().getProjects();
+        assertEquals(1, projects.size());
+        projects.containsKey(new AppArtifactKey(MvnProjectBuilder.DEFAULT_GROUP_ID, "independent"));
+
+        assertLocalDeps(project);
+    }
+
+    @Test
+    public void loadModuleProjectWithoutParent() throws Exception {
+        final LocalProject project = LocalProject.load(workDir.resolve("root").resolve("module1").resolve("target").resolve("classes"));
+        assertNotNull(project);
+        assertNull(project.getWorkspace());
+        assertEquals(MvnProjectBuilder.DEFAULT_GROUP_ID, project.getGroupId());
+        assertEquals("root-no-parent-module", project.getArtifactId());
+        assertEquals(MvnProjectBuilder.DEFAULT_VERSION, project.getVersion());
+        assertLocalDeps(project);
+    }
+
+    @Test
+    public void loadWorkspaceForModuleWithoutParent() throws Exception {
+        final LocalProject project = LocalProject.loadWorkspace(workDir.resolve("root").resolve("module1").resolve("target").resolve("classes"));
+        assertNotNull(project);
+        assertEquals(MvnProjectBuilder.DEFAULT_GROUP_ID, project.getGroupId());
+        assertEquals("root-no-parent-module", project.getArtifactId());
+        assertEquals(MvnProjectBuilder.DEFAULT_VERSION, project.getVersion());
+        assertNotNull(project.getWorkspace());
+        final Map projects = project.getWorkspace().getProjects();
+        assertEquals(1, projects.size());
+        projects.containsKey(new AppArtifactKey(MvnProjectBuilder.DEFAULT_GROUP_ID, "root-no-parent-module"));
+        assertLocalDeps(project);
+    }
+
+    @Test
+    public void loadModuleProjectWithParent() throws Exception {
+        final LocalProject project = LocalProject.load(workDir.resolve("root").resolve("module2").resolve("target").resolve("classes"));
+        assertNotNull(project);
+        assertNull(project.getWorkspace());
+        assertEquals(MvnProjectBuilder.DEFAULT_GROUP_ID, project.getGroupId());
+        assertEquals("root-module-with-parent", project.getArtifactId());
+        assertEquals(MvnProjectBuilder.DEFAULT_VERSION, project.getVersion());
+        assertLocalDeps(project);
+    }
+
+    @Test
+    public void loadWorkspaceForModuleWithParent() throws Exception {
+        final LocalProject project = LocalProject.loadWorkspace(workDir.resolve("root").resolve("module2").resolve("target").resolve("classes"));
+        assertNotNull(project);
+        assertNotNull(project.getWorkspace());
+        assertEquals(MvnProjectBuilder.DEFAULT_GROUP_ID, project.getGroupId());
+        assertEquals("root-module-with-parent", project.getArtifactId());
+        assertEquals(MvnProjectBuilder.DEFAULT_VERSION, project.getVersion());
+
+        assertCompleteWorkspace(project);
+        assertLocalDeps(project, "root-module-not-direct-child", "root-no-parent-module");
+    }
+
+    @Test
+    public void loadWorkspaceForModuleWithNotDirectParentPath() throws Exception {
+        final LocalProject project = LocalProject.loadWorkspace(workDir.resolve("root").resolve("other").resolve("module3").resolve("target").resolve("classes"));
+        assertNotNull(project);
+        assertNotNull(project.getWorkspace());
+        assertEquals(MvnProjectBuilder.DEFAULT_GROUP_ID, project.getGroupId());
+        assertEquals("root-module-not-direct-child", project.getArtifactId());
+        assertEquals(MvnProjectBuilder.DEFAULT_VERSION, project.getVersion());
+
+        assertCompleteWorkspace(project);
+        assertLocalDeps(project);
+    }
+
+    @Test
+    public void loadNonModuleChildProject() throws Exception {
+        final LocalProject project = LocalProject.loadWorkspace(workDir.resolve("root").resolve("non-module-child").resolve("target").resolve("classes"));
+        assertNotNull(project);
+        assertNotNull(project.getWorkspace());
+        assertEquals("non-module-child", project.getArtifactId());
+        final Map projects = project.getWorkspace().getProjects();
+        assertEquals(6, projects.size());
+        projects.containsKey(new AppArtifactKey(MvnProjectBuilder.DEFAULT_GROUP_ID, "root-no-parent-module"));
+        projects.containsKey(new AppArtifactKey(MvnProjectBuilder.DEFAULT_GROUP_ID, "root-module-with-parent"));
+        projects.containsKey(new AppArtifactKey(MvnProjectBuilder.DEFAULT_GROUP_ID, "root-module-not-direct-parent"));
+        projects.containsKey(new AppArtifactKey(MvnProjectBuilder.DEFAULT_GROUP_ID, "root"));
+        projects.containsKey(new AppArtifactKey(MvnProjectBuilder.DEFAULT_GROUP_ID, "non-module-child"));
+        projects.containsKey(new AppArtifactKey(MvnProjectBuilder.DEFAULT_GROUP_ID, "another-child"));
+        assertLocalDeps(project, "another-child");
+    }
+
+    private void assertCompleteWorkspace(final LocalProject project) {
+        final Map projects = project.getWorkspace().getProjects();
+        assertEquals(4, projects.size());
+        projects.containsKey(new AppArtifactKey(MvnProjectBuilder.DEFAULT_GROUP_ID, "root-no-parent-module"));
+        projects.containsKey(new AppArtifactKey(MvnProjectBuilder.DEFAULT_GROUP_ID, "root-module-with-parent"));
+        projects.containsKey(new AppArtifactKey(MvnProjectBuilder.DEFAULT_GROUP_ID, "root-module-not-direct-parent"));
+        projects.containsKey(new AppArtifactKey(MvnProjectBuilder.DEFAULT_GROUP_ID, "root"));
+    }
+
+    private static void assertLocalDeps(LocalProject project, String... deps) {
+        final List list = project.getSelfWithLocalDeps();
+        assertEquals(deps.length + 1, list.size());
+        int i = 0;
+        while(i < deps.length) {
+            final LocalProject dep = list.get(i);
+            assertEquals(deps[i++], dep.getArtifactId());
+            assertEquals(project.getGroupId(), dep.getGroupId());
+        }
+        final LocalProject self = list.get(i);
+        assertEquals(project.getGroupId(), self.getGroupId());
+        assertEquals(project.getArtifactId(), self.getArtifactId());
+        assertEquals(project.getVersion(), self.getVersion());
+    }
+}
diff --git a/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/workspace/test/MvnProjectBuilder.java b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/workspace/test/MvnProjectBuilder.java
new file mode 100644
index 0000000000000..190b1c57452a0
--- /dev/null
+++ b/independent-projects/bootstrap/core/src/test/java/io/quarkus/bootstrap/workspace/test/MvnProjectBuilder.java
@@ -0,0 +1,126 @@
+/**
+ *
+ */
+package io.quarkus.bootstrap.workspace.test;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.maven.model.Dependency;
+import org.apache.maven.model.Model;
+import org.apache.maven.model.Parent;
+
+import io.quarkus.bootstrap.resolver.maven.workspace.ModelUtils;
+import io.quarkus.bootstrap.util.IoUtils;
+
+public class MvnProjectBuilder {
+
+    public static final String DEFAULT_GROUP_ID = "io.quarkus.test";
+    public static final String DEFAULT_VERSION = "1.0";
+
+    private MvnProjectBuilder parent;
+    private Model model = new Model();
+    private List modules = new ArrayList<>(0);
+
+    public static MvnProjectBuilder forArtifact(String artifactId) {
+        return new MvnProjectBuilder(artifactId);
+    }
+
+    private MvnProjectBuilder(String artifactId) {
+        this(artifactId, null);
+    }
+
+    private MvnProjectBuilder(String artifactId, MvnProjectBuilder parent) {
+        model.setModelVersion("4.0.0");
+        model.setGroupId(DEFAULT_GROUP_ID);
+        model.setArtifactId(artifactId);
+        model.setVersion(DEFAULT_VERSION);
+        model.setPackaging("jar");
+        this.parent = parent;
+    }
+
+    public MvnProjectBuilder getParent() {
+        return parent;
+    }
+
+    public MvnProjectBuilder setParent(Parent parent) {
+        model.setParent(parent);
+        return this;
+    }
+
+    public MvnProjectBuilder setGroupId(String groupId) {
+        model.setGroupId(groupId);
+        return this;
+    }
+
+    public MvnProjectBuilder setVersion(String version) {
+        model.setVersion(version);
+        return this;
+    }
+
+    public MvnProjectBuilder addDependency(String artifactId) {
+        final Dependency dep = new Dependency();
+        dep.setGroupId(DEFAULT_GROUP_ID);
+        dep.setArtifactId(artifactId);
+        dep.setVersion(DEFAULT_VERSION);
+        return addDependency(dep);
+    }
+
+    public MvnProjectBuilder addDependency(String artifactId, String scope) {
+        final Dependency dep = new Dependency();
+        dep.setGroupId(DEFAULT_GROUP_ID);
+        dep.setArtifactId(artifactId);
+        dep.setVersion(DEFAULT_VERSION);
+        dep.setScope(scope);
+        return addDependency(dep);
+    }
+
+    public MvnProjectBuilder addDependency(Dependency dep) {
+        model.addDependency(dep);
+        return this;
+    }
+
+    public MvnProjectBuilder addModule(String path, String artifactId) {
+        return addModule(path, artifactId, true);
+    }
+
+    public MvnProjectBuilder addModule(String path, String artifactId, boolean initParent) {
+        model.addModule(path);
+        final MvnProjectBuilder module = new MvnProjectBuilder(artifactId, this);
+        if(initParent) {
+            final Parent parentModel = new Parent();
+            module.model.setParent(parentModel);
+            parentModel.setGroupId(model.getGroupId());
+            parentModel.setArtifactId(model.getArtifactId());
+            parentModel.setVersion(model.getVersion());
+            final Path rootDir = Paths.get("").toAbsolutePath();
+            final Path moduleDir = rootDir.resolve(path).normalize();
+            if(!moduleDir.getParent().equals(rootDir)) {
+                parentModel.setRelativePath(moduleDir.relativize(rootDir).toString());
+            }
+        }
+        modules.add(module);
+        return module;
+    }
+
+    public void build(Path projectDir) {
+        //System.out.println("build " + model.getArtifactId() + " " + projectDir);
+        IoUtils.mkdirs(projectDir);
+        if(!modules.isEmpty()) {
+            model.setPackaging("pom");
+            for(int i = 0; i < modules.size(); ++i) {
+                modules.get(i).build(projectDir.resolve(model.getModules().get(i)));
+            }
+        } else {
+            IoUtils.mkdirs(projectDir.resolve("target").resolve("classes"));
+        }
+        try {
+            ModelUtils.persistModel(projectDir.resolve("pom.xml"), model);
+        } catch (IOException e) {
+            throw new IllegalStateException();
+        }
+    }
+}
diff --git a/independent-projects/bootstrap/core/src/test/resources/local-workspace/root/module1/pom.xml b/independent-projects/bootstrap/core/src/test/resources/local-workspace/root/module1/pom.xml
new file mode 100644
index 0000000000000..a30e81ad34f78
--- /dev/null
+++ b/independent-projects/bootstrap/core/src/test/resources/local-workspace/root/module1/pom.xml
@@ -0,0 +1,13 @@
+
+
+    4.0.0
+
+    
+        org.acme
+        root
+        1.0
+        ../
+    
+
+    root-module1
+
diff --git a/independent-projects/bootstrap/core/src/test/resources/local-workspace/root/pom.xml b/independent-projects/bootstrap/core/src/test/resources/local-workspace/root/pom.xml
new file mode 100644
index 0000000000000..4b6d30d45ede5
--- /dev/null
+++ b/independent-projects/bootstrap/core/src/test/resources/local-workspace/root/pom.xml
@@ -0,0 +1,14 @@
+
+
+    4.0.0
+
+    org.acme
+    root
+    1.0
+    pom
+
+    
+        module1
+    
+
+
diff --git a/independent-projects/bootstrap/maven-plugin/pom.xml b/independent-projects/bootstrap/maven-plugin/pom.xml
new file mode 100644
index 0000000000000..e9df5ef5e1165
--- /dev/null
+++ b/independent-projects/bootstrap/maven-plugin/pom.xml
@@ -0,0 +1,84 @@
+
+
+    4.0.0
+    
+        io.quarkus
+        quarkus-bootstrap-parent
+        999-SNAPSHOT
+        ../
+    
+    quarkus-bootstrap-maven-plugin
+    Quarkus - Bootstrap - Maven plugin
+    maven-plugin
+    
+        3.5.2
+    
+    
+        
+            
+                org.apache.maven.plugins
+                maven-plugin-plugin
+                
+                    quarkus-bootstrap
+                    true
+                
+                
+                    
+                        help-goal
+                        
+                            helpmojo
+                        
+                    
+                    
+                        default-descriptor
+                        process-classes
+                    
+                
+            
+        
+    
+    
+        
+            ${project.groupId}
+            quarkus-bootstrap-core
+        
+        
+            ${project.groupId}
+            quarkus-bootstrap-core
+            test-jar
+            test
+        
+        
+            org.apache.maven
+            maven-plugin-api
+        
+        
+            org.apache.maven.plugin-tools
+            maven-plugin-annotations
+        
+        
+            org.apache.maven
+            maven-core
+        
+        
+            junit
+            junit
+        
+    
+
\ No newline at end of file
diff --git a/independent-projects/bootstrap/maven-plugin/src/main/java/io/quarkus/maven/AbstractTreeMojo.java b/independent-projects/bootstrap/maven-plugin/src/main/java/io/quarkus/maven/AbstractTreeMojo.java
new file mode 100644
index 0000000000000..bf678ac78795b
--- /dev/null
+++ b/independent-projects/bootstrap/maven-plugin/src/main/java/io/quarkus/maven/AbstractTreeMojo.java
@@ -0,0 +1,71 @@
+package io.quarkus.maven;
+
+import java.util.List;
+
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugins.annotations.Component;
+import org.apache.maven.plugins.annotations.Parameter;
+import org.apache.maven.project.MavenProject;
+import org.eclipse.aether.RepositorySystem;
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.repository.RemoteRepository;
+
+import io.quarkus.bootstrap.model.AppArtifact;
+import io.quarkus.bootstrap.resolver.AppModelResolverException;
+import io.quarkus.bootstrap.resolver.BootstrapAppModelResolver;
+import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver;
+
+public class AbstractTreeMojo extends AbstractMojo {
+    /**
+     * The entry point to Aether, i.e. the component doing all the work.
+     *
+     * @component
+     */
+    @Component
+    protected RepositorySystem repoSystem;
+
+    /**
+     * The current repository/network configuration of Maven.
+     *
+     * @parameter default-value="${repositorySystemSession}"
+     * @readonly
+     */
+    @Parameter(defaultValue = "${repositorySystemSession}", readonly = true)
+    protected RepositorySystemSession repoSession;
+
+    /**
+     * The project's remote repositories to use for the resolution of artifacts and their dependencies.
+     *
+     * @parameter default-value="${project.remoteProjectRepositories}"
+     * @readonly
+     */
+    @Parameter(defaultValue = "${project.remoteProjectRepositories}", readonly = true, required = true)
+    protected List repos;
+
+    @Parameter(defaultValue = "${project}", readonly = true, required = true)
+    protected MavenProject project;
+
+    @Override
+    public void execute() throws MojoExecutionException, MojoFailureException {
+        final AppArtifact appArtifact = new AppArtifact(project.getGroupId(), project.getArtifactId(), project.getVersion());
+        final BootstrapAppModelResolver modelResolver;
+        try {
+            modelResolver = new BootstrapAppModelResolver(
+                    MavenArtifactResolver.builder()
+                            .setRepositorySystem(repoSystem)
+                            .setRepositorySystemSession(repoSession)
+                            .setRemoteRepositories(repos)
+                            .build());
+            setupResolver(modelResolver);
+            modelResolver.setBuildTreeLogger(s -> getLog().info(s));
+            modelResolver.resolveModel(appArtifact);
+        } catch (AppModelResolverException e) {
+            throw new MojoExecutionException("Failed to resolve application model " + appArtifact + " dependencies", e);
+        }
+    }
+
+    protected void setupResolver(BootstrapAppModelResolver modelResolver) {
+    }
+}
diff --git a/independent-projects/bootstrap/maven-plugin/src/main/java/io/quarkus/maven/BuildTreeMojo.java b/independent-projects/bootstrap/maven-plugin/src/main/java/io/quarkus/maven/BuildTreeMojo.java
new file mode 100644
index 0000000000000..cf56f6e3c1119
--- /dev/null
+++ b/independent-projects/bootstrap/maven-plugin/src/main/java/io/quarkus/maven/BuildTreeMojo.java
@@ -0,0 +1,15 @@
+/**
+ *
+ */
+package io.quarkus.maven;
+
+import org.apache.maven.plugins.annotations.LifecyclePhase;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.ResolutionScope;
+
+/**
+ * Displays Quarkus application build dependency tree including the deployment ones.
+ */
+@Mojo(name = "build-tree", defaultPhase = LifecyclePhase.NONE, requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME)
+public class BuildTreeMojo extends AbstractTreeMojo {
+}
diff --git a/independent-projects/bootstrap/maven-plugin/src/main/java/io/quarkus/maven/DevModeTreeMojo.java b/independent-projects/bootstrap/maven-plugin/src/main/java/io/quarkus/maven/DevModeTreeMojo.java
new file mode 100644
index 0000000000000..bf260379b7e01
--- /dev/null
+++ b/independent-projects/bootstrap/maven-plugin/src/main/java/io/quarkus/maven/DevModeTreeMojo.java
@@ -0,0 +1,20 @@
+/**
+ *
+ */
+package io.quarkus.maven;
+
+import org.apache.maven.plugins.annotations.LifecyclePhase;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.ResolutionScope;
+import io.quarkus.bootstrap.resolver.BootstrapAppModelResolver;
+
+/**
+ * Displays Quarkus application dependency tree used to set up the classpath for the dev mode.
+ */
+@Mojo(name = "dev-mode-tree", defaultPhase = LifecyclePhase.NONE, requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME)
+public class DevModeTreeMojo extends AbstractTreeMojo {
+    @Override
+    protected void setupResolver(BootstrapAppModelResolver modelResolver) {
+        modelResolver.setDevMode(true);
+    }
+}
diff --git a/independent-projects/bootstrap/maven-plugin/src/main/java/io/quarkus/maven/ExtensionDescriptorMojo.java b/independent-projects/bootstrap/maven-plugin/src/main/java/io/quarkus/maven/ExtensionDescriptorMojo.java
new file mode 100644
index 0000000000000..14bd681c52c5b
--- /dev/null
+++ b/independent-projects/bootstrap/maven-plugin/src/main/java/io/quarkus/maven/ExtensionDescriptorMojo.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2019 Red Hat, Inc.
+ *
+ * Licensed 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.maven;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.Properties;
+
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugins.annotations.Component;
+import org.apache.maven.plugins.annotations.LifecyclePhase;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+import org.apache.maven.plugins.annotations.ResolutionScope;
+import org.eclipse.aether.RepositorySystem;
+import org.eclipse.aether.RepositorySystemSession;
+import org.eclipse.aether.repository.RemoteRepository;
+import io.quarkus.bootstrap.BootstrapConstants;
+
+/**
+ * Generates Quarkus extension descriptor for the runtime artifact.
+ *
+ * @author Alexey Loubyansky
+ */
+@Mojo(name = "extension-descriptor", defaultPhase = LifecyclePhase.COMPILE, requiresDependencyResolution = ResolutionScope.COMPILE_PLUS_RUNTIME)
+public class ExtensionDescriptorMojo extends AbstractMojo {
+
+    /**
+     * The entry point to Aether, i.e. the component doing all the work.
+     *
+     * @component
+     */
+    @Component
+    private RepositorySystem repoSystem;
+
+    /**
+     * The current repository/network configuration of Maven.
+     *
+     * @parameter default-value="${repositorySystemSession}"
+     * @readonly
+     */
+    @Parameter(defaultValue = "${repositorySystemSession}", readonly = true)
+    private RepositorySystemSession repoSession;
+
+    /**
+     * The project's remote repositories to use for the resolution of artifacts and their dependencies.
+     *
+     * @parameter default-value="${project.remoteProjectRepositories}"
+     * @readonly
+     */
+    @Parameter( defaultValue = "${project.remoteProjectRepositories}", readonly = true, required = true )
+    private List repos;
+
+    /**
+     * The directory for compiled classes.
+     */
+    @Parameter(readonly = true, required = true, defaultValue = "${project.build.outputDirectory}")
+    private File outputDirectory;
+
+    @Parameter(required = true)
+    private String deployment;
+
+    @Override
+    public void execute() throws MojoExecutionException {
+
+        final Properties props = new Properties();
+        props.setProperty(BootstrapConstants.PROP_DEPLOYMENT_ARTIFACT, deployment);
+
+        final Path output = outputDirectory.toPath().resolve(BootstrapConstants.META_INF);
+        try {
+            Files.createDirectories(output);
+            try (BufferedWriter writer = Files.newBufferedWriter(output.resolve(BootstrapConstants.DESCRIPTOR_FILE_NAME))) {
+                props.store(writer, "Generated by extension-descriptor");
+            }
+        } catch(IOException e) {
+            throw new MojoExecutionException("Failed to persist extension descriptor " + output.resolve(BootstrapConstants.DESCRIPTOR_FILE_NAME), e);
+        }
+    }
+}
diff --git a/independent-projects/bootstrap/maven-plugin/src/main/resources/META-INF/m2e/lifecycle-mapping-metadata.xml b/independent-projects/bootstrap/maven-plugin/src/main/resources/META-INF/m2e/lifecycle-mapping-metadata.xml
new file mode 100644
index 0000000000000..1728d5c90c7dd
--- /dev/null
+++ b/independent-projects/bootstrap/maven-plugin/src/main/resources/META-INF/m2e/lifecycle-mapping-metadata.xml
@@ -0,0 +1,17 @@
+
+  
+    
+      
+        
+          extension-descriptor
+        
+      
+      
+        
+          true
+          false
+        
+      
+    
+  
+
\ No newline at end of file
diff --git a/independent-projects/bootstrap/maven-plugin/src/test/java/io/quarkus/maven/BuildTreeMojoTest.java b/independent-projects/bootstrap/maven-plugin/src/test/java/io/quarkus/maven/BuildTreeMojoTest.java
new file mode 100644
index 0000000000000..c0b4b9b07fb2e
--- /dev/null
+++ b/independent-projects/bootstrap/maven-plugin/src/test/java/io/quarkus/maven/BuildTreeMojoTest.java
@@ -0,0 +1,16 @@
+package io.quarkus.maven;
+
+import io.quarkus.maven.BuildTreeMojo;
+
+public class BuildTreeMojoTest extends TreeMojoTestBase {
+
+    @Override
+    protected AbstractTreeMojo newTreeMojo() {
+        return new BuildTreeMojo();
+    }
+
+    @Override
+    protected String mojoName() {
+        return "build-tree";
+    }
+}
diff --git a/independent-projects/bootstrap/maven-plugin/src/test/java/io/quarkus/maven/DevModeTreeMojoTest.java b/independent-projects/bootstrap/maven-plugin/src/test/java/io/quarkus/maven/DevModeTreeMojoTest.java
new file mode 100644
index 0000000000000..21891dd619143
--- /dev/null
+++ b/independent-projects/bootstrap/maven-plugin/src/test/java/io/quarkus/maven/DevModeTreeMojoTest.java
@@ -0,0 +1,14 @@
+package io.quarkus.maven;
+
+public class DevModeTreeMojoTest extends TreeMojoTestBase {
+
+    @Override
+    protected AbstractTreeMojo newTreeMojo() {
+        return new DevModeTreeMojo();
+    }
+
+    @Override
+    protected String mojoName() {
+        return "dev-mode-tree";
+    }
+}
diff --git a/independent-projects/bootstrap/maven-plugin/src/test/java/io/quarkus/maven/TreeMojoTestBase.java b/independent-projects/bootstrap/maven-plugin/src/test/java/io/quarkus/maven/TreeMojoTestBase.java
new file mode 100644
index 0000000000000..c70e4b418cc69
--- /dev/null
+++ b/independent-projects/bootstrap/maven-plugin/src/test/java/io/quarkus/maven/TreeMojoTestBase.java
@@ -0,0 +1,116 @@
+package io.quarkus.maven;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.maven.model.Model;
+import org.apache.maven.project.MavenProject;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import io.quarkus.bootstrap.resolver.BootstrapAppModelResolver;
+import io.quarkus.bootstrap.resolver.TsArtifact;
+import io.quarkus.bootstrap.resolver.TsDependency;
+import io.quarkus.bootstrap.resolver.TsQuarkusExt;
+import io.quarkus.bootstrap.resolver.TsRepoBuilder;
+import io.quarkus.bootstrap.resolver.maven.MavenArtifactResolver;
+import io.quarkus.bootstrap.util.IoUtils;
+
+public abstract class TreeMojoTestBase {
+    protected Path workDir;
+    protected Path repoHome;
+
+    protected MavenArtifactResolver mvnResolver;
+    protected TsRepoBuilder repoBuilder;
+    protected TsArtifact app;
+    protected Model appModel;
+
+    @Before
+    public void setup() throws Exception {
+        workDir = IoUtils.createRandomTmpDir();
+        repoHome = IoUtils.mkdirs(workDir.resolve("repo"));
+
+        mvnResolver = MavenArtifactResolver.builder()
+                .setOffline(true)
+                .setRepoHome(repoHome)
+                .setRemoteRepositories(Collections.emptyList())
+                .build();
+
+        repoBuilder = TsRepoBuilder.getInstance(new BootstrapAppModelResolver(mvnResolver), workDir);
+        initRepo();
+    }
+
+    protected void initRepo() throws Exception {
+        final TsQuarkusExt coreExt = new TsQuarkusExt("test-core-ext");
+        app = TsArtifact.jar("test-app")
+                .addDependency(new TsArtifact(TsArtifact.DEFAULT_GROUP_ID, "artifact-with-classifier", "classifier", "jar", TsArtifact.DEFAULT_VERSION))
+                .addDependency(new TsQuarkusExt("test-ext2")
+                        .addDependency(new TsQuarkusExt("test-ext1").addDependency(coreExt)))
+                .addDependency(new TsDependency(TsArtifact.jar("optional"), true))
+                .addDependency(new TsQuarkusExt("test-ext3").addDependency(coreExt))
+                .addDependency(new TsDependency(TsArtifact.jar("provided"), "provided"))
+                .addDependency(new TsDependency(TsArtifact.jar("runtime"), "runtime"))
+                .addDependency(new TsDependency(TsArtifact.jar("test"), "test"));
+        appModel = app.getPomModel();
+        app.install(repoBuilder);
+    }
+
+    @After
+    public void cleanup() {
+        if(workDir != null) {
+            IoUtils.recursiveDelete(workDir);
+        }
+    }
+
+    protected abstract AbstractTreeMojo newTreeMojo();
+
+    protected abstract String mojoName();
+
+    @Test
+    public void test() throws Exception {
+
+        final AbstractTreeMojo mojo = newTreeMojo();
+        mojo.project = new MavenProject();
+        mojo.project.setModel(appModel);
+        mojo.project.setOriginalModel(appModel);
+
+        mojo.repoSystem = mvnResolver.getSystem();
+        mojo.repoSession = mvnResolver.getSession();
+        mojo.repos = mvnResolver.getRepositories();
+
+        final Path mojoLog = workDir.resolve("mojo.log");
+        final PrintStream defaultOut = System.out;
+
+        try (PrintStream logOut = new PrintStream(mojoLog.toFile(), "UTF-8")) {
+            System.setOut(logOut);
+            mojo.execute();
+        } finally {
+            System.setOut(defaultOut);
+        }
+
+        assertEquals(readInLowCase(Paths.get("").toAbsolutePath().resolve("target").resolve("test-classes")
+                .resolve(app.getArtifactFileName() + "." + mojoName())), readInLowCase(mojoLog));
+    }
+
+    private static List readInLowCase(Path p) throws IOException {
+        final List list = new ArrayList<>();
+        try(BufferedReader reader = Files.newBufferedReader(p)) {
+            String line = reader.readLine();
+            while(line != null) {
+                list.add(line.toLowerCase());
+                line = reader.readLine();
+            }
+        }
+        return list;
+    }
+}
diff --git a/independent-projects/bootstrap/maven-plugin/src/test/resources/test-app-1.jar.build-tree b/independent-projects/bootstrap/maven-plugin/src/test/resources/test-app-1.jar.build-tree
new file mode 100644
index 0000000000000..d200674abf166
--- /dev/null
+++ b/independent-projects/bootstrap/maven-plugin/src/test/resources/test-app-1.jar.build-tree
@@ -0,0 +1,12 @@
+[info] io.quarkus.bootstrap.test:test-app:jar:1
+[info] ├─ io.quarkus.bootstrap.test:artifact-with-classifier:jar:classifier:1 (compile)
+[info] ├─ io.quarkus.bootstrap.test:test-ext2-deployment:jar:1 (compile)
+[info] │  ├─ io.quarkus.bootstrap.test:test-ext2:jar:1 (compile)
+[info] │  │  └─ io.quarkus.bootstrap.test:test-ext1:jar:1 (compile)
+[info] │  └─ io.quarkus.bootstrap.test:test-ext1-deployment:jar:1 (compile)
+[info] ├─ io.quarkus.bootstrap.test:optional:jar:1 (compile optional)
+[info] ├─ io.quarkus.bootstrap.test:test-ext3-deployment:jar:1 (compile)
+[info] │  ├─ io.quarkus.bootstrap.test:test-ext3:jar:1 (compile)
+[info] │  │  └─ io.quarkus.bootstrap.test:test-core-ext:jar:1 (compile)
+[info] │  └─ io.quarkus.bootstrap.test:test-core-ext-deployment:jar:1 (compile)
+[info] └─ io.quarkus.bootstrap.test:runtime:jar:1 (runtime)
diff --git a/independent-projects/bootstrap/maven-plugin/src/test/resources/test-app-1.jar.dev-mode-tree b/independent-projects/bootstrap/maven-plugin/src/test/resources/test-app-1.jar.dev-mode-tree
new file mode 100644
index 0000000000000..4028beaf21278
--- /dev/null
+++ b/independent-projects/bootstrap/maven-plugin/src/test/resources/test-app-1.jar.dev-mode-tree
@@ -0,0 +1,13 @@
+[info] io.quarkus.bootstrap.test:test-app:jar:1
+[info] ├─ io.quarkus.bootstrap.test:artifact-with-classifier:jar:classifier:1 (compile)
+[info] ├─ io.quarkus.bootstrap.test:test-ext2-deployment:jar:1 (compile)
+[info] │  ├─ io.quarkus.bootstrap.test:test-ext2:jar:1 (compile)
+[info] │  │  └─ io.quarkus.bootstrap.test:test-ext1:jar:1 (compile)
+[info] │  └─ io.quarkus.bootstrap.test:test-ext1-deployment:jar:1 (compile)
+[info] ├─ io.quarkus.bootstrap.test:optional:jar:1 (compile optional)
+[info] ├─ io.quarkus.bootstrap.test:test-ext3-deployment:jar:1 (compile)
+[info] │  ├─ io.quarkus.bootstrap.test:test-ext3:jar:1 (compile)
+[info] │  │  └─ io.quarkus.bootstrap.test:test-core-ext:jar:1 (compile)
+[info] │  └─ io.quarkus.bootstrap.test:test-core-ext-deployment:jar:1 (compile)
+[info] ├─ io.quarkus.bootstrap.test:provided:jar:1 (provided)
+[info] └─ io.quarkus.bootstrap.test:runtime:jar:1 (runtime)
diff --git a/independent-projects/bootstrap/pom.xml b/independent-projects/bootstrap/pom.xml
new file mode 100644
index 0000000000000..4b11d1e3606a0
--- /dev/null
+++ b/independent-projects/bootstrap/pom.xml
@@ -0,0 +1,268 @@
+
+
+    4.0.0
+    
+        org.jboss
+        jboss-parent
+        31
+    
+    io.quarkus
+    quarkus-bootstrap-parent
+    Quarkus - Bootstrap - Parent
+    pom
+    999-SNAPSHOT
+    
+        
+            Apache License, Version 2.0
+            repo
+            http://www.apache.org/licenses/LICENSE-2.0.html
+        
+    
+    
+        UTF-8
+        1.8
+        1.8
+        
+        3.3.2.Final
+        4.12
+        3.5.4
+        3.5.2
+        1.1.1
+        1.6.8
+    
+    
+        core
+        maven-plugin
+    
+    
+        
+            
+                ${project.groupId}
+                quarkus-bootstrap-core
+                ${project.version}
+            
+            
+                ${project.groupId}
+                quarkus-bootstrap-core
+                ${project.version}
+                test-jar
+                test
+            
+            
+                ${project.groupId}
+                quarkus-bootstrap-maven-plugin
+                ${project.version}
+            
+            
+                org.apache.maven
+                maven-plugin-api
+                ${maven-core.version}
+            
+            
+                org.apache.maven
+                maven-model
+                ${maven-core.version}
+            
+            
+                org.apache.maven
+                maven-core
+                ${maven-core.version}
+            
+            
+                org.apache.maven
+                maven-embedder
+                ${maven-core.version}
+                
+                    
+                        org.sonatype.plexus
+                        plexus-sec-dispatcher
+                    
+                    
+                        org.sonatype.plexus
+                        plexus-cipher
+                    
+                
+            
+            
+                org.apache.maven
+                maven-resolver-provider
+                ${maven-core.version}
+            
+            
+                org.apache.maven
+                maven-settings-builder
+                ${maven-core.version}
+            
+            
+                org.apache.maven.plugin-tools
+                maven-plugin-annotations
+                ${maven-plugin-annotations.version}
+                
+                    
+                        org.apache.maven
+                        maven-artifact
+                    
+                
+            
+            
+                org.apache.maven.resolver
+                maven-resolver-connector-basic
+                ${maven-resolver.version}
+            
+            
+                org.apache.maven.resolver
+                maven-resolver-transport-file
+                ${maven-resolver.version}
+            
+            
+                org.apache.maven.resolver
+                maven-resolver-transport-http
+                ${maven-resolver.version}
+            
+            
+                org.jboss.logging
+                jboss-logging
+                ${jboss-logging.version}
+            
+            
+                junit
+                junit
+                test
+                ${junit.version}
+            
+        
+    
+    
+        
+            
+                
+                    maven-javadoc-plugin
+                    
+                        true
+                        none
+                    
+                
+                
+                    org.apache.maven.plugins
+                    maven-enforcer-plugin
+                    
+                        
+                            enforce
+                            
+                                
+                                    
+                                        
+                                            
+                                            org.jboss.spec.javax.annotation:jboss-annotations-api_1.2_spec
+                                            org.jboss.spec.javax.annotation:jboss-annotations-api_1.3_spec
+                                        
+                                    
+                                
+                            
+                            
+                                enforce
+                            
+                        
+                    
+                
+            
+        
+    
+
+    
+        
+            sonatype-nexus-snapshots
+            https://oss.sonatype.org/content/repositories/snapshots
+        
+        
+            sonatype-nexus-release
+            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
+                            true
+                            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
+                                
+                            
+                        
+                    
+                
+            
+        
+    
+
diff --git a/integration-tests/amazon-lambda/pom.xml b/integration-tests/amazon-lambda/pom.xml
new file mode 100644
index 0000000000000..0e2a298f8e4b4
--- /dev/null
+++ b/integration-tests/amazon-lambda/pom.xml
@@ -0,0 +1,128 @@
+
+
+
+
+    
+        quarkus-integration-tests-parent
+        io.quarkus
+        999-SNAPSHOT
+        ../
+    
+    4.0.0
+
+    quarkus-integration-test-amazon-lambda
+    Quarkus - Integration Tests - Amazon Lambda
+    Module that contains Amazon Lambda related tests
+    
+        
+            io.quarkus
+            quarkus-amazon-lambda
+        
+
+        
+        
+            io.quarkus
+            quarkus-junit5
+            test
+        
+        
+            io.quarkus
+            quarkus-test-amazon-lambda
+            test
+        
+    
+
+    
+        
+            
+                src/main/resources
+                true
+            
+        
+        
+            
+                ${project.groupId}
+                quarkus-maven-plugin
+                
+                    
+                        
+                            build
+                        
+                    
+                
+            
+        
+    
+
+    
+        
+            native-image
+            
+                
+                    native
+                
+            
+            
+                
+                    
+                        org.apache.maven.plugins
+                        maven-failsafe-plugin
+                        
+                            
+                                
+                                    integration-test
+                                    verify
+                                
+                                
+                                    
+                                        
+                                            ${project.build.directory}/${project.build.finalName}-runner
+                                        
+                                    
+                                
+                            
+                        
+                    
+                    
+                        ${project.groupId}
+                        quarkus-maven-plugin
+                        
+                            
+                                native-image
+                                
+                                    native-image
+                                
+                                
+                                    false
+                                    true
+                                    true
+                                    ${graalvmHome}
+                                    false
+                                    false
+                                
+                            
+                        
+                    
+                
+            
+        
+
+    
+
+
diff --git a/integration-tests/amazon-lambda/src/main/java/io/quarkus/it/amazon/lambda/InputObject.java b/integration-tests/amazon-lambda/src/main/java/io/quarkus/it/amazon/lambda/InputObject.java
new file mode 100644
index 0000000000000..cc451a04c596f
--- /dev/null
+++ b/integration-tests/amazon-lambda/src/main/java/io/quarkus/it/amazon/lambda/InputObject.java
@@ -0,0 +1,25 @@
+package io.quarkus.it.amazon.lambda;
+
+public class InputObject {
+
+    private String name;
+    private String greeting;
+
+    public String getName() {
+        return name;
+    }
+
+    public InputObject setName(String name) {
+        this.name = name;
+        return this;
+    }
+
+    public String getGreeting() {
+        return greeting;
+    }
+
+    public InputObject setGreeting(String greeting) {
+        this.greeting = greeting;
+        return this;
+    }
+}
diff --git a/integration-tests/amazon-lambda/src/main/java/io/quarkus/it/amazon/lambda/OutputObject.java b/integration-tests/amazon-lambda/src/main/java/io/quarkus/it/amazon/lambda/OutputObject.java
new file mode 100644
index 0000000000000..265c8fa1e7c10
--- /dev/null
+++ b/integration-tests/amazon-lambda/src/main/java/io/quarkus/it/amazon/lambda/OutputObject.java
@@ -0,0 +1,26 @@
+package io.quarkus.it.amazon.lambda;
+
+public class OutputObject {
+
+    private String result;
+
+    private String requestId;
+
+    public String getResult() {
+        return result;
+    }
+
+    public String getRequestId() {
+        return requestId;
+    }
+
+    public OutputObject setResult(String result) {
+        this.result = result;
+        return this;
+    }
+
+    public OutputObject setRequestId(String requestId) {
+        this.requestId = requestId;
+        return this;
+    }
+}
diff --git a/integration-tests/amazon-lambda/src/main/java/io/quarkus/it/amazon/lambda/ProcessingService.java b/integration-tests/amazon-lambda/src/main/java/io/quarkus/it/amazon/lambda/ProcessingService.java
new file mode 100644
index 0000000000000..c6d04bd4d96ff
--- /dev/null
+++ b/integration-tests/amazon-lambda/src/main/java/io/quarkus/it/amazon/lambda/ProcessingService.java
@@ -0,0 +1,19 @@
+package io.quarkus.it.amazon.lambda;
+
+import javax.enterprise.context.ApplicationScoped;
+
+@ApplicationScoped
+public class ProcessingService {
+
+    public static final String CAN_ONLY_GREET_NICKNAMES = "Can only greet nicknames";
+
+    public OutputObject proces(InputObject input) {
+        if (input.getName().equals("Stuart")) {
+            throw new IllegalArgumentException(CAN_ONLY_GREET_NICKNAMES);
+        }
+        String result = input.getGreeting() + " " + input.getName();
+        OutputObject out = new OutputObject();
+        out.setResult(result);
+        return out;
+    }
+}
diff --git a/integration-tests/amazon-lambda/src/main/java/io/quarkus/it/amazon/lambda/TestLambda.java b/integration-tests/amazon-lambda/src/main/java/io/quarkus/it/amazon/lambda/TestLambda.java
new file mode 100644
index 0000000000000..f392be6af8cb9
--- /dev/null
+++ b/integration-tests/amazon-lambda/src/main/java/io/quarkus/it/amazon/lambda/TestLambda.java
@@ -0,0 +1,17 @@
+package io.quarkus.it.amazon.lambda;
+
+import javax.inject.Inject;
+
+import com.amazonaws.services.lambda.runtime.Context;
+import com.amazonaws.services.lambda.runtime.RequestHandler;
+
+public class TestLambda implements RequestHandler {
+
+    @Inject
+    ProcessingService service;
+
+    @Override
+    public OutputObject handleRequest(InputObject input, Context context) {
+        return service.proces(input).setRequestId(context.getAwsRequestId());
+    }
+}
diff --git a/integration-tests/amazon-lambda/src/test/java/io/quarkus/it/amazon/lambda/AmazonLambdaSimpleIT.java b/integration-tests/amazon-lambda/src/test/java/io/quarkus/it/amazon/lambda/AmazonLambdaSimpleIT.java
new file mode 100644
index 0000000000000..c8cdcb95ca7aa
--- /dev/null
+++ b/integration-tests/amazon-lambda/src/test/java/io/quarkus/it/amazon/lambda/AmazonLambdaSimpleIT.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2019 Red Hat, Inc.
+ *
+ * Licensed 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.it.amazon.lambda;
+
+import io.quarkus.test.junit.SubstrateTest;
+
+@SubstrateTest
+public class AmazonLambdaSimpleIT extends AmazonLambdaSimpleTestCase {
+}
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
new file mode 100644
index 0000000000000..dae0bf3248646
--- /dev/null
+++ b/integration-tests/amazon-lambda/src/test/java/io/quarkus/it/amazon/lambda/AmazonLambdaSimpleTestCase.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2019 Red Hat, Inc.
+ *
+ * Licensed 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.it.amazon.lambda;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import io.quarkus.amazon.lambda.test.LambdaClient;
+import io.quarkus.amazon.lambda.test.LambdaException;
+import io.quarkus.test.junit.QuarkusTest;
+
+@QuarkusTest
+public class AmazonLambdaSimpleTestCase {
+
+    @Test
+    public void testSimpleLambdaSucess() throws Exception {
+        InputObject in = new InputObject();
+        in.setGreeting("Hello");
+        in.setName("Stu");
+        OutputObject out = LambdaClient.invoke(OutputObject.class, in);
+        Assertions.assertEquals("Hello Stu", out.getResult());
+        Assertions.assertTrue(out.getRequestId().matches("aws-request-\\d"), "Expected requestId as 'aws-request-'");
+    }
+
+    @Test
+    public void testSimpleLambdaFailure() throws Exception {
+        InputObject in = new InputObject();
+        in.setGreeting("Hello");
+        in.setName("Stuart");
+        try {
+            OutputObject out = LambdaClient.invoke(OutputObject.class, in);
+            out.getResult();
+            Assertions.fail();
+        } catch (LambdaException e) {
+            Assertions.assertEquals(ProcessingService.CAN_ONLY_GREET_NICKNAMES, e.getMessage());
+            Assertions.assertEquals(IllegalArgumentException.class.getName(), e.getType());
+        }
+
+    }
+}
diff --git a/integration-tests/camel-aws-s3/pom.xml b/integration-tests/camel-aws-s3/pom.xml
new file mode 100644
index 0000000000000..a765467363c40
--- /dev/null
+++ b/integration-tests/camel-aws-s3/pom.xml
@@ -0,0 +1,165 @@
+
+
+    
+        quarkus-integration-tests-parent
+        io.quarkus
+        999-SNAPSHOT
+        ../
+    
+    4.0.0
+
+    quarkus-integration-test-camel-aws-s3
+    Quarkus - Integration Tests - Camel - AWS S3
+    The camel integration tests
+    
+        
+            io.quarkus
+            quarkus-arc
+        
+        
+            io.quarkus
+            quarkus-resteasy
+        
+        
+            io.quarkus
+            quarkus-core
+        
+
+        
+            io.quarkus
+            quarkus-camel-core
+        
+
+        
+            io.quarkus
+            quarkus-camel-aws-s3
+        
+
+        
+            org.jboss.slf4j
+            slf4j-jboss-logging
+            provided
+        
+        
+            org.apache.camel
+            camel-core
+            provided
+        
+
+        
+        
+            io.quarkus
+            quarkus-junit5
+            test
+        
+        
+            org.glassfish
+            javax.json
+            test
+        
+        
+            io.rest-assured
+            rest-assured
+            test
+        
+    
+
+    
+        
+            
+                src/main/resources
+                true
+            
+        
+        
+            
+                maven-surefire-plugin
+                
+                    true
+                
+            
+            
+                maven-failsafe-plugin
+                
+                    true
+                
+            
+            
+                ${project.groupId}
+                quarkus-maven-plugin
+                
+                    
+                        
+                            build
+                        
+                    
+                
+            
+            
+                org.apache.maven.plugins
+                maven-enforcer-plugin
+                
+                    true
+                
+            
+        
+    
+
+    
+        
+            native-image
+            
+                
+                    native-camel
+                
+            
+            
+                
+                    
+                        org.apache.maven.plugins
+                        maven-failsafe-plugin
+                        
+                            
+                                
+                                    integration-test
+                                    verify
+                                
+                                
+                                    
+                                        ${project.build.directory}/${project.build.finalName}-runner
+                                    
+                                
+                            
+                        
+                    
+                    
+                        ${project.groupId}
+                        quarkus-maven-plugin
+                        ${project.version}
+                        
+                            
+                                native-image
+                                
+                                    native-image
+                                
+                                
+                                    false
+                                    true
+                                    true
+                                    false
+                                    false
+                                    ${graalvmHome}
+                                    true
+                                    true
+                                
+                            
+                        
+                    
+                
+            
+        
+    
+
+
diff --git a/integration-tests/camel-core/src/main/java/io/quarkus/camel/it/core/CamelApplication.java b/integration-tests/camel-aws-s3/src/main/java/io/quarkus/it/camel/aws/CamelApplication.java
similarity index 81%
rename from integration-tests/camel-core/src/main/java/io/quarkus/camel/it/core/CamelApplication.java
rename to integration-tests/camel-aws-s3/src/main/java/io/quarkus/it/camel/aws/CamelApplication.java
index 3b5a0f7b7ca48..e3586c428bf5e 100644
--- a/integration-tests/camel-core/src/main/java/io/quarkus/camel/it/core/CamelApplication.java
+++ b/integration-tests/camel-aws-s3/src/main/java/io/quarkus/it/camel/aws/CamelApplication.java
@@ -1,4 +1,4 @@
-package io.quarkus.camel.it.core;
+package io.quarkus.it.camel.aws;
 
 import javax.ws.rs.ApplicationPath;
 import javax.ws.rs.core.Application;
diff --git a/integration-tests/camel-aws-s3/src/main/java/io/quarkus/it/camel/aws/CamelRoute.java b/integration-tests/camel-aws-s3/src/main/java/io/quarkus/it/camel/aws/CamelRoute.java
new file mode 100644
index 0000000000000..457d64c4a187e
--- /dev/null
+++ b/integration-tests/camel-aws-s3/src/main/java/io/quarkus/it/camel/aws/CamelRoute.java
@@ -0,0 +1,20 @@
+package io.quarkus.it.camel.aws;
+
+import org.apache.camel.builder.RouteBuilder;
+
+import io.quarkus.runtime.annotations.RegisterForReflection;
+
+@RegisterForReflection
+public class CamelRoute extends RouteBuilder {
+
+    @Override
+    public void configure() {
+        from("timer:quarkus?repeatCount=1")
+                .setHeader("CamelAwsS3Key", constant("testquarkus"))
+                .setBody(constant("Quarkus is great!"))
+                .to("aws-s3://devvox1")
+                .to("log:sf?showAll=true");
+
+    }
+
+}
diff --git a/integration-tests/camel-aws-s3/src/main/resources/application.properties b/integration-tests/camel-aws-s3/src/main/resources/application.properties
new file mode 100644
index 0000000000000..a9d2a47a33607
--- /dev/null
+++ b/integration-tests/camel-aws-s3/src/main/resources/application.properties
@@ -0,0 +1,14 @@
+quarkus.ssl.native=true
+
+camel.context.name=quarkus-camel-aws-s3
+camel.properties.prefixToken={{
+camel.properties.suffixToken=}}
+camel.conf={{env:CAMEL_CONF:}}
+camel.confd={{env:CAMEL_CONFD:}}
+camel.routes.dump=true
+camel.routes.defer=true
+camel.routes.locations=
+
+camel.component.aws-s3.access-key={{env:S3_ACCESS_KEY}}
+camel.component.aws-s3.secret-key={{env:S3_SECRET_KEY}}
+camel.component.aws-s3.region={{env:S3_REGION}}
diff --git a/integration-tests/camel-core/pom.xml b/integration-tests/camel-core/pom.xml
index d0a6d1eb18376..f5b7659b33a30 100644
--- a/integration-tests/camel-core/pom.xml
+++ b/integration-tests/camel-core/pom.xml
@@ -17,32 +17,26 @@
         
             io.quarkus
             quarkus-arc
-            provided
         
         
             io.quarkus
             quarkus-resteasy
-            provided
         
         
             io.quarkus
             quarkus-core
-            provided
         
         
             io.quarkus
             quarkus-camel-core
-            provided
         
         
             io.quarkus
             quarkus-camel-netty4-http
-            provided
         
         
             io.quarkus
             quarkus-camel-infinispan
-            provided
         
 
         
@@ -122,7 +116,7 @@
             native-image
             
                 
-                    native
+                    native-camel
                 
             
             
diff --git a/integration-tests/camel-salesforce/src/main/java/io/quarkus/camel/it/salesforce/CamelApplication.java b/integration-tests/camel-core/src/main/java/io/quarkus/it/camel/core/CamelApplication.java
similarity index 79%
rename from integration-tests/camel-salesforce/src/main/java/io/quarkus/camel/it/salesforce/CamelApplication.java
rename to integration-tests/camel-core/src/main/java/io/quarkus/it/camel/core/CamelApplication.java
index 6730a0c2eed57..d844d02945cf2 100644
--- a/integration-tests/camel-salesforce/src/main/java/io/quarkus/camel/it/salesforce/CamelApplication.java
+++ b/integration-tests/camel-core/src/main/java/io/quarkus/it/camel/core/CamelApplication.java
@@ -1,4 +1,4 @@
-package io.quarkus.camel.it.salesforce;
+package io.quarkus.it.camel.core;
 
 import javax.ws.rs.ApplicationPath;
 import javax.ws.rs.core.Application;
diff --git a/integration-tests/camel-core/src/main/java/io/quarkus/camel/it/core/CamelRoute.java b/integration-tests/camel-core/src/main/java/io/quarkus/it/camel/core/CamelRoute.java
similarity index 99%
rename from integration-tests/camel-core/src/main/java/io/quarkus/camel/it/core/CamelRoute.java
rename to integration-tests/camel-core/src/main/java/io/quarkus/it/camel/core/CamelRoute.java
index 47d1dac30a560..4ca7ac9b7aa96 100644
--- a/integration-tests/camel-core/src/main/java/io/quarkus/camel/it/core/CamelRoute.java
+++ b/integration-tests/camel-core/src/main/java/io/quarkus/it/camel/core/CamelRoute.java
@@ -1,4 +1,4 @@
-package io.quarkus.camel.it.core;
+package io.quarkus.it.camel.core;
 
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
diff --git a/integration-tests/camel-core/src/main/java/io/quarkus/camel/it/core/CamelServlet.java b/integration-tests/camel-core/src/main/java/io/quarkus/it/camel/core/CamelServlet.java
similarity index 96%
rename from integration-tests/camel-core/src/main/java/io/quarkus/camel/it/core/CamelServlet.java
rename to integration-tests/camel-core/src/main/java/io/quarkus/it/camel/core/CamelServlet.java
index 8ad5b70171e8d..c7cc367dbb0ff 100644
--- a/integration-tests/camel-core/src/main/java/io/quarkus/camel/it/core/CamelServlet.java
+++ b/integration-tests/camel-core/src/main/java/io/quarkus/it/camel/core/CamelServlet.java
@@ -1,4 +1,4 @@
-package io.quarkus.camel.it.core;
+package io.quarkus.it.camel.core;
 
 import java.util.List;
 import java.util.stream.Collectors;
diff --git a/integration-tests/camel-core/src/test/java/io/quarkus/camel/it/core/CamelITCase.java b/integration-tests/camel-core/src/test/java/io/quarkus/it/camel/core/CamelITCase.java
similarity index 76%
rename from integration-tests/camel-core/src/test/java/io/quarkus/camel/it/core/CamelITCase.java
rename to integration-tests/camel-core/src/test/java/io/quarkus/it/camel/core/CamelITCase.java
index 476f1ca9d5d3d..0770eee2c378b 100644
--- a/integration-tests/camel-core/src/test/java/io/quarkus/camel/it/core/CamelITCase.java
+++ b/integration-tests/camel-core/src/test/java/io/quarkus/it/camel/core/CamelITCase.java
@@ -1,4 +1,4 @@
-package io.quarkus.camel.it.core;
+package io.quarkus.it.camel.core;
 
 import io.quarkus.test.junit.SubstrateTest;
 
diff --git a/integration-tests/camel-core/src/test/java/io/quarkus/camel/it/core/CamelInfinispanITCase.java b/integration-tests/camel-core/src/test/java/io/quarkus/it/camel/core/CamelInfinispanITCase.java
similarity index 79%
rename from integration-tests/camel-core/src/test/java/io/quarkus/camel/it/core/CamelInfinispanITCase.java
rename to integration-tests/camel-core/src/test/java/io/quarkus/it/camel/core/CamelInfinispanITCase.java
index 6ee2206c91b50..135138e351ff7 100644
--- a/integration-tests/camel-core/src/test/java/io/quarkus/camel/it/core/CamelInfinispanITCase.java
+++ b/integration-tests/camel-core/src/test/java/io/quarkus/it/camel/core/CamelInfinispanITCase.java
@@ -1,4 +1,4 @@
-package io.quarkus.camel.it.core;
+package io.quarkus.it.camel.core;
 
 import io.quarkus.test.junit.SubstrateTest;
 
diff --git a/integration-tests/camel-core/src/test/java/io/quarkus/camel/it/core/CamelInfinispanTest.java b/integration-tests/camel-core/src/test/java/io/quarkus/it/camel/core/CamelInfinispanTest.java
similarity index 94%
rename from integration-tests/camel-core/src/test/java/io/quarkus/camel/it/core/CamelInfinispanTest.java
rename to integration-tests/camel-core/src/test/java/io/quarkus/it/camel/core/CamelInfinispanTest.java
index ac83761c7f820..6f4f3bac63d25 100644
--- a/integration-tests/camel-core/src/test/java/io/quarkus/camel/it/core/CamelInfinispanTest.java
+++ b/integration-tests/camel-core/src/test/java/io/quarkus/it/camel/core/CamelInfinispanTest.java
@@ -1,4 +1,4 @@
-package io.quarkus.camel.it.core;
+package io.quarkus.it.camel.core;
 
 import static org.hamcrest.Matchers.is;
 
diff --git a/integration-tests/camel-core/src/test/java/io/quarkus/camel/it/core/CamelTest.java b/integration-tests/camel-core/src/test/java/io/quarkus/it/camel/core/CamelTest.java
similarity index 96%
rename from integration-tests/camel-core/src/test/java/io/quarkus/camel/it/core/CamelTest.java
rename to integration-tests/camel-core/src/test/java/io/quarkus/it/camel/core/CamelTest.java
index b7618b3fca3c5..e5ffe7322aae1 100644
--- a/integration-tests/camel-core/src/test/java/io/quarkus/camel/it/core/CamelTest.java
+++ b/integration-tests/camel-core/src/test/java/io/quarkus/it/camel/core/CamelTest.java
@@ -1,4 +1,4 @@
-package io.quarkus.camel.it.core;
+package io.quarkus.it.camel.core;
 
 import static org.hamcrest.Matchers.containsString;
 import static org.hamcrest.Matchers.is;
diff --git a/integration-tests/camel-core/src/test/java/io/quarkus/camel/it/core/InfinispanServerTestResource.java b/integration-tests/camel-core/src/test/java/io/quarkus/it/camel/core/InfinispanServerTestResource.java
similarity index 87%
rename from integration-tests/camel-core/src/test/java/io/quarkus/camel/it/core/InfinispanServerTestResource.java
rename to integration-tests/camel-core/src/test/java/io/quarkus/it/camel/core/InfinispanServerTestResource.java
index 770e24cb630db..98b59149b8d30 100644
--- a/integration-tests/camel-core/src/test/java/io/quarkus/camel/it/core/InfinispanServerTestResource.java
+++ b/integration-tests/camel-core/src/test/java/io/quarkus/it/camel/core/InfinispanServerTestResource.java
@@ -1,4 +1,7 @@
-package io.quarkus.camel.it.core;
+package io.quarkus.it.camel.core;
+
+import java.util.Collections;
+import java.util.Map;
 
 import org.infinispan.configuration.cache.ConfigurationBuilder;
 import org.infinispan.configuration.global.GlobalConfigurationBuilder;
@@ -14,13 +17,14 @@ public class InfinispanServerTestResource implements QuarkusTestResourceLifecycl
     private HotRodServer hotRodServer;
 
     @Override
-    public void start() {
+    public Map start() {
         TestResourceTracker.setThreadTestName("InfinispanServer");
         EmbeddedCacheManager ecm = TestCacheManagerFactory.createCacheManager(
                 new GlobalConfigurationBuilder().nonClusteredDefault().defaultCacheName("default"),
                 new ConfigurationBuilder());
         // Client connects to a non default port
         hotRodServer = HotRodTestingUtil.startHotRodServer(ecm, 11232);
+        return Collections.emptyMap();
     }
 
     @Override
diff --git a/integration-tests/camel-salesforce/pom.xml b/integration-tests/camel-salesforce/pom.xml
index 74ec0d1ea2185..94ad1987e2dbc 100644
--- a/integration-tests/camel-salesforce/pom.xml
+++ b/integration-tests/camel-salesforce/pom.xml
@@ -17,50 +17,29 @@
         
             io.quarkus
             quarkus-arc
-            provided
         
         
             io.quarkus
             quarkus-resteasy
-            provided
         
         
             io.quarkus
             quarkus-core
-            provided
-        
-        
-            io.quarkus
-            quarkus-core-runtime
-            provided
         
 
         
             io.quarkus
             quarkus-camel-core
-            provided
-        
-        
-            io.quarkus
-            quarkus-camel-core-runtime
-            provided
         
 
         
             io.quarkus
             quarkus-camel-salesforce
-            provided
-        
-        
-            io.quarkus
-            quarkus-camel-salesforce-runtime
-            provided
         
 
         
             org.jboss.slf4j
             slf4j-jboss-logging
-            1.1.0.Final
             provided
         
         
@@ -127,12 +106,13 @@
             
         
     
+
     
         
             native-image
             
                 
-                    native
+                    native-camel
                 
             
             
diff --git a/integration-tests/camel-salesforce/src/main/java/io/quarkus/it/camel/salesforce/CamelApplication.java b/integration-tests/camel-salesforce/src/main/java/io/quarkus/it/camel/salesforce/CamelApplication.java
new file mode 100644
index 0000000000000..075de854070a1
--- /dev/null
+++ b/integration-tests/camel-salesforce/src/main/java/io/quarkus/it/camel/salesforce/CamelApplication.java
@@ -0,0 +1,8 @@
+package io.quarkus.it.camel.salesforce;
+
+import javax.ws.rs.ApplicationPath;
+import javax.ws.rs.core.Application;
+
+@ApplicationPath("/test")
+public class CamelApplication extends Application {
+}
\ No newline at end of file
diff --git a/integration-tests/camel-salesforce/src/main/java/io/quarkus/camel/it/salesforce/CamelRoute.java b/integration-tests/camel-salesforce/src/main/java/io/quarkus/it/camel/salesforce/CamelRoute.java
similarity index 92%
rename from integration-tests/camel-salesforce/src/main/java/io/quarkus/camel/it/salesforce/CamelRoute.java
rename to integration-tests/camel-salesforce/src/main/java/io/quarkus/it/camel/salesforce/CamelRoute.java
index bd730013c6166..25d4b15ab3bcb 100644
--- a/integration-tests/camel-salesforce/src/main/java/io/quarkus/camel/it/salesforce/CamelRoute.java
+++ b/integration-tests/camel-salesforce/src/main/java/io/quarkus/it/camel/salesforce/CamelRoute.java
@@ -1,4 +1,4 @@
-package io.quarkus.camel.it.salesforce;
+package io.quarkus.it.camel.salesforce;
 
 import org.apache.camel.builder.RouteBuilder;
 
diff --git a/integration-tests/camel-salesforce/src/main/java/io/quarkus/camel/it/salesforce/CamelServlet.java b/integration-tests/camel-salesforce/src/main/java/io/quarkus/it/camel/salesforce/CamelServlet.java
similarity index 95%
rename from integration-tests/camel-salesforce/src/main/java/io/quarkus/camel/it/salesforce/CamelServlet.java
rename to integration-tests/camel-salesforce/src/main/java/io/quarkus/it/camel/salesforce/CamelServlet.java
index 062b342c5eea3..87749486e2efa 100644
--- a/integration-tests/camel-salesforce/src/main/java/io/quarkus/camel/it/salesforce/CamelServlet.java
+++ b/integration-tests/camel-salesforce/src/main/java/io/quarkus/it/camel/salesforce/CamelServlet.java
@@ -1,4 +1,4 @@
-package io.quarkus.camel.it.salesforce;
+package io.quarkus.it.camel.salesforce;
 
 import javax.enterprise.context.ApplicationScoped;
 import javax.inject.Inject;
diff --git a/integration-tests/class-transformer/deployment/pom.xml b/integration-tests/class-transformer/deployment/pom.xml
new file mode 100644
index 0000000000000..891639c7dab72
--- /dev/null
+++ b/integration-tests/class-transformer/deployment/pom.xml
@@ -0,0 +1,56 @@
+
+
+
+
+    
+        quarkus-integration-test-class-transformer-parent
+        io.quarkus
+        999-SNAPSHOT
+        ../
+    
+    4.0.0
+
+    quarkus-integration-test-class-transformer-deployment
+    Quarkus - Integration Tests - Class tranformer - Deployment
+
+    
+        
+            io.quarkus
+            quarkus-core-deployment
+        
+    
+
+    
+        
+            
+                maven-compiler-plugin
+                
+                    
+                        
+                            io.quarkus
+                            quarkus-extension-processor
+                            ${project.version}
+                        
+                    
+                
+            
+        
+    
+
+
diff --git a/integration-tests/class-transformer/src/main/java/io/quarkus/example/classtransformer/ClassTransformerProcessor.java b/integration-tests/class-transformer/deployment/src/main/java/io/quarkus/it/classtransformer/ClassTransformerProcessor.java
similarity index 98%
rename from integration-tests/class-transformer/src/main/java/io/quarkus/example/classtransformer/ClassTransformerProcessor.java
rename to integration-tests/class-transformer/deployment/src/main/java/io/quarkus/it/classtransformer/ClassTransformerProcessor.java
index 007cde5aa9959..332f965255e85 100644
--- a/integration-tests/class-transformer/src/main/java/io/quarkus/example/classtransformer/ClassTransformerProcessor.java
+++ b/integration-tests/class-transformer/deployment/src/main/java/io/quarkus/it/classtransformer/ClassTransformerProcessor.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package io.quarkus.example.classtransformer;
+package io.quarkus.it.classtransformer;
 
 import java.lang.reflect.Modifier;
 import java.util.Collection;
diff --git a/integration-tests/class-transformer/pom.xml b/integration-tests/class-transformer/pom.xml
index 6e6e047769c91..980998b3bff97 100644
--- a/integration-tests/class-transformer/pom.xml
+++ b/integration-tests/class-transformer/pom.xml
@@ -26,7 +26,7 @@
     
     4.0.0
 
-    quarkus-integration-test-class-transformer
+    quarkus-integration-test-class-transformer-parent
     Quarkus - Integration Tests - Class tranformer
     
 
@@ -37,31 +37,12 @@
         This should probably be deleted once we have a proper test framework.
 
     
-    
-        
-            io.quarkus
-            quarkus-core
-        
-    
 
-    
-        
-            
-                maven-dependency-plugin
-            
-            
-                maven-compiler-plugin
-                
-                    
-                        
-                            io.quarkus
-                            quarkus-extension-processor
-                            ${project.version}
-                        
-                    
-                
-            
-        
-    
+    pom
+
+    
+        deployment
+        runtime
+    
 
 
diff --git a/integration-tests/class-transformer/runtime/pom.xml b/integration-tests/class-transformer/runtime/pom.xml
new file mode 100644
index 0000000000000..1e8c58c27c780
--- /dev/null
+++ b/integration-tests/class-transformer/runtime/pom.xml
@@ -0,0 +1,40 @@
+
+
+
+
+  
+    quarkus-integration-test-class-transformer-parent
+    io.quarkus
+    999-SNAPSHOT
+    ../
+  
+  4.0.0
+
+  quarkus-integration-test-class-transformer
+  Quarkus - Integration Tests - Class tranformer - Runtime
+
+  
+    
+      
+        io.quarkus
+        quarkus-bootstrap-maven-plugin
+      
+    
+  
+
diff --git a/integration-tests/common-jpa-entities/src/main/java/io/quarkus/examples/common/Clown.java b/integration-tests/common-jpa-entities/src/main/java/io/quarkus/it/common/Clown.java
similarity index 96%
rename from integration-tests/common-jpa-entities/src/main/java/io/quarkus/examples/common/Clown.java
rename to integration-tests/common-jpa-entities/src/main/java/io/quarkus/it/common/Clown.java
index a382dcaf5ac94..dcc6dab0cd2dd 100644
--- a/integration-tests/common-jpa-entities/src/main/java/io/quarkus/examples/common/Clown.java
+++ b/integration-tests/common-jpa-entities/src/main/java/io/quarkus/it/common/Clown.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package io.quarkus.examples.common;
+package io.quarkus.it.common;
 
 import javax.persistence.Entity;
 import javax.persistence.GeneratedValue;
diff --git a/integration-tests/elytron-security/pom.xml b/integration-tests/elytron-security/pom.xml
index 39098f14d0790..d70c8b5259839 100644
--- a/integration-tests/elytron-security/pom.xml
+++ b/integration-tests/elytron-security/pom.xml
@@ -38,14 +38,12 @@
         
             io.quarkus
             quarkus-elytron-security
-            provided
         
 
         
         
             io.quarkus
             quarkus-resteasy-jsonb
-            provided
         
 
         
diff --git a/integration-tests/elytron-security/src/main/java/io/quarkus/it/elytron/KeyFactoryEndpoint.java b/integration-tests/elytron-security/src/main/java/io/quarkus/it/elytron/KeyFactoryEndpoint.java
new file mode 100644
index 0000000000000..c5c0a55e4ff16
--- /dev/null
+++ b/integration-tests/elytron-security/src/main/java/io/quarkus/it/elytron/KeyFactoryEndpoint.java
@@ -0,0 +1,64 @@
+package io.quarkus.it.elytron;
+
+import java.security.KeyFactory;
+import java.security.Provider;
+import java.security.PublicKey;
+import java.security.Security;
+import java.security.Signature;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.Base64;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.QueryParam;
+
+import org.jboss.logging.Logger;
+
+@Path("/jca")
+public class KeyFactoryEndpoint {
+    private static final Logger log = Logger.getLogger(KeyFactoryEndpoint.class);
+
+    @GET
+    @Path("listProviders")
+    public String listProviders() {
+        StringBuilder result = new StringBuilder();
+        final Provider[] providerList = Security.getProviders();
+        for (Provider provider : providerList) {
+            result.append(provider.getName());
+        }
+        log.infof("Found providers: %s", result);
+        return result.toString();
+    }
+
+    @GET
+    @Path("decodeRSAKey")
+    public String decodeRSAKey(@QueryParam("pemEncoded") String pemEncoded) throws Exception {
+        byte[] encodedBytes = Base64.getDecoder().decode(pemEncoded);
+
+        X509EncodedKeySpec spec = new X509EncodedKeySpec(encodedBytes);
+        KeyFactory kf = KeyFactory.getInstance("RSA");
+        log.infof("Loaded RSA.KeyFactory: %s", kf);
+        PublicKey pk = kf.generatePublic(spec);
+        return pk.getAlgorithm();
+    }
+
+    @GET
+    @Path("verifyRSASig")
+    public boolean verifyRSASig(@QueryParam("msg") String msg, @QueryParam("publicKey") String publicKey,
+            @QueryParam("sig") String sig) throws Exception {
+        byte[] encodedBytes = Base64.getDecoder().decode(publicKey);
+
+        X509EncodedKeySpec spec = new X509EncodedKeySpec(encodedBytes);
+        KeyFactory kf = KeyFactory.getInstance("RSA");
+        log.infof("Loaded RSA.KeyFactory: %s", kf);
+        PublicKey pk = kf.generatePublic(spec);
+
+        Signature sha256withRSA = Signature.getInstance("SHA256withRSA");
+        log.infof("Loaded SHA256withRSA: %s", sha256withRSA);
+        //log.infof("Loaded SHA256withRSA: %s, %s", sha256withRSA, sha256withRSA.getProvider());
+        sha256withRSA.initVerify(pk);
+        log.infof("Initialized SHA256withRSA");
+
+        return true;
+    }
+}
diff --git a/integration-tests/elytron-security/src/main/java/io/quarkus/example/X500PrincipalEndpoint.java b/integration-tests/elytron-security/src/main/java/io/quarkus/it/elytron/X500PrincipalEndpoint.java
similarity index 94%
rename from integration-tests/elytron-security/src/main/java/io/quarkus/example/X500PrincipalEndpoint.java
rename to integration-tests/elytron-security/src/main/java/io/quarkus/it/elytron/X500PrincipalEndpoint.java
index ffc9b6af3e712..e78ccbb4d7535 100644
--- a/integration-tests/elytron-security/src/main/java/io/quarkus/example/X500PrincipalEndpoint.java
+++ b/integration-tests/elytron-security/src/main/java/io/quarkus/it/elytron/X500PrincipalEndpoint.java
@@ -1,4 +1,4 @@
-package io.quarkus.example;
+package io.quarkus.it.elytron;
 
 import javax.inject.Inject;
 import javax.security.auth.x500.X500Principal;
diff --git a/integration-tests/elytron-security/src/main/java/io/quarkus/example/X500PrincipalUtilUser.java b/integration-tests/elytron-security/src/main/java/io/quarkus/it/elytron/X500PrincipalUtilUser.java
similarity index 80%
rename from integration-tests/elytron-security/src/main/java/io/quarkus/example/X500PrincipalUtilUser.java
rename to integration-tests/elytron-security/src/main/java/io/quarkus/it/elytron/X500PrincipalUtilUser.java
index 311d1b39cb574..3a645352644a5 100644
--- a/integration-tests/elytron-security/src/main/java/io/quarkus/example/X500PrincipalUtilUser.java
+++ b/integration-tests/elytron-security/src/main/java/io/quarkus/it/elytron/X500PrincipalUtilUser.java
@@ -1,4 +1,4 @@
-package io.quarkus.example;
+package io.quarkus.it.elytron;
 
 import java.security.Principal;
 
@@ -21,7 +21,9 @@ public String getName() {
                 return "O=Fake X500Principal";
             }
         };
+        System.out.printf("@Produces X500Principal called%n");
         X500Principal principal = X500PrincipalUtil.asX500Principal(dummy, true);
+        System.out.printf("@Produces X500Principal created: %s%n", principal);
         return principal;
     }
 }
diff --git a/integration-tests/elytron-security/src/main/resources/META-INF/microprofile-config.properties b/integration-tests/elytron-security/src/main/resources/META-INF/microprofile-config.properties
index cce07b09b0b37..1fe8aaeb04288 100644
--- a/integration-tests/elytron-security/src/main/resources/META-INF/microprofile-config.properties
+++ b/integration-tests/elytron-security/src/main/resources/META-INF/microprofile-config.properties
@@ -18,6 +18,8 @@
 # we probably won't be able to test SSL proper so it seems reasonable to set it globally
 quarkus.ssl.native=false
 
+quarkus.security.security-providers=SunRsaSign
+
 quarkus.security.embedded.enabled=true
 quarkus.security.embedded.users.scott=jb0ss
 quarkus.security.embedded.roles.scott=Admin,admin,Tester,user
diff --git a/integration-tests/elytron-security/src/test/java/io/quarkus/it/elytron/JCAITCase.java b/integration-tests/elytron-security/src/test/java/io/quarkus/it/elytron/JCAITCase.java
new file mode 100644
index 0000000000000..f767f67c56efd
--- /dev/null
+++ b/integration-tests/elytron-security/src/test/java/io/quarkus/it/elytron/JCAITCase.java
@@ -0,0 +1,7 @@
+package io.quarkus.it.elytron;
+
+import io.quarkus.test.junit.SubstrateTest;
+
+@SubstrateTest
+public class JCAITCase extends JCATestCase {
+}
diff --git a/integration-tests/elytron-security/src/test/java/io/quarkus/it/elytron/JCATestCase.java b/integration-tests/elytron-security/src/test/java/io/quarkus/it/elytron/JCATestCase.java
new file mode 100644
index 0000000000000..158203b0567db
--- /dev/null
+++ b/integration-tests/elytron-security/src/test/java/io/quarkus/it/elytron/JCATestCase.java
@@ -0,0 +1,68 @@
+package io.quarkus.it.elytron;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.is;
+
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PublicKey;
+import java.util.Base64;
+
+import org.junit.jupiter.api.Test;
+
+import io.quarkus.test.junit.QuarkusTest;
+import io.restassured.RestAssured;
+
+@QuarkusTest
+public class JCATestCase {
+
+    @Test
+    public void testListProviders() {
+        RestAssured.given()
+                .when()
+                .get("/jca/listProviders")
+                .then()
+                .statusCode(200)
+                .body(containsString("SunRsaSign"));
+    }
+
+    @Test
+    public void testDecodeRSAKey() throws Exception {
+        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
+        keyPairGenerator.initialize(4096);
+        KeyPair keyPair = keyPairGenerator.genKeyPair();
+        PublicKey publicKey = keyPair.getPublic();
+        byte[] encoded = publicKey.getEncoded();
+        byte[] pemEncoded = Base64.getEncoder().encode(encoded);
+        String pemString = new String(pemEncoded, "UTF-8");
+
+        RestAssured.given()
+                .queryParam("pemEncoded", pemString)
+                .when()
+                .get("/jca/decodeRSAKey")
+                .then()
+                .statusCode(200)
+                .body(is("RSA"));
+    }
+
+    @Test
+    public void testVerifyRSASig() throws Exception {
+        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
+        keyPairGenerator.initialize(4096);
+        KeyPair keyPair = keyPairGenerator.genKeyPair();
+        PublicKey publicKey = keyPair.getPublic();
+        byte[] encoded = publicKey.getEncoded();
+        byte[] pemEncoded = Base64.getEncoder().encode(encoded);
+        String pemString = new String(pemEncoded, "UTF-8");
+
+        RestAssured.given()
+                .queryParam("msg", "Hello verifyRSASig")
+                .queryParam("publicKey", pemString)
+                .queryParam("sig", "")
+                .when()
+                .get("/jca/verifyRSASig")
+                .then()
+                .statusCode(200)
+                .body(is("true"));
+    }
+}
diff --git a/integration-tests/elytron-security/src/test/java/io/quarkus/example/test/X500PrincipalUtilITCase.java b/integration-tests/elytron-security/src/test/java/io/quarkus/it/elytron/X500PrincipalUtilITCase.java
similarity index 80%
rename from integration-tests/elytron-security/src/test/java/io/quarkus/example/test/X500PrincipalUtilITCase.java
rename to integration-tests/elytron-security/src/test/java/io/quarkus/it/elytron/X500PrincipalUtilITCase.java
index e4172410911c0..7a4fd8e66a1ea 100644
--- a/integration-tests/elytron-security/src/test/java/io/quarkus/example/test/X500PrincipalUtilITCase.java
+++ b/integration-tests/elytron-security/src/test/java/io/quarkus/it/elytron/X500PrincipalUtilITCase.java
@@ -1,4 +1,4 @@
-package io.quarkus.example.test;
+package io.quarkus.it.elytron;
 
 import io.quarkus.test.junit.SubstrateTest;
 
diff --git a/integration-tests/elytron-security/src/test/java/io/quarkus/example/test/X500PrincipalUtilTestCase.java b/integration-tests/elytron-security/src/test/java/io/quarkus/it/elytron/X500PrincipalUtilTestCase.java
similarity index 93%
rename from integration-tests/elytron-security/src/test/java/io/quarkus/example/test/X500PrincipalUtilTestCase.java
rename to integration-tests/elytron-security/src/test/java/io/quarkus/it/elytron/X500PrincipalUtilTestCase.java
index caf4f8d6ffbbd..ea020fbb87624 100644
--- a/integration-tests/elytron-security/src/test/java/io/quarkus/example/test/X500PrincipalUtilTestCase.java
+++ b/integration-tests/elytron-security/src/test/java/io/quarkus/it/elytron/X500PrincipalUtilTestCase.java
@@ -1,4 +1,4 @@
-package io.quarkus.example.test;
+package io.quarkus.it.elytron;
 
 import static org.hamcrest.Matchers.is;
 
diff --git a/integration-tests/flyway/pom.xml b/integration-tests/flyway/pom.xml
new file mode 100644
index 0000000000000..dbf531645af88
--- /dev/null
+++ b/integration-tests/flyway/pom.xml
@@ -0,0 +1,146 @@
+
+
+
+
+    
+        quarkus-integration-tests-parent
+        io.quarkus
+        999-SNAPSHOT
+        ../
+    
+    4.0.0
+
+    quarkus-integration-test-flyway
+    Quarkus - Integration Tests - Flyway
+    Module that contains Flyway related tests
+    
+        
+            io.quarkus
+            quarkus-flyway
+        
+        
+            io.quarkus
+            quarkus-jdbc-h2
+        
+        
+            io.quarkus
+            quarkus-resteasy
+        
+        
+        
+            io.quarkus
+            quarkus-junit5
+            test
+        
+        
+            io.quarkus
+            quarkus-test-h2
+            test
+        
+        
+            io.rest-assured
+            rest-assured
+            test
+            
+                
+                    org.apache.commons
+                    commons-lang3
+                
+            
+        
+    
+
+    
+        
+            
+                src/main/resources
+                true
+            
+        
+        
+            
+                ${project.groupId}
+                quarkus-maven-plugin
+                
+                    
+                        
+                            build
+                        
+                    
+                
+            
+        
+    
+
+    
+        
+            native-image
+            
+                
+                    native
+                
+            
+            
+                
+                    
+                        org.apache.maven.plugins
+                        maven-failsafe-plugin
+                        
+                            
+                                
+                                    integration-test
+                                    verify
+                                
+                                
+                                    
+                                        
+                                            ${project.build.directory}/${project.build.finalName}-runner
+                                        
+                                    
+                                
+                            
+                        
+                    
+                    
+                        ${project.groupId}
+                        quarkus-maven-plugin
+                        
+                            
+                                native-image
+                                
+                                    native-image
+                                
+                                
+                                    false
+                                    true
+                                    true
+                                    ${graalvmHome}
+                                    false
+                                    false
+                                
+                            
+                        
+                    
+                
+            
+        
+
+    
+
+
diff --git a/integration-tests/flyway/src/main/java/io/quarkus/it/flyway/FlywayApp.java b/integration-tests/flyway/src/main/java/io/quarkus/it/flyway/FlywayApp.java
new file mode 100644
index 0000000000000..170f972de627d
--- /dev/null
+++ b/integration-tests/flyway/src/main/java/io/quarkus/it/flyway/FlywayApp.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2019 Red Hat, Inc.
+ *
+ * Licensed 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.it.flyway;
+
+import javax.ws.rs.ApplicationPath;
+import javax.ws.rs.core.Application;
+
+@ApplicationPath("/flyway")
+public class FlywayApp extends Application {
+}
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
new file mode 100644
index 0000000000000..d3a383ac1216f
--- /dev/null
+++ b/integration-tests/flyway/src/main/java/io/quarkus/it/flyway/FlywayFunctionalityResource.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2019 Red Hat, Inc.
+ *
+ * Licensed 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.it.flyway;
+
+import java.util.Objects;
+
+import javax.inject.Inject;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+import org.flywaydb.core.Flyway;
+import org.flywaydb.core.api.MigrationVersion;
+
+@Path("/")
+@Consumes(MediaType.APPLICATION_JSON)
+@Produces(MediaType.APPLICATION_JSON)
+public class FlywayFunctionalityResource {
+    @Inject
+    Flyway flyway;
+
+    @GET
+    @Path("migrate")
+    public String doMigrateAuto() {
+        flyway.migrate();
+        MigrationVersion version = Objects.requireNonNull(flyway.info().current().getVersion(),
+                "Version is null! Migration was not applied");
+        return version.toString();
+    }
+}
\ No newline at end of file
diff --git a/integration-tests/flyway/src/main/resources/application.properties b/integration-tests/flyway/src/main/resources/application.properties
new file mode 100644
index 0000000000000..eca2d905aa66a
--- /dev/null
+++ b/integration-tests/flyway/src/main/resources/application.properties
@@ -0,0 +1,29 @@
+#
+# Copyright 2019 Red Hat, Inc.
+#
+# Licensed 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.
+#
+quarkus.log.console.level=DEBUG
+quarkus.log.category."org.flywaydb.core".level=DEBUG
+quarkus.log.category."io.quarkus.flyway".level=DEBUG
+# Agroal config
+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
+quarkus.flyway.table=flyway_quarkus_history
+quarkus.flyway.locations=db/location1,db/location2
+quarkus.flyway.sql-migration-prefix=V
\ 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
new file mode 100644
index 0000000000000..a8559cd9d5cd9
--- /dev/null
+++ b/integration-tests/flyway/src/main/resources/db/location1/V1.0.1__Quarkus.sql
@@ -0,0 +1,7 @@
+CREATE TABLE TEST_SCHEMA.quarkus_table2
+(
+    id   INT,
+    name VARCHAR(20)
+);
+INSERT INTO TEST_SCHEMA.quarkus_table2(id, name)
+VALUES (1, 'QUARKED');
\ No newline at end of file
diff --git a/integration-tests/flyway/src/main/resources/db/location2/V1.0.0__Quarkus.sql b/integration-tests/flyway/src/main/resources/db/location2/V1.0.0__Quarkus.sql
new file mode 100644
index 0000000000000..616c27b45ed56
--- /dev/null
+++ b/integration-tests/flyway/src/main/resources/db/location2/V1.0.0__Quarkus.sql
@@ -0,0 +1,7 @@
+CREATE TABLE TEST_SCHEMA.quarkus
+(
+    id   INT,
+    name VARCHAR(20)
+);
+INSERT INTO TEST_SCHEMA.quarkus(id, name)
+VALUES (1, 'QUARKED');
\ No newline at end of file
diff --git a/integration-tests/flyway/src/test/java/io/quarkus/it/flyway/FlywayFunctionalityNativeIT.java b/integration-tests/flyway/src/test/java/io/quarkus/it/flyway/FlywayFunctionalityNativeIT.java
new file mode 100644
index 0000000000000..fac057ba780b9
--- /dev/null
+++ b/integration-tests/flyway/src/test/java/io/quarkus/it/flyway/FlywayFunctionalityNativeIT.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2019 Red Hat, Inc.
+ *
+ * Licensed 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.it.flyway;
+
+import io.quarkus.test.junit.SubstrateTest;
+
+@SubstrateTest
+public class FlywayFunctionalityNativeIT extends FlywayFunctionalityTest {
+}
diff --git a/integration-tests/hibernate-orm-panache/src/test/java/io/quarkus/example/test/PanacheFunctionalityTest.java b/integration-tests/flyway/src/test/java/io/quarkus/it/flyway/FlywayFunctionalityTest.java
similarity index 59%
rename from integration-tests/hibernate-orm-panache/src/test/java/io/quarkus/example/test/PanacheFunctionalityTest.java
rename to integration-tests/flyway/src/test/java/io/quarkus/it/flyway/FlywayFunctionalityTest.java
index b09fcb460600c..669cdc42b0688 100644
--- a/integration-tests/hibernate-orm-panache/src/test/java/io/quarkus/example/test/PanacheFunctionalityTest.java
+++ b/integration-tests/flyway/src/test/java/io/quarkus/it/flyway/FlywayFunctionalityTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2018 Red Hat, Inc.
+ * Copyright 2019 Red Hat, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,26 +14,23 @@
  * limitations under the License.
  */
 
-package io.quarkus.example.test;
+package io.quarkus.it.flyway;
 
+import static io.restassured.RestAssured.when;
 import static org.hamcrest.Matchers.is;
 
+import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Test;
 
 import io.quarkus.test.junit.QuarkusTest;
-import io.restassured.RestAssured;
 
-/**
- * Test various Panache operations running in Quarkus
- */
 @QuarkusTest
-public class PanacheFunctionalityTest {
+@DisplayName("Tests flyway extension")
+public class FlywayFunctionalityTest {
 
     @Test
-    public void testPanacheFunctionality() throws Exception {
-        RestAssured.when().get("/test/model-dao").then().body(is("OK"));
-        RestAssured.when().get("/test/model").then().body(is("OK"));
-        RestAssured.when().get("/test/accessors").then().body(is("OK"));
+    @DisplayName("Migrates a schema correctly using integrated instance")
+    public void testFlywayQuarkusFunctionality() {
+        when().get("/flyway/migrate").then().body(is("1.0.1"));
     }
-
 }
diff --git a/integration-tests/flyway/src/test/java/io/quarkus/it/flyway/FlywayTestResources.java b/integration-tests/flyway/src/test/java/io/quarkus/it/flyway/FlywayTestResources.java
new file mode 100644
index 0000000000000..3c0de66b37abf
--- /dev/null
+++ b/integration-tests/flyway/src/test/java/io/quarkus/it/flyway/FlywayTestResources.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2019 Red Hat, Inc.
+ *
+ * Licensed 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.it.flyway;
+
+import io.quarkus.test.common.QuarkusTestResource;
+import io.quarkus.test.h2.H2DatabaseTestResource;
+
+@QuarkusTestResource(H2DatabaseTestResource.class)
+public class FlywayTestResources {
+}
diff --git a/integration-tests/hibernate-orm-panache/pom.xml b/integration-tests/hibernate-orm-panache/pom.xml
index a47d8b24ba6b8..74e4d34e6e7cd 100644
--- a/integration-tests/hibernate-orm-panache/pom.xml
+++ b/integration-tests/hibernate-orm-panache/pom.xml
@@ -32,34 +32,41 @@
         
             io.quarkus
             quarkus-hibernate-orm-panache
-            provided
         
         
             io.quarkus
             quarkus-hibernate-orm
-            provided
         
         
             io.quarkus
             quarkus-resteasy
-            provided
+        
+        
+            io.quarkus
+            quarkus-resteasy-jsonb
+        
+        
+            io.quarkus
+            quarkus-jaxb
+        
+        
+            org.jboss.resteasy
+            resteasy-jaxb-provider
         
         
             io.quarkus
             quarkus-core
-            provided
         
         
             io.quarkus
             quarkus-jdbc-h2
-            provided
         
         
             org.junit.jupiter
             junit-jupiter-api
             compile
         
-        
+
         
         
             io.quarkus
diff --git a/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/example/panache/AccessorEntity.java b/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/AccessorEntity.java
similarity index 97%
rename from integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/example/panache/AccessorEntity.java
rename to integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/AccessorEntity.java
index 40b66b031e2db..4fc422c0df440 100644
--- a/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/example/panache/AccessorEntity.java
+++ b/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/AccessorEntity.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package io.quarkus.example.panache;
+package io.quarkus.it.panache;
 
 import javax.persistence.Entity;
 import javax.persistence.Transient;
diff --git a/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/example/panache/Address.java b/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/Address.java
similarity index 76%
rename from integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/example/panache/Address.java
rename to integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/Address.java
index 246b8e45d61a2..dd0354f465ea9 100644
--- a/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/example/panache/Address.java
+++ b/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/Address.java
@@ -14,18 +14,26 @@
  * limitations under the License.
  */
 
-package io.quarkus.example.panache;
+package io.quarkus.it.panache;
 
 import javax.persistence.Entity;
 
 import io.quarkus.hibernate.orm.panache.PanacheEntity;
 
 @Entity
-public class Address extends PanacheEntity {
+public class Address extends PanacheEntity implements Comparable
{ public String street; + public Address() { + } + public Address(String street) { this.street = street; } + + @Override + public int compareTo(Address address) { + return street.compareTo(address.street); + } } diff --git a/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/example/panache/AddressDao.java b/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/AddressDao.java similarity index 84% rename from integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/example/panache/AddressDao.java rename to integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/AddressDao.java index f78a96e6da98f..c39dabb1a9c4b 100644 --- a/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/example/panache/AddressDao.java +++ b/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/AddressDao.java @@ -1,4 +1,4 @@ -package io.quarkus.example.panache; +package io.quarkus.it.panache; import javax.enterprise.context.ApplicationScoped; diff --git a/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/example/panache/Dog.java b/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/Dog.java similarity index 93% rename from integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/example/panache/Dog.java rename to integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/Dog.java index d73752826b269..e16ce4d4b03aa 100644 --- a/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/example/panache/Dog.java +++ b/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/Dog.java @@ -1,4 +1,4 @@ -package io.quarkus.example.panache; +package io.quarkus.it.panache; import javax.persistence.Entity; import javax.persistence.GeneratedValue; diff --git a/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/example/panache/DogDao.java b/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/DogDao.java similarity index 86% rename from integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/example/panache/DogDao.java rename to integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/DogDao.java index 7d557f895e6d3..77ebcc97be3a1 100644 --- a/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/example/panache/DogDao.java +++ b/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/DogDao.java @@ -1,4 +1,4 @@ -package io.quarkus.example.panache; +package io.quarkus.it.panache; import javax.enterprise.context.ApplicationScoped; diff --git a/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/example/panache/GenericEntity.java b/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/GenericEntity.java similarity index 96% rename from integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/example/panache/GenericEntity.java rename to integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/GenericEntity.java index 794eb473d4e03..ff37677715054 100644 --- a/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/example/panache/GenericEntity.java +++ b/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/GenericEntity.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.panache; +package io.quarkus.it.panache; import javax.persistence.MappedSuperclass; diff --git a/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/example/panache/Person.java b/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/Person.java similarity index 63% rename from integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/example/panache/Person.java rename to integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/Person.java index 076aa7051ed28..ce5ed79ca1191 100644 --- a/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/example/panache/Person.java +++ b/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/Person.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.panache; +package io.quarkus.it.panache; import java.util.ArrayList; import java.util.List; @@ -24,9 +24,13 @@ import javax.persistence.FetchType; import javax.persistence.ManyToOne; import javax.persistence.OneToMany; +import javax.persistence.Transient; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlTransient; import io.quarkus.hibernate.orm.panache.PanacheEntity; +@XmlRootElement @Entity(name = "Person2") public class Person extends PanacheEntity { @@ -40,7 +44,23 @@ public class Person extends PanacheEntity { @OneToMany(mappedBy = "owner", cascade = CascadeType.ALL, fetch = FetchType.LAZY) public List dogs = new ArrayList<>(); + // note that this annotation is automatically added for mapped fields, which is not the case here + // so we do it manually to emulate a mapped field situation + @XmlTransient + @Transient + public int serialisationTrick; + public static List findOrdered() { return find("ORDER BY name").list(); } + + // For JAXB: both getter and setter are required + // Here we make sure the field is not used by Hibernate, but the accessor is used by jaxb and jsonb + public int getSerialisationTrick() { + return ++serialisationTrick; + } + + public void setSerialisationTrick(int serialisationTrick) { + this.serialisationTrick = serialisationTrick; + } } diff --git a/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/example/panache/PersonRepository.java b/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/PersonRepository.java similarity index 84% rename from integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/example/panache/PersonRepository.java rename to integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/PersonRepository.java index 6fbe6fd55e0fb..65eaca454e303 100644 --- a/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/example/panache/PersonRepository.java +++ b/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/PersonRepository.java @@ -1,4 +1,4 @@ -package io.quarkus.example.panache; +package io.quarkus.it.panache; import javax.enterprise.context.ApplicationScoped; diff --git a/integration-tests/jpa-postgresql/src/main/java/io/quarkus/example/jpa/Status.java b/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/Status.java similarity index 91% rename from integration-tests/jpa-postgresql/src/main/java/io/quarkus/example/jpa/Status.java rename to integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/Status.java index 6400ed4d0288f..b4b438ef65e92 100644 --- a/integration-tests/jpa-postgresql/src/main/java/io/quarkus/example/jpa/Status.java +++ b/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/Status.java @@ -14,8 +14,9 @@ * limitations under the License. */ -package io.quarkus.example.jpa; +package io.quarkus.it.panache; public enum Status { - LIVING, DECEASED + LIVING, + DECEASED } diff --git a/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/example/panache/TestEndpoint.java b/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/TestEndpoint.java similarity index 92% rename from integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/example/panache/TestEndpoint.java rename to integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/TestEndpoint.java index d890508fe45fe..1cb162c4f5514 100644 --- a/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/example/panache/TestEndpoint.java +++ b/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/TestEndpoint.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.panache; +package io.quarkus.it.panache; import java.lang.reflect.Method; import java.util.Arrays; @@ -28,7 +28,10 @@ import javax.transaction.Transactional; import javax.ws.rs.GET; import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import org.hibernate.engine.spi.SelfDirtinessTracker; import org.junit.jupiter.api.Assertions; import io.quarkus.hibernate.orm.panache.PanacheQuery; @@ -271,7 +274,6 @@ private Person makeSavedPerson() { Dog dog = new Dog("octave", "dalmatian"); dog.owner = person; dog.persist(); - person.dogs.add(dog); return person; } @@ -511,7 +513,9 @@ private void testSortingDao() { } enum PersistTest { - Iterable, Variadic, Stream; + Iterable, + Variadic, + Stream; } private void testPersistDao(PersistTest persistTest) { @@ -554,7 +558,6 @@ private Person makeSavedPersonDao() { Dog dog = new Dog("octave", "dalmatian"); dog.owner = person; dogDao.persist(dog); - person.dogs.add(dog); return person; } @@ -699,4 +702,75 @@ private void checkMethod(Class klass, String name, Class returnType, Class Method method = klass.getMethod(name, params); Assertions.assertEquals(returnType, method.getReturnType()); } + + @GET + @Path("model1") + @Transactional + public String testModel1() { + Assertions.assertEquals(0, Person.count()); + + Person person = makeSavedPerson(); + SelfDirtinessTracker trackingPerson = (SelfDirtinessTracker) person; + + String[] dirtyAttributes = trackingPerson.$$_hibernate_getDirtyAttributes(); + Assertions.assertEquals(0, dirtyAttributes.length); + + person.name = "1"; + + dirtyAttributes = trackingPerson.$$_hibernate_getDirtyAttributes(); + Assertions.assertEquals(1, dirtyAttributes.length); + + Assertions.assertEquals(1, Person.count()); + return "OK"; + } + + @GET + @Path("model2") + @Transactional + public String testModel2() { + Assertions.assertEquals(1, Person.count()); + + Person person = Person.findAll().firstResult(); + Assertions.assertEquals("1", person.name); + + person.name = "2"; + return "OK"; + } + + @GET + @Path("model3") + @Transactional + public String testModel3() { + Assertions.assertEquals(1, Person.count()); + + Person person = Person.findAll().firstResult(); + Assertions.assertEquals("2", person.name); + + Dog.deleteAll(); + Person.deleteAll(); + Address.deleteAll(); + Assertions.assertEquals(0, Person.count()); + + return "OK"; + } + + @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) + @GET + @Path("ignored-properties") + public Person ignoredProperties() throws NoSuchMethodException, SecurityException { + Person.class.getMethod("$$_hibernate_read_id"); + Person.class.getMethod("$$_hibernate_read_name"); + try { + Person.class.getMethod("$$_hibernate_read_persistent"); + Assertions.fail(); + } catch (NoSuchMethodException e) { + } + + // no need to persist it, we can fake it + Person person = new Person(); + person.id = 666l; + person.name = "Eddie"; + person.status = Status.DECEASED; + return person; + } } diff --git a/integration-tests/hibernate-orm-panache/src/main/resources/application.properties b/integration-tests/hibernate-orm-panache/src/main/resources/application.properties index 205846a92695b..05f341a5538c1 100644 --- a/integration-tests/hibernate-orm-panache/src/main/resources/application.properties +++ b/integration-tests/hibernate-orm-panache/src/main/resources/application.properties @@ -16,8 +16,8 @@ quarkus.datasource.url=jdbc:h2:tcp://localhost/mem:test quarkus.datasource.driver=org.h2.Driver -quarkus.datasource.maxSize=8 -quarkus.datasource.minSize=2 +quarkus.datasource.max-size=8 +quarkus.datasource.min-size=2 quarkus.hibernate-orm.dialect=org.hibernate.dialect.H2Dialect quarkus.hibernate-orm.database.generation=drop-and-create diff --git a/integration-tests/hibernate-orm-panache/src/test/java/io/quarkus/example/test/PanacheFunctionalityInGraalITCase.java b/integration-tests/hibernate-orm-panache/src/test/java/io/quarkus/it/panache/PanacheFunctionalityInGraalITCase.java similarity index 96% rename from integration-tests/hibernate-orm-panache/src/test/java/io/quarkus/example/test/PanacheFunctionalityInGraalITCase.java rename to integration-tests/hibernate-orm-panache/src/test/java/io/quarkus/it/panache/PanacheFunctionalityInGraalITCase.java index 0b29e618ce7e0..ca7d603869142 100644 --- a/integration-tests/hibernate-orm-panache/src/test/java/io/quarkus/example/test/PanacheFunctionalityInGraalITCase.java +++ b/integration-tests/hibernate-orm-panache/src/test/java/io/quarkus/it/panache/PanacheFunctionalityInGraalITCase.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.test; +package io.quarkus.it.panache; import io.quarkus.test.junit.SubstrateTest; 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 new file mode 100644 index 0000000000000..4383ebfcb007a --- /dev/null +++ b/integration-tests/hibernate-orm-panache/src/test/java/io/quarkus/it/panache/PanacheFunctionalityTest.java @@ -0,0 +1,64 @@ +/* + * Copyright 2018 Red Hat, Inc. + * + * Licensed 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.it.panache; + +import static org.hamcrest.Matchers.is; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import io.quarkus.it.panache.Person; +import io.quarkus.test.junit.DisabledOnSubstrate; +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.RestAssured; +import io.restassured.http.ContentType; + +/** + * Test various Panache operations running in Quarkus + */ +@QuarkusTest +public class PanacheFunctionalityTest { + + @Test + public void testPanacheFunctionality() throws Exception { + RestAssured.when().get("/test/model-dao").then().body(is("OK")); + RestAssured.when().get("/test/model").then().body(is("OK")); + RestAssured.when().get("/test/accessors").then().body(is("OK")); + + RestAssured.when().get("/test/model1").then().body(is("OK")); + RestAssured.when().get("/test/model2").then().body(is("OK")); + RestAssured.when().get("/test/model3").then().body(is("OK")); + } + + @Test + public void testPanacheSerialisation() { + RestAssured.given().accept(ContentType.JSON) + .when().get("/test/ignored-properties") + .then() + .body(is("{\"id\":666,\"dogs\":[],\"name\":\"Eddie\",\"serialisationTrick\":1,\"status\":\"DECEASED\"}")); + RestAssured.given().accept(ContentType.XML) + .when().get("/test/ignored-properties") + .then().body(is( + "666Eddie1DECEASED")); + } + + @DisabledOnSubstrate + @Test + public void testPanacheInTest() { + Assertions.assertEquals(0, Person.count()); + } +} diff --git a/integration-tests/jpa-h2/src/test/java/io/quarkus/example/test/TestResources.java b/integration-tests/hibernate-orm-panache/src/test/java/io/quarkus/it/panache/TestResources.java similarity index 95% rename from integration-tests/jpa-h2/src/test/java/io/quarkus/example/test/TestResources.java rename to integration-tests/hibernate-orm-panache/src/test/java/io/quarkus/it/panache/TestResources.java index 06653064904e9..42b1b9c514139 100644 --- a/integration-tests/jpa-h2/src/test/java/io/quarkus/example/test/TestResources.java +++ b/integration-tests/hibernate-orm-panache/src/test/java/io/quarkus/it/panache/TestResources.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.test; +package io.quarkus.it.panache; import io.quarkus.test.common.QuarkusTestResource; import io.quarkus.test.h2.H2DatabaseTestResource; diff --git a/integration-tests/hibernate-search-elasticsearch/pom.xml b/integration-tests/hibernate-search-elasticsearch/pom.xml new file mode 100644 index 0000000000000..90a2a144c7d98 --- /dev/null +++ b/integration-tests/hibernate-search-elasticsearch/pom.xml @@ -0,0 +1,218 @@ + + + + + + quarkus-integration-tests-parent + io.quarkus + 999-SNAPSHOT + ../ + + 4.0.0 + + quarkus-integration-test-hibernate-search-elasticsearch + Quarkus - Integration Tests - Hibernate Search + Elasticsearch + + + io.quarkus + quarkus-resteasy + + + io.quarkus + quarkus-hibernate-search-elasticsearch + + + io.quarkus + quarkus-jdbc-h2 + + + org.junit.jupiter + junit-jupiter-api + compile + + + + + io.quarkus + quarkus-junit5 + test + + + io.quarkus + quarkus-test-h2 + test + + + io.rest-assured + rest-assured + test + + + org.apache.commons + commons-lang3 + + + + + + + + + src/main/resources + true + + + + + maven-surefire-plugin + + true + + + + maven-failsafe-plugin + + true + + + + ${project.groupId} + quarkus-maven-plugin + + + + build + + + + + + + + + + test-elasticsearch + + + test-elasticsearch + + + + + + maven-surefire-plugin + + false + + + + maven-failsafe-plugin + + false + + + + com.github.alexcojocaru + elasticsearch-maven-plugin + + 90 + ${project.build.directory}/test-classes/elasticsearch-maven-plugin/configuration/ + ${project.build.directory}/test-classes/elasticsearch-maven-plugin/init/init.script + true + + + + + start-elasticsearch + process-test-classes + + runforked + + + + stop-elasticsearch + post-integration-test + + stop + + + + + + + + + + native-image + + + native + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + verify + + + + ${project.build.directory}/${project.build.finalName}-runner + + + + + + + ${project.groupId} + quarkus-maven-plugin + + + native-image + + native-image + + + false + true + true + false + false + + ${graalvmHome} + false + false + + + + + + + + + + diff --git a/integration-tests/hibernate-search-elasticsearch/src/main/java/io/quarkus/test/hibernate/search/elasticsearch/client/ElasticsearchClientTestResource.java b/integration-tests/hibernate-search-elasticsearch/src/main/java/io/quarkus/test/hibernate/search/elasticsearch/client/ElasticsearchClientTestResource.java new file mode 100644 index 0000000000000..4ad7814f52e03 --- /dev/null +++ b/integration-tests/hibernate-search-elasticsearch/src/main/java/io/quarkus/test/hibernate/search/elasticsearch/client/ElasticsearchClientTestResource.java @@ -0,0 +1,139 @@ +package io.quarkus.test.hibernate.search.elasticsearch.client; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.security.NoSuchAlgorithmException; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import org.apache.http.HttpHost; +import org.elasticsearch.client.Request; +import org.elasticsearch.client.Response; +import org.elasticsearch.client.RestClient; +import org.elasticsearch.client.sniff.Sniffer; + +@Path("/test/elasticsearch-client") +public class ElasticsearchClientTestResource { + + @GET + @Path("/connection") + @Produces(MediaType.TEXT_PLAIN) + public String testConnection() throws IOException, NoSuchAlgorithmException { + try (RestClient restClient = createRestClient()) { + Response response = restClient.performRequest(new Request("GET", "/")); + + checkStatus(response, 200); + + return "OK"; + } + } + + @GET + @Path("/full-cycle") + @Produces(MediaType.TEXT_PLAIN) + public String testFullCycle() throws IOException { + try (RestClient restClient = createRestClient()) { + try { + restClient.performRequest(new Request("DELETE", "/books")); + } catch (Exception e) { + // ignore + } + + // create schema + Request createIndex = new Request("PUT", "/books"); + createIndex.setJsonEntity( + "{ " + + " \"settings\" : { " + + " \"number_of_shards\" : 1 " + + " }, " + + " \"mappings\" : { " + + " \"properties\" : { " + + " \"title\" : { \"type\" : \"text\" }, " + + " \"author\" : { \"type\" : \"text\" } " + + " } " + + " } " + + "}"); + + Response response = restClient.performRequest(createIndex); + checkStatus(response, 200); + + // index documents + Request indexDocument = new Request("POST", "/books/_doc/1?refresh=true"); + indexDocument.setJsonEntity( + "{" + + " \"title\": \"4 3 2 1\"," + + " \"author\": \"Auster\"" + + "}"); + response = restClient.performRequest(indexDocument); + checkStatus(response, 201); + + indexDocument = new Request("POST", "/books/_doc/2?refresh=true"); + indexDocument.setJsonEntity( + "{" + + " \"title\": \"Avenue of mysteries\"," + + " \"author\": \"Irving\"" + + "}"); + response = restClient.performRequest(indexDocument); + checkStatus(response, 201); + + // search + Request searchRequest = new Request("POST", "/books/_search"); + searchRequest.setJsonEntity("{" + + " \"query\": { " + + " \"simple_query_string\": {" + + " \"query\": \"Irving\"" + + " } " + + " } " + + "}"); + response = restClient.performRequest(searchRequest); + checkStatus(response, 200); + checkContent(response, "mysteries"); + + return "OK"; + } + } + + @GET + @Path("/sniffer") + @Produces(MediaType.TEXT_PLAIN) + public String testSniffer() throws IOException, InterruptedException { + try (RestClient restClient = createRestClient()) { + Sniffer sniffer = Sniffer.builder(restClient).setSniffIntervalMillis(5).build(); + + // Wait for a few iterations of the sniffer + Thread.sleep(20); + + sniffer.close(); + + return "OK"; + } + } + + private static RestClient createRestClient() { + return RestClient.builder(new HttpHost("localhost", 9200)).build(); + } + + private static void checkStatus(Response response, int status) { + if (response.getStatusLine().getStatusCode() != status) { + throw new IllegalStateException("Status should have been " + status + " but is: " + + response.getStatusLine().getStatusCode() + " - " + response.getStatusLine().getReasonPhrase()); + } + } + + private static void checkContent(Response response, String token) throws IOException { + String content = getContent(response); + if (!content.contains(token)) { + throw new IllegalStateException("Content should contain " + token + " but is: " + content); + } + } + + private static String getContent(Response response) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + response.getEntity().writeTo(baos); + return new String(baos.toByteArray(), StandardCharsets.UTF_8); + } +} diff --git a/integration-tests/hibernate-search-elasticsearch/src/main/java/io/quarkus/test/hibernate/search/elasticsearch/search/Address.java b/integration-tests/hibernate-search-elasticsearch/src/main/java/io/quarkus/test/hibernate/search/elasticsearch/search/Address.java new file mode 100644 index 0000000000000..28a35f5003caa --- /dev/null +++ b/integration-tests/hibernate-search-elasticsearch/src/main/java/io/quarkus/test/hibernate/search/elasticsearch/search/Address.java @@ -0,0 +1,54 @@ +package io.quarkus.test.hibernate.search.elasticsearch.search; + +import java.util.List; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.OneToMany; + +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField; +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.Indexed; + +@Entity +@Indexed +public class Address { + + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "addressSeq") + private Long id; + + @FullTextField(analyzer = "standard") + private String city; + + @OneToMany(mappedBy = "address") + private List person; + + public Address() { + } + + public Address(String city) { + this.city = city; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getCity() { + return city; + } + + public void setCity(String name) { + this.city = name; + } + + public void describeFully(StringBuilder sb) { + sb.append("Address with id=").append(id).append(", city='").append(city).append("'"); + } +} diff --git a/integration-tests/hibernate-search-elasticsearch/src/main/java/io/quarkus/test/hibernate/search/elasticsearch/search/DefaultITAnalysisConfigurer.java b/integration-tests/hibernate-search-elasticsearch/src/main/java/io/quarkus/test/hibernate/search/elasticsearch/search/DefaultITAnalysisConfigurer.java new file mode 100644 index 0000000000000..05d1a51621f85 --- /dev/null +++ b/integration-tests/hibernate-search-elasticsearch/src/main/java/io/quarkus/test/hibernate/search/elasticsearch/search/DefaultITAnalysisConfigurer.java @@ -0,0 +1,13 @@ +package io.quarkus.test.hibernate.search.elasticsearch.search; + +import org.hibernate.search.backend.elasticsearch.analysis.ElasticsearchAnalysisConfigurer; +import org.hibernate.search.backend.elasticsearch.analysis.model.dsl.ElasticsearchAnalysisDefinitionContainerContext; + +public class DefaultITAnalysisConfigurer implements ElasticsearchAnalysisConfigurer { + + @Override + public void configure(ElasticsearchAnalysisDefinitionContainerContext context) { + context.analyzer("standard").type("standard"); + context.normalizer("lowercase").custom().withTokenFilters("lowercase"); + } +} diff --git a/integration-tests/hibernate-search-elasticsearch/src/main/java/io/quarkus/test/hibernate/search/elasticsearch/search/HibernateSearchTestResource.java b/integration-tests/hibernate-search-elasticsearch/src/main/java/io/quarkus/test/hibernate/search/elasticsearch/search/HibernateSearchTestResource.java new file mode 100644 index 0000000000000..f958b8fd5973a --- /dev/null +++ b/integration-tests/hibernate-search-elasticsearch/src/main/java/io/quarkus/test/hibernate/search/elasticsearch/search/HibernateSearchTestResource.java @@ -0,0 +1,83 @@ +package io.quarkus.test.hibernate.search.elasticsearch.search; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; + +import javax.inject.Inject; +import javax.persistence.EntityManager; +import javax.transaction.Transactional; +import javax.ws.rs.GET; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import org.hibernate.search.mapper.orm.Search; +import org.hibernate.search.mapper.orm.session.SearchSession; + +@Path("/test/hibernate-search") +public class HibernateSearchTestResource { + + @Inject + EntityManager entityManager; + + @PUT + @Path("/init-data") + @Transactional + public void initData() { + createPerson("John Irving", "Burlington"); + createPerson("David Lodge", "London"); + createPerson("Paul Auster", "New York"); + createPerson("John Grisham", "Oxford"); + } + + @GET + @Path("/search") + @Produces(MediaType.TEXT_PLAIN) + public String testSearch() { + SearchSession searchSession = Search.getSearchSession(entityManager); + + List person = searchSession.search(Person.class) + .asEntity() + .predicate(f -> f.match().onField("name").matching("john")) + .sort(f -> f.byField("name_sort")) + .toQuery() + .fetchHits(); + + assertEquals(2, person.size()); + assertEquals("John Grisham", person.get(0).getName()); + assertEquals("John Irving", person.get(1).getName()); + + person = searchSession.search(Person.class) + .asEntity() + .predicate(f -> f.nested().onObjectField("address").nest(f.match().onField("address.city").matching("london"))) + .sort(f -> f.byField("name_sort")) + .toQuery() + .fetchHits(); + + assertEquals(1, person.size()); + assertEquals("David Lodge", person.get(0).getName()); + + return "OK"; + } + + @PUT + @Path("/mass-indexer") + @Produces(MediaType.TEXT_PLAIN) + public String testMassIndexer() throws InterruptedException { + SearchSession searchSession = Search.getSearchSession(entityManager); + + searchSession.createIndexer().startAndWait(); + + return "OK"; + } + + private void createPerson(String name, String city) { + Address address = new Address(city); + entityManager.persist(address); + + Person person = new Person(name, address); + entityManager.persist(person); + } +} diff --git a/integration-tests/hibernate-search-elasticsearch/src/main/java/io/quarkus/test/hibernate/search/elasticsearch/search/Person.java b/integration-tests/hibernate-search-elasticsearch/src/main/java/io/quarkus/test/hibernate/search/elasticsearch/search/Person.java new file mode 100644 index 0000000000000..392f551be5e9f --- /dev/null +++ b/integration-tests/hibernate-search-elasticsearch/src/main/java/io/quarkus/test/hibernate/search/elasticsearch/search/Person.java @@ -0,0 +1,71 @@ +package io.quarkus.test.hibernate.search.elasticsearch.search; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +import org.hibernate.search.engine.backend.document.model.dsl.ObjectFieldStorage; +import org.hibernate.search.engine.backend.types.Sortable; +import org.hibernate.search.mapper.pojo.mapping.definition.annotation.FullTextField; +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.KeywordField; + +@Entity +@Indexed +public class Person { + + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "personSeq") + private Long id; + + @FullTextField(analyzer = "standard") + @KeywordField(name = "name_sort", normalizer = "lowercase", sortable = Sortable.YES) + private String name; + + @ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY) + @IndexedEmbedded(storage = ObjectFieldStorage.NESTED) + private Address address; + + public Person() { + } + + public Person(String name, Address address) { + this.name = name; + this.address = address; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Address getAddress() { + return address; + } + + public void setAddress(Address address) { + this.address = address; + } + + public void describeFully(StringBuilder sb) { + sb.append("Person with id=").append(id).append(", name='").append(name).append("', address { "); + getAddress().describeFully(sb); + sb.append(" }"); + } +} diff --git a/integration-tests/hibernate-search-elasticsearch/src/main/resources/META-INF/beans.xml b/integration-tests/hibernate-search-elasticsearch/src/main/resources/META-INF/beans.xml new file mode 100644 index 0000000000000..1e58b4b51bb3c --- /dev/null +++ b/integration-tests/hibernate-search-elasticsearch/src/main/resources/META-INF/beans.xml @@ -0,0 +1,16 @@ + + diff --git a/integration-tests/hibernate-search-elasticsearch/src/main/resources/application.properties b/integration-tests/hibernate-search-elasticsearch/src/main/resources/application.properties new file mode 100644 index 0000000000000..541140a65a224 --- /dev/null +++ b/integration-tests/hibernate-search-elasticsearch/src/main/resources/application.properties @@ -0,0 +1,30 @@ +# +# Copyright 2019 Red Hat, Inc. +# +# Licensed 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. +# + +quarkus.ssl.native = false + +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 + +quarkus.hibernate-orm.dialect=org.hibernate.dialect.H2Dialect +quarkus.hibernate-orm.database.generation=drop-and-create + +quarkus.hibernate-search.elasticsearch.version=7 +quarkus.hibernate-search.elasticsearch.analysis-configurer=io.quarkus.test.hibernate.search.elasticsearch.search.DefaultITAnalysisConfigurer +quarkus.hibernate-search.elasticsearch.index-defaults.lifecycle.strategy=drop-and-create-and-drop +quarkus.hibernate-search.elasticsearch.index-defaults.refresh-after-write=true diff --git a/integration-tests/hibernate-search-elasticsearch/src/test/java/io/quarkus/test/hibernate/search/elasticsearch/ElasticsearchClientInGraalIT.java b/integration-tests/hibernate-search-elasticsearch/src/test/java/io/quarkus/test/hibernate/search/elasticsearch/ElasticsearchClientInGraalIT.java new file mode 100644 index 0000000000000..f4c36fdb8c952 --- /dev/null +++ b/integration-tests/hibernate-search-elasticsearch/src/test/java/io/quarkus/test/hibernate/search/elasticsearch/ElasticsearchClientInGraalIT.java @@ -0,0 +1,24 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Licensed 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.test.hibernate.search.elasticsearch; + +import io.quarkus.test.junit.SubstrateTest; + +@SubstrateTest +public class ElasticsearchClientInGraalIT extends ElasticsearchClientTest { + +} diff --git a/integration-tests/hibernate-search-elasticsearch/src/test/java/io/quarkus/test/hibernate/search/elasticsearch/ElasticsearchClientTest.java b/integration-tests/hibernate-search-elasticsearch/src/test/java/io/quarkus/test/hibernate/search/elasticsearch/ElasticsearchClientTest.java new file mode 100644 index 0000000000000..e340963518d0e --- /dev/null +++ b/integration-tests/hibernate-search-elasticsearch/src/test/java/io/quarkus/test/hibernate/search/elasticsearch/ElasticsearchClientTest.java @@ -0,0 +1,49 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Licensed 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.test.hibernate.search.elasticsearch; + +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 ElasticsearchClientTest { + + @Test + public void testConnection() throws Exception { + RestAssured.when().get("/test/elasticsearch-client/connection").then() + .statusCode(200) + .body(is("OK")); + } + + @Test + public void testFullCycle() throws Exception { + RestAssured.when().get("/test/elasticsearch-client/full-cycle").then() + .statusCode(200) + .body(is("OK")); + } + + @Test + public void testSniffer() throws Exception { + RestAssured.when().get("/test/elasticsearch-client/sniffer").then() + .statusCode(200) + .body(is("OK")); + } +} diff --git a/integration-tests/hibernate-search-elasticsearch/src/test/java/io/quarkus/test/hibernate/search/elasticsearch/HibernateSearchInGraalIT.java b/integration-tests/hibernate-search-elasticsearch/src/test/java/io/quarkus/test/hibernate/search/elasticsearch/HibernateSearchInGraalIT.java new file mode 100644 index 0000000000000..80e52441940f3 --- /dev/null +++ b/integration-tests/hibernate-search-elasticsearch/src/test/java/io/quarkus/test/hibernate/search/elasticsearch/HibernateSearchInGraalIT.java @@ -0,0 +1,24 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Licensed 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.test.hibernate.search.elasticsearch; + +import io.quarkus.test.junit.SubstrateTest; + +@SubstrateTest +public class HibernateSearchInGraalIT extends HibernateSearchTest { + +} diff --git a/integration-tests/hibernate-search-elasticsearch/src/test/java/io/quarkus/test/hibernate/search/elasticsearch/HibernateSearchTest.java b/integration-tests/hibernate-search-elasticsearch/src/test/java/io/quarkus/test/hibernate/search/elasticsearch/HibernateSearchTest.java new file mode 100644 index 0000000000000..90762752e8022 --- /dev/null +++ b/integration-tests/hibernate-search-elasticsearch/src/test/java/io/quarkus/test/hibernate/search/elasticsearch/HibernateSearchTest.java @@ -0,0 +1,42 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Licensed 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.test.hibernate.search.elasticsearch; + +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 HibernateSearchTest { + + @Test + public void testSearch() throws Exception { + RestAssured.when().put("/test/hibernate-search/init-data").then() + .statusCode(204); + + RestAssured.when().get("/test/hibernate-search/search").then() + .statusCode(200) + .body(is("OK")); + + RestAssured.when().put("/test/hibernate-search/mass-indexer").then() + .statusCode(200) + .body(is("OK")); + } +} diff --git a/integration-tests/jpa/src/test/java/io/quarkus/jpa/tests/configurationless/TestResources.java b/integration-tests/hibernate-search-elasticsearch/src/test/java/io/quarkus/test/hibernate/search/elasticsearch/TestResources.java similarity index 93% rename from integration-tests/jpa/src/test/java/io/quarkus/jpa/tests/configurationless/TestResources.java rename to integration-tests/hibernate-search-elasticsearch/src/test/java/io/quarkus/test/hibernate/search/elasticsearch/TestResources.java index e0749f5d0e825..60028c9ad992c 100644 --- a/integration-tests/jpa/src/test/java/io/quarkus/jpa/tests/configurationless/TestResources.java +++ b/integration-tests/hibernate-search-elasticsearch/src/test/java/io/quarkus/test/hibernate/search/elasticsearch/TestResources.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.jpa.tests.configurationless; +package io.quarkus.test.hibernate.search.elasticsearch; import io.quarkus.test.common.QuarkusTestResource; import io.quarkus.test.h2.H2DatabaseTestResource; diff --git a/integration-tests/hibernate-search-elasticsearch/src/test/resources/elasticsearch-maven-plugin/configuration/elasticsearch.yml b/integration-tests/hibernate-search-elasticsearch/src/test/resources/elasticsearch-maven-plugin/configuration/elasticsearch.yml new file mode 100644 index 0000000000000..e15abadf05b9d --- /dev/null +++ b/integration-tests/hibernate-search-elasticsearch/src/test/resources/elasticsearch-maven-plugin/configuration/elasticsearch.yml @@ -0,0 +1,92 @@ +# ======================== Elasticsearch Configuration ========================= +# +# NOTE: Elasticsearch comes with reasonable defaults for most settings. +# Before you set out to tweak and tune the configuration, make sure you +# understand what are you trying to accomplish and the consequences. +# +# The primary way of configuring a node is via this file. This template lists +# the most important settings you may want to configure for a production cluster. +# +# Please consult the documentation for further information on configuration options: +# https://www.elastic.co/guide/en/elasticsearch/reference/index.html +# +# ---------------------------------- Cluster ----------------------------------- +# +# Use a descriptive name for your cluster: +# +#cluster.name: my-application +# +# ------------------------------------ Node ------------------------------------ +# +# Use a descriptive name for the node: +# +#node.name: node-1 +# +# Add custom attributes to the node: +# +#node.attr.rack: r1 +# +# ----------------------------------- Paths ------------------------------------ +# +# Path to directory where to store the data (separate multiple locations by comma): +# +#path.data: /path/to/data +# +# Path to log files: +# +#path.logs: /path/to/logs +# +# ----------------------------------- Memory ----------------------------------- +# +# Lock the memory on startup: +# +bootstrap.memory_lock: true +# +# Make sure that the heap size is set to about half the memory available +# on the system and that the owner of the process is allowed to use this +# limit. +# +# Elasticsearch performs poorly when the system is swapping the memory. +# +# ---------------------------------- Network ----------------------------------- +# +# Set the bind address to a specific IP (IPv4 or IPv6): +# +network.host: _local_ +# +# Set a custom port for HTTP: +# +http.port: 9200 +# +# For more information, consult the network module documentation. +# +# --------------------------------- Discovery ---------------------------------- +# +# Pass an initial list of hosts to perform discovery when this node is started: +# The default list of hosts is ["127.0.0.1", "[::1]"] +# +#discovery.seed_hosts: ["host1", "host2"] +# +# Bootstrap the cluster using an initial set of master-eligible nodes: +# +#cluster.initial_master_nodes: ["node-1", "node-2"] +# +# For more information, consult the discovery and cluster formation module documentation. +# +# ---------------------------------- Gateway ----------------------------------- +# +# Block initial recovery after a full cluster restart until N nodes are started: +# +#gateway.recover_after_nodes: 3 +# +# For more information, consult the gateway module documentation. +# +# ---------------------------------- Various ----------------------------------- +# +# Require explicit names when deleting indices: +# +#action.destructive_requires_name: true +# +# Disable starting multiple nodes on a single system: +# +node.max_local_storage_nodes: 1 diff --git a/integration-tests/hibernate-search-elasticsearch/src/test/resources/elasticsearch-maven-plugin/configuration/jvm.options b/integration-tests/hibernate-search-elasticsearch/src/test/resources/elasticsearch-maven-plugin/configuration/jvm.options new file mode 100644 index 0000000000000..91f3f0ee7dd56 --- /dev/null +++ b/integration-tests/hibernate-search-elasticsearch/src/test/resources/elasticsearch-maven-plugin/configuration/jvm.options @@ -0,0 +1,125 @@ +## JVM configuration + +################################################################ +## IMPORTANT: JVM heap size +################################################################ +## +## You should always set the min and max JVM heap +## size to the same value. For example, to set +## the heap to 4 GB, set: +## +## -Xms4g +## -Xmx4g +## +## See https://www.elastic.co/guide/en/elasticsearch/reference/current/heap-size.html +## for more information +## +################################################################ + +# Xms represents the initial size of total heap space +# Xmx represents the maximum size of total heap space + +# For Hibernate Search, we don't need as much as the default 2g +# Let's keep it low, so that we'll be able to run tests +# on memory-constrained CI slaves +-Xms256m +-Xmx256m + +################################################################ +## Expert settings +################################################################ +## +## All settings below this section are considered +## expert settings. Don't tamper with them unless +## you understand what you are doing +## +################################################################ + +## GC configuration +-XX:+UseConcMarkSweepGC +-XX:CMSInitiatingOccupancyFraction=75 +-XX:+UseCMSInitiatingOccupancyOnly + +## G1GC Configuration +# NOTE: G1GC is only supported on JDK version 10 or later. +# To use G1GC uncomment the lines below. +# 10-:-XX:-UseConcMarkSweepGC +# 10-:-XX:-UseCMSInitiatingOccupancyOnly +# 10-:-XX:+UseG1GC +# 10-:-XX:InitiatingHeapOccupancyPercent=75 + +## DNS cache policy +# cache ttl in seconds for positive DNS lookups noting that this overrides the +# JDK security property networkaddress.cache.ttl; set to -1 to cache forever +-Des.networkaddress.cache.ttl=60 +# cache ttl in seconds for negative DNS lookups noting that this overrides the +# JDK security property networkaddress.cache.negative ttl; set to -1 to cache +# forever +-Des.networkaddress.cache.negative.ttl=10 + +## optimizations + +# pre-touch memory pages used by the JVM during initialization +-XX:+AlwaysPreTouch + +## basic + +# explicitly set the stack size +-Xss1m + +# set to headless, just in case +-Djava.awt.headless=true + +# ensure UTF-8 encoding by default (e.g. filenames) +-Dfile.encoding=UTF-8 + +# use our provided JNA always versus the system one +-Djna.nosys=true + +# turn off a JDK optimization that throws away stack traces for common +# exceptions because stack traces are important for debugging +-XX:-OmitStackTraceInFastThrow + +# flags to configure Netty +-Dio.netty.noUnsafe=true +-Dio.netty.noKeySetOptimization=true +-Dio.netty.recycler.maxCapacityPerThread=0 + +# log4j 2 +-Dlog4j.shutdownHookEnabled=false +-Dlog4j2.disable.jmx=true + +-Djava.io.tmpdir=${ES_TMPDIR} + +## heap dumps + +# generate a heap dump when an allocation from the Java heap fails +# heap dumps are created in the working directory of the JVM +-XX:+HeapDumpOnOutOfMemoryError + +# specify an alternative path for heap dumps; ensure the directory exists and +# has sufficient space +-XX:HeapDumpPath=data + +# specify an alternative path for JVM fatal error logs +-XX:ErrorFile=logs/hs_err_pid%p.log + +## JDK 8 GC logging + +8:-XX:+PrintGCDetails +8:-XX:+PrintGCDateStamps +8:-XX:+PrintTenuringDistribution +8:-XX:+PrintGCApplicationStoppedTime +8:-Xloggc:logs/gc.log +8:-XX:+UseGCLogFileRotation +8:-XX:NumberOfGCLogFiles=32 +8:-XX:GCLogFileSize=64m + +# JDK 9+ GC logging +9-:-Xlog:gc*,gc+age=trace,safepoint:file=logs/gc.log:utctime,pid,tags:filecount=32,filesize=64m +# due to internationalization enhancements in JDK 9 Elasticsearch need to set the provider to COMPAT otherwise +# time/date parsing will break in an incompatible way for some date patterns and locals +9-:-Djava.locale.providers=COMPAT + +# temporary workaround for C2 bug with JDK 10 on hardware with AVX-512 +10-:-XX:UseAVX=2 \ No newline at end of file diff --git a/integration-tests/hibernate-search-elasticsearch/src/test/resources/elasticsearch-maven-plugin/configuration/log4j2.properties b/integration-tests/hibernate-search-elasticsearch/src/test/resources/elasticsearch-maven-plugin/configuration/log4j2.properties new file mode 100644 index 0000000000000..e4789c251b25a --- /dev/null +++ b/integration-tests/hibernate-search-elasticsearch/src/test/resources/elasticsearch-maven-plugin/configuration/log4j2.properties @@ -0,0 +1,36 @@ +status = error +appenders = console +loggers = action, metadata, cluster, settings, deprecation, slow_search, slow_indexing + +# log action execution errors for easier debugging +logger.action.name = org.elasticsearch.action +logger.action.level = info + +# do not log metadata too much as we generate a log of noise +logger.metadata.name = org.elasticsearch.cluster.metadata +logger.metadata.level = warn + +logger.cluster.name = org.elasticsearch.cluster.routing.allocation +logger.cluster.level = warn + +logger.settings.name = org.elasticsearch.common.settings +logger.settings.level = warn + +logger.deprecation.name = org.elasticsearch.deprecation +logger.deprecation.level = warn + +# Warn us about using inefficient Search operations +logger.slow_search.name = index.search.slowlog +logger.slow_search.level = trace + +# Warn us about using inefficient indexing actions +logger.slow_indexing.name = index.indexing.slowlog +logger.slow_indexing.level = trace + +appender.console.type = Console +appender.console.name = console +appender.console.layout.type = PatternLayout +appender.console.layout.pattern = %d{ABSOLUTE} (%t) %5p %c{1}:%L - %m%n + +rootLogger.level = info +rootLogger.appenderRef.console.ref = console diff --git a/integration-tests/hibernate-search-elasticsearch/src/test/resources/elasticsearch-maven-plugin/init/init.script b/integration-tests/hibernate-search-elasticsearch/src/test/resources/elasticsearch-maven-plugin/init/init.script new file mode 100644 index 0000000000000..7c20a72b48ffc --- /dev/null +++ b/integration-tests/hibernate-search-elasticsearch/src/test/resources/elasticsearch-maven-plugin/init/init.script @@ -0,0 +1,6 @@ +PUT:_template/lightweight_index:{ "template" : "*", "order": -9999, "settings" : { "number_of_shards" : 1, "number_of_replicas" : 0 } } +PUT:_template/slowlogs_search_level:{ "template" : "*", "order": -9999, "settings" : { "index.search.slowlog.level": "debug" } } +PUT:_template/slowlogs_indexing_level:{ "template" : "*", "order": -9999, "settings" : { "index.indexing.slowlog.level": "debug" } } +PUT:_template/slowlogs_search_threshold_query:{ "template" : "*", "order": -9999, "settings" : { "index.search.slowlog.threshold.query.warn": "5s", "index.search.slowlog.threshold.query.info": "500ms", "index.search.slowlog.threshold.query.debug": "100ms", "index.search.slowlog.threshold.query.trace": "10ms" } } +PUT:_template/slowlogs_search_threshold_fetch:{ "template" : "*", "order": -9999, "settings" : { "index.search.slowlog.threshold.fetch.warn": "1s", "index.search.slowlog.threshold.fetch.info": "200ms", "index.search.slowlog.threshold.fetch.debug": "100ms", "index.search.slowlog.threshold.fetch.trace": "10ms" } } +PUT:_template/slowlogs_indexing_threshold_index:{ "template" : "*", "order": -9999, "settings" : { "index.indexing.slowlog.threshold.index.warn": "2s", "index.indexing.slowlog.threshold.index.info": "500ms", "index.indexing.slowlog.threshold.index.debug": "100ms", "index.indexing.slowlog.threshold.index.trace": "10ms" } } diff --git a/integration-tests/hibernate-validator/pom.xml b/integration-tests/hibernate-validator/pom.xml index 4259ca0b4308e..3a166f0eb45ed 100644 --- a/integration-tests/hibernate-validator/pom.xml +++ b/integration-tests/hibernate-validator/pom.xml @@ -17,17 +17,14 @@ io.quarkus quarkus-resteasy - provided io.quarkus quarkus-hibernate-validator - provided io.quarkus quarkus-arc - provided diff --git a/integration-tests/hibernate-validator/src/main/java/io/quarkus/example/hibernate/validator/GreetingService.java b/integration-tests/hibernate-validator/src/main/java/io/quarkus/it/hibernate/validator/GreetingService.java similarity index 83% rename from integration-tests/hibernate-validator/src/main/java/io/quarkus/example/hibernate/validator/GreetingService.java rename to integration-tests/hibernate-validator/src/main/java/io/quarkus/it/hibernate/validator/GreetingService.java index 3e73706c89a9c..485f79f90cbda 100644 --- a/integration-tests/hibernate-validator/src/main/java/io/quarkus/example/hibernate/validator/GreetingService.java +++ b/integration-tests/hibernate-validator/src/main/java/io/quarkus/it/hibernate/validator/GreetingService.java @@ -1,4 +1,4 @@ -package io.quarkus.example.hibernate.validator; +package io.quarkus.it.hibernate.validator; import javax.enterprise.context.ApplicationScoped; import javax.validation.constraints.NotNull; diff --git a/integration-tests/hibernate-validator/src/main/java/io/quarkus/example/hibernate/validator/HibernateValidatorTestResource.java b/integration-tests/hibernate-validator/src/main/java/io/quarkus/it/hibernate/validator/HibernateValidatorTestResource.java similarity index 94% rename from integration-tests/hibernate-validator/src/main/java/io/quarkus/example/hibernate/validator/HibernateValidatorTestResource.java rename to integration-tests/hibernate-validator/src/main/java/io/quarkus/it/hibernate/validator/HibernateValidatorTestResource.java index 2e2380e705b1c..945ea52ca6120 100644 --- a/integration-tests/hibernate-validator/src/main/java/io/quarkus/example/hibernate/validator/HibernateValidatorTestResource.java +++ b/integration-tests/hibernate-validator/src/main/java/io/quarkus/it/hibernate/validator/HibernateValidatorTestResource.java @@ -1,4 +1,4 @@ -package io.quarkus.example.hibernate.validator; +package io.quarkus.it.hibernate.validator; import java.util.Collections; import java.util.HashMap; @@ -20,10 +20,11 @@ import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; import org.hibernate.validator.constraints.Length; -import io.quarkus.example.hibernate.validator.custom.MyOtherBean; +import io.quarkus.it.hibernate.validator.custom.MyOtherBean; @Path("/hibernate-validator/test") public class HibernateValidatorTestResource { @@ -101,6 +102,12 @@ public String testRestEndPointValidation(@Digits(integer = 5, fraction = 0) @Pat return id; } + @GET + @Path("/no-produces/{id}/") + public Response noProduces(@Digits(integer = 5, fraction = 0) @PathParam("id") String id) { + return Response.accepted().build(); + } + private String formatViolations(Set> violations) { if (violations.isEmpty()) { return "passed"; diff --git a/integration-tests/hibernate-validator/src/main/java/io/quarkus/example/hibernate/validator/custom/MyCustomConstraint.java b/integration-tests/hibernate-validator/src/main/java/io/quarkus/it/hibernate/validator/custom/MyCustomConstraint.java similarity index 94% rename from integration-tests/hibernate-validator/src/main/java/io/quarkus/example/hibernate/validator/custom/MyCustomConstraint.java rename to integration-tests/hibernate-validator/src/main/java/io/quarkus/it/hibernate/validator/custom/MyCustomConstraint.java index 75980d3982f0d..28a6bfdc163d3 100644 --- a/integration-tests/hibernate-validator/src/main/java/io/quarkus/example/hibernate/validator/custom/MyCustomConstraint.java +++ b/integration-tests/hibernate-validator/src/main/java/io/quarkus/it/hibernate/validator/custom/MyCustomConstraint.java @@ -1,4 +1,4 @@ -package io.quarkus.example.hibernate.validator.custom; +package io.quarkus.it.hibernate.validator.custom; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/integration-tests/hibernate-validator/src/main/java/io/quarkus/example/hibernate/validator/custom/MyOtherBean.java b/integration-tests/hibernate-validator/src/main/java/io/quarkus/it/hibernate/validator/custom/MyOtherBean.java similarity index 83% rename from integration-tests/hibernate-validator/src/main/java/io/quarkus/example/hibernate/validator/custom/MyOtherBean.java rename to integration-tests/hibernate-validator/src/main/java/io/quarkus/it/hibernate/validator/custom/MyOtherBean.java index c6784ed405992..4961a6d0103b0 100644 --- a/integration-tests/hibernate-validator/src/main/java/io/quarkus/example/hibernate/validator/custom/MyOtherBean.java +++ b/integration-tests/hibernate-validator/src/main/java/io/quarkus/it/hibernate/validator/custom/MyOtherBean.java @@ -1,4 +1,4 @@ -package io.quarkus.example.hibernate.validator.custom; +package io.quarkus.it.hibernate.validator.custom; @MyCustomConstraint public class MyOtherBean { diff --git a/integration-tests/hibernate-validator/src/test/java/io/quarkus/example/test/HibernateValidatorFunctionalityInGraalITCase.java b/integration-tests/hibernate-validator/src/test/java/io/quarkus/it/hibernate/validator/HibernateValidatorFunctionalityInGraalITCase.java similarity index 84% rename from integration-tests/hibernate-validator/src/test/java/io/quarkus/example/test/HibernateValidatorFunctionalityInGraalITCase.java rename to integration-tests/hibernate-validator/src/test/java/io/quarkus/it/hibernate/validator/HibernateValidatorFunctionalityInGraalITCase.java index 645cea0c45cfc..dfe34d8c60b6f 100644 --- a/integration-tests/hibernate-validator/src/test/java/io/quarkus/example/test/HibernateValidatorFunctionalityInGraalITCase.java +++ b/integration-tests/hibernate-validator/src/test/java/io/quarkus/it/hibernate/validator/HibernateValidatorFunctionalityInGraalITCase.java @@ -1,4 +1,4 @@ -package io.quarkus.example.test; +package io.quarkus.it.hibernate.validator; import io.quarkus.test.junit.SubstrateTest; diff --git a/integration-tests/hibernate-validator/src/test/java/io/quarkus/example/test/HibernateValidatorFunctionalityTest.java b/integration-tests/hibernate-validator/src/test/java/io/quarkus/it/hibernate/validator/HibernateValidatorFunctionalityTest.java similarity index 88% rename from integration-tests/hibernate-validator/src/test/java/io/quarkus/example/test/HibernateValidatorFunctionalityTest.java rename to integration-tests/hibernate-validator/src/test/java/io/quarkus/it/hibernate/validator/HibernateValidatorFunctionalityTest.java index 4948a29594f31..460405213b90b 100644 --- a/integration-tests/hibernate-validator/src/test/java/io/quarkus/example/test/HibernateValidatorFunctionalityTest.java +++ b/integration-tests/hibernate-validator/src/test/java/io/quarkus/it/hibernate/validator/HibernateValidatorFunctionalityTest.java @@ -1,4 +1,4 @@ -package io.quarkus.example.test; +package io.quarkus.it.hibernate.validator; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; @@ -68,4 +68,13 @@ public void testRestEndPointValidation() { .then() .body(is("42")); } + + @Test + public void testNoProduces() { + RestAssured.when() + .get("/hibernate-validator/test/no-produces/plop/") + .then() + .statusCode(400) + .body(containsString("numeric value out of bounds")); + } } diff --git a/integration-tests/infinispan-cache-jpa-stress/pom.xml b/integration-tests/infinispan-cache-jpa-stress/pom.xml deleted file mode 100644 index d043199bfe04f..0000000000000 --- a/integration-tests/infinispan-cache-jpa-stress/pom.xml +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - quarkus-integration-tests-parent - io.quarkus - 999-SNAPSHOT - ../ - - 4.0.0 - - quarkus-integration-test-infinispan-cache-jpa-stress - Quarkus - Integration Tests - JPA - Infinispan Cache - Stress - Module that contains JPA related stress tests with Infinispan second level cache - - - 2.8.2 - - - - - - io.quarkus - quarkus-hibernate-orm - provided - - - - io.quarkus - quarkus-jdbc-h2 - provided - - - - io.quarkus - quarkus-resteasy - provided - - - - - io.quarkus - quarkus-junit5-internal - test - - - io.rest-assured - rest-assured - test - - - org.apache.logging.log4j - log4j-api - test - - - org.apache.logging.log4j - log4j-core - test - - - org.apache.logging.log4j - log4j-slf4j-impl - test - - - - - - - src/main/resources - true - - - - - maven-surefire-plugin - - - org.jboss.logmanager.LogManager - - - - - - - diff --git a/integration-tests/infinispan-cache-jpa-stress/src/main/java/io/quarkus/example/infinispancachejpa/correctness/Family.java b/integration-tests/infinispan-cache-jpa-stress/src/main/java/io/quarkus/example/infinispancachejpa/correctness/Family.java deleted file mode 100644 index 5f9b6df852331..0000000000000 --- a/integration-tests/infinispan-cache-jpa-stress/src/main/java/io/quarkus/example/infinispancachejpa/correctness/Family.java +++ /dev/null @@ -1,17 +0,0 @@ -package io.quarkus.example.infinispancachejpa.correctness; - -import java.util.Set; - -public interface Family { - - int getId(); - - String getName(); - - void setName(String name); - - Set getMembers(); - - boolean addMember(Member member); - -} diff --git a/integration-tests/infinispan-cache-jpa-stress/src/main/java/io/quarkus/example/infinispancachejpa/correctness/Member.java b/integration-tests/infinispan-cache-jpa-stress/src/main/java/io/quarkus/example/infinispancachejpa/correctness/Member.java deleted file mode 100644 index 3f4a37bb60857..0000000000000 --- a/integration-tests/infinispan-cache-jpa-stress/src/main/java/io/quarkus/example/infinispancachejpa/correctness/Member.java +++ /dev/null @@ -1,7 +0,0 @@ -package io.quarkus.example.infinispancachejpa.correctness; - -public interface Member { - - String getFirstName(); - -} diff --git a/integration-tests/infinispan-cache-jpa-stress/src/main/java/io/quarkus/example/infinispancachejpa/correctness/readwrite/AddressRW.java b/integration-tests/infinispan-cache-jpa-stress/src/main/java/io/quarkus/example/infinispancachejpa/correctness/readwrite/AddressRW.java deleted file mode 100644 index 088dbfbca6643..0000000000000 --- a/integration-tests/infinispan-cache-jpa-stress/src/main/java/io/quarkus/example/infinispancachejpa/correctness/readwrite/AddressRW.java +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later. - * See the lgpl.txt file in the root directory or . - */ - -package io.quarkus.example.infinispancachejpa.correctness.readwrite; - -import java.util.HashSet; -import java.util.Iterator; -import java.util.Set; - -import javax.persistence.*; - -import org.hibernate.annotations.Cache; -import org.hibernate.annotations.CacheConcurrencyStrategy; - -@Entity -public final class AddressRW { - - @Id - @GeneratedValue - private int id; - private int streetNumber; - private String streetName; - private String cityName; - private String countryName; - private String zipCode; - @OneToMany - @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) - private Set inhabitants; - @Version - private int version; - - public AddressRW(int streetNumber, String streetName, String cityName, String countryName) { - this.streetNumber = streetNumber; - this.streetName = streetName; - this.cityName = cityName; - this.countryName = countryName; - this.zipCode = null; - this.inhabitants = new HashSet(); - this.id = 0; - this.version = 0; - } - - protected AddressRW() { - this.streetNumber = 0; - this.streetName = null; - this.cityName = null; - this.countryName = null; - this.zipCode = null; - this.inhabitants = new HashSet(); - this.id = 0; - this.version = 0; - } - - public int getStreetNumber() { - return streetNumber; - } - - public String getStreetName() { - return streetName; - } - - public String getCityName() { - return cityName; - } - - public String getCountryName() { - return countryName; - } - - public String getZipCode() { - return zipCode; - } - - public void setZipCode(String zipCode) { - this.zipCode = zipCode; - } - - public Set getInhabitants() { - return inhabitants; - } - - public boolean addInhabitant(MemberRW inhabitant) { - boolean done = false; - if (inhabitants.add(inhabitant)) { - inhabitant.setAddress(this); - done = true; - } - return done; - } - - public boolean remInhabitant(MemberRW inhabitant) { - boolean done = false; - if (inhabitants.remove(inhabitant)) { - inhabitant.setAddress(null); - done = true; - } - return done; - } - - public int getId() { - return id; - } - - public void setId(int id) { - this.id = id; - } - - public int getVersion() { - return version; - } - - protected void removeAllInhabitants() { - // inhabitants relation is not CASCADED, we must delete the relation on other side by ourselves - for (Iterator iterator = inhabitants.iterator(); iterator.hasNext();) { - MemberRW p = iterator.next(); - p.setAddress(null); - } - } - - protected void setStreetNumber(int streetNumber) { - this.streetNumber = streetNumber; - } - - protected void setStreetName(String streetName) { - this.streetName = streetName; - } - - protected void setCityName(String cityName) { - this.cityName = cityName; - } - - protected void setCountryName(String countryName) { - this.countryName = countryName; - } - - protected void setInhabitants(Set inhabitants) { - if (inhabitants == null) { - this.inhabitants = new HashSet(); - } else { - this.inhabitants = inhabitants; - } - } - - protected void setVersion(Integer version) { - this.version = version; - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - - AddressRW address = (AddressRW) o; - - // inhabitants must not be in the comparison since we would end up in infinite recursion - if (id != address.id) - return false; - if (streetNumber != address.streetNumber) - return false; - if (version != address.version) - return false; - if (cityName != null ? !cityName.equals(address.cityName) : address.cityName != null) - return false; - if (countryName != null ? !countryName.equals(address.countryName) : address.countryName != null) - return false; - if (streetName != null ? !streetName.equals(address.streetName) : address.streetName != null) - return false; - if (zipCode != null ? !zipCode.equals(address.zipCode) : address.zipCode != null) - return false; - - return true; - } - - @Override - public int hashCode() { - int result = streetNumber; - result = 31 * result + (streetName != null ? streetName.hashCode() : 0); - result = 31 * result + (cityName != null ? cityName.hashCode() : 0); - result = 31 * result + (countryName != null ? countryName.hashCode() : 0); - result = 31 * result + (zipCode != null ? zipCode.hashCode() : 0); - result = 31 * result + id; - result = 31 * result + version; - return result; - } - - @Override - public String toString() { - return "Address{" + - "cityName='" + cityName + '\'' + - ", streetNumber=" + streetNumber + - ", streetName='" + streetName + '\'' + - ", countryName='" + countryName + '\'' + - ", zipCode='" + zipCode + '\'' + - ", inhabitants=" + inhabitants + - ", id=" + id + - ", version=" + version + - '}'; - } - -} diff --git a/integration-tests/infinispan-cache-jpa-stress/src/main/java/io/quarkus/example/infinispancachejpa/correctness/readwrite/FamilyRW.java b/integration-tests/infinispan-cache-jpa-stress/src/main/java/io/quarkus/example/infinispancachejpa/correctness/readwrite/FamilyRW.java deleted file mode 100644 index ecc6a59c44154..0000000000000 --- a/integration-tests/infinispan-cache-jpa-stress/src/main/java/io/quarkus/example/infinispancachejpa/correctness/readwrite/FamilyRW.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later. - * See the lgpl.txt file in the root directory or . - */ - -package io.quarkus.example.infinispancachejpa.correctness.readwrite; - -import java.util.HashSet; -import java.util.Set; - -import javax.persistence.*; - -import org.hibernate.annotations.Cache; -import org.hibernate.annotations.CacheConcurrencyStrategy; - -import io.quarkus.example.infinispancachejpa.correctness.Family; -import io.quarkus.example.infinispancachejpa.correctness.Member; - -@Entity -public final class FamilyRW implements Family { - - @Id - @GeneratedValue - private int id; - private String name; - private String secondName; - @OneToMany(cascade = CascadeType.ALL, mappedBy = "family", orphanRemoval = true) - @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) - private Set members; - @Version - private int version; - - public FamilyRW(String name) { - this.name = name; - this.secondName = null; - this.members = new HashSet<>(); - this.id = 0; - this.version = 0; - } - - protected FamilyRW() { - this.name = null; - this.secondName = null; - this.members = new HashSet<>(); - this.id = 0; - this.version = 0; - } - - public String getName() { - return name; - } - - public Set getMembers() { - return members; - } - - public String getSecondName() { - return secondName; - } - - public void setSecondName(String secondName) { - this.secondName = secondName; - } - - public int getId() { - return id; - } - - public void setId(int id) { - this.id = id; - } - - public int getVersion() { - return version; - } - - public void setName(String name) { - this.name = name; - } - - public void setMembers(Set members) { - if (members == null) { - this.members = new HashSet<>(); - } else { - this.members = members; - } - } - - public void setVersion(Integer version) { - this.version = version; - } - - public boolean addMember(Member member) { - return members.add((MemberRW) member); - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - - FamilyRW family = (FamilyRW) o; - - // members must not be in the comparison since we would end up in infinite recursive call - if (id != family.id) - return false; - if (version != family.version) - return false; - if (name != null ? !name.equals(family.name) : family.name != null) - return false; - if (secondName != null ? !secondName.equals(family.secondName) : family.secondName != null) - return false; - - return true; - } - - @Override - public int hashCode() { - int result = name != null ? name.hashCode() : 0; - result = 31 * result + (secondName != null ? secondName.hashCode() : 0); - result = 31 * result + id; - result = 31 * result + version; - return result; - } - - @Override - public String toString() { - return "Family{" + - "id=" + id + - ", name='" + name + '\'' + - ", secondName='" + secondName + '\'' + - ", members=" + members + - ", version=" + version + - '}'; - } - -} diff --git a/integration-tests/infinispan-cache-jpa-stress/src/main/java/io/quarkus/example/infinispancachejpa/correctness/readwrite/MemberRW.java b/integration-tests/infinispan-cache-jpa-stress/src/main/java/io/quarkus/example/infinispancachejpa/correctness/readwrite/MemberRW.java deleted file mode 100644 index c96b39dffe1bc..0000000000000 --- a/integration-tests/infinispan-cache-jpa-stress/src/main/java/io/quarkus/example/infinispancachejpa/correctness/readwrite/MemberRW.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Hibernate, Relational Persistence for Idiomatic Java - * - * License: GNU Lesser General Public License (LGPL), version 2.1 or later. - * See the lgpl.txt file in the root directory or . - */ - -package io.quarkus.example.infinispancachejpa.correctness.readwrite; - -import java.util.Date; - -import javax.persistence.*; - -import io.quarkus.example.infinispancachejpa.correctness.Family; -import io.quarkus.example.infinispancachejpa.correctness.Member; - -@Entity -public class MemberRW implements Member { - - @Id - @GeneratedValue - private int id; - private String firstName; - @ManyToOne - private FamilyRW family; - private Date birthDate; - @ManyToOne - private AddressRW address; - private boolean checked; - @Version - private int version; - - public MemberRW(String firstName, Family family) { - this.firstName = firstName; - this.family = (FamilyRW) family; - this.birthDate = null; - this.address = null; - this.checked = false; - this.id = 0; - this.version = 0; - this.family.addMember(this); - } - - protected MemberRW() { - this.firstName = null; - this.family = null; - this.birthDate = null; - this.address = null; - this.checked = false; - this.id = 0; - this.version = 0; - } - - public String getFirstName() { - return firstName; - } - - public Family getFamily() { - return family; - } - - public Date getBirthDate() { - return birthDate; - } - - public void setBirthDate(Date birthDate) { - this.birthDate = birthDate; - } - - public AddressRW getAddress() { - return address; - } - - public void setAddress(AddressRW address) { - // To skip Hibernate BUG with access.PROPERTY : the rest should be done in DAO - // this.address = address; - // Hibernate BUG : if we update the relation on 2 sides - if (this.address != address) { - if (this.address != null) - this.address.remInhabitant(this); - this.address = address; - if (this.address != null) - this.address.addInhabitant(this); - } - } - - public boolean isChecked() { - return checked; - } - - public void setChecked(boolean checked) { - this.checked = checked; - } - - public int getId() { - return id; - } - - public void setId(int id) { - this.id = id; - } - - public int getVersion() { - return version; - } - - protected void setFirstName(String firstName) { - this.firstName = firstName; - } - - protected void setFamily(FamilyRW family) { - this.family = family; - } - - protected void setVersion(Integer version) { - this.version = version; - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - - MemberRW member = (MemberRW) o; - - if (checked != member.checked) - return false; - if (id != member.id) - return false; - if (version != member.version) - return false; - if (address != null ? !address.equals(member.address) : member.address != null) - return false; - if (birthDate != null ? !birthDate.equals(member.birthDate) : member.birthDate != null) - return false; - if (family != null ? !family.equals(member.family) : member.family != null) - return false; - if (firstName != null ? !firstName.equals(member.firstName) : member.firstName != null) - return false; - - return true; - } - - @Override - public int hashCode() { - int result = firstName != null ? firstName.hashCode() : 0; - result = 31 * result + (family != null ? family.hashCode() : 0); - result = 31 * result + (birthDate != null ? birthDate.hashCode() : 0); - result = 31 * result + (address != null ? address.hashCode() : 0); - result = 31 * result + (checked ? 1 : 0); - result = 31 * result + id; - result = 31 * result + version; - return result; - } - - @Override - public String toString() { - return "Member{" + - "address=" + address + - ", firstName='" + firstName + '\'' + - ", family=" + family + - ", birthDate=" + birthDate + - ", checked=" + checked + - ", id=" + id + - ", version=" + version + - '}'; - } - -} diff --git a/integration-tests/infinispan-cache-jpa-stress/src/main/resources/META-INF/nonstrict-persistence.xml b/integration-tests/infinispan-cache-jpa-stress/src/main/resources/META-INF/nonstrict-persistence.xml deleted file mode 100644 index 7a6cd397ee66e..0000000000000 --- a/integration-tests/infinispan-cache-jpa-stress/src/main/resources/META-INF/nonstrict-persistence.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/integration-tests/infinispan-cache-jpa-stress/src/main/resources/META-INF/readwrite-persistence.xml b/integration-tests/infinispan-cache-jpa-stress/src/main/resources/META-INF/readwrite-persistence.xml deleted file mode 100644 index 97c1591bd94db..0000000000000 --- a/integration-tests/infinispan-cache-jpa-stress/src/main/resources/META-INF/readwrite-persistence.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/integration-tests/infinispan-cache-jpa-stress/src/main/resources/application-nonstrict.properties b/integration-tests/infinispan-cache-jpa-stress/src/main/resources/application-nonstrict.properties deleted file mode 100644 index 7e262551aed1a..0000000000000 --- a/integration-tests/infinispan-cache-jpa-stress/src/main/resources/application-nonstrict.properties +++ /dev/null @@ -1,5 +0,0 @@ -io.quarkus.example.rest.RestInterface/mp-rest/url=http://localhost:8081/rest -quarkus.datasource.url=jdbc:h2:mem:nonstrict;TRACE_LEVEL_FILE=4 -quarkus.datasource.driver=org.h2.Driver -quarkus.datasource.maxSize=8 -quarkus.datasource.minSize=2 diff --git a/integration-tests/infinispan-cache-jpa-stress/src/main/resources/application-readwrite.properties b/integration-tests/infinispan-cache-jpa-stress/src/main/resources/application-readwrite.properties deleted file mode 100644 index 5249a4fb53468..0000000000000 --- a/integration-tests/infinispan-cache-jpa-stress/src/main/resources/application-readwrite.properties +++ /dev/null @@ -1,5 +0,0 @@ -io.quarkus.example.rest.RestInterface/mp-rest/url=http://localhost:8081/rest -quarkus.datasource.url=jdbc:h2:mem:readwrite;TRACE_LEVEL_FILE=4 -quarkus.datasource.driver=org.h2.Driver -quarkus.datasource.maxSize=8 -quarkus.datasource.minSize=2 diff --git a/integration-tests/infinispan-cache-jpa-stress/src/test/java/io/quarkus/example/test/InfinispanCacheJPACorrectnessTestCase.java b/integration-tests/infinispan-cache-jpa-stress/src/test/java/io/quarkus/example/test/InfinispanCacheJPACorrectnessTestCase.java deleted file mode 100644 index 631738f1f4d25..0000000000000 --- a/integration-tests/infinispan-cache-jpa-stress/src/test/java/io/quarkus/example/test/InfinispanCacheJPACorrectnessTestCase.java +++ /dev/null @@ -1,942 +0,0 @@ -package io.quarkus.example.test; - -import java.io.BufferedWriter; -import java.io.File; -import java.io.IOException; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.nio.file.Files; -import java.text.SimpleDateFormat; -import java.util.*; -import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Supplier; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import javax.persistence.EntityNotFoundException; -import javax.persistence.PersistenceException; -import javax.transaction.RollbackException; - -import org.hibernate.*; -import org.hibernate.cache.spi.Region; -import org.hibernate.criterion.Restrictions; -import org.hibernate.engine.spi.SessionFactoryImplementor; -import org.hibernate.exception.ConstraintViolationException; -import org.hibernate.exception.LockAcquisitionException; -import org.hibernate.resource.transaction.spi.TransactionStatus; -import org.infinispan.quarkus.hibernate.cache.PutFromLoadValidator; -import org.jboss.logging.Logger; - -import io.quarkus.example.infinispancachejpa.correctness.Family; -import io.quarkus.example.infinispancachejpa.correctness.Member; - -public class InfinispanCacheJPACorrectnessTestCase { - private static final Logger log = Logger.getLogger(InfinispanCacheJPACorrectnessTestCase.class); - - static final long EXECUTION_TIME = TimeUnit.MINUTES.toMillis(2); - static final int NUM_THREADS = 4; - static final int NUM_FAMILIES = 1; - static final int NUM_ACCESS_AFTER_REMOVAL = NUM_THREADS * 2; - static final int MAX_MEMBERS = 10; - final static Comparator> WALL_CLOCK_TIME_COMPARATOR = Comparator.comparingLong(o -> o.wallClockTime); - - private final static boolean INVALIDATE_REGION = true; - private final static boolean INJECT_FAILURES = Boolean.getBoolean("testInfinispan.correctness.injectFailures"); - - final AtomicInteger timestampGenerator = new AtomicInteger(); - final ConcurrentSkipListMap familyIds = new ConcurrentSkipListMap<>(); - volatile boolean running = true; - - final ThreadLocal>>> familyNames = ThreadLocal.withInitial(HashMap::new); - final ThreadLocal>>>> familyMembers = ThreadLocal.withInitial(HashMap::new); - private BlockingDeque exceptions = new LinkedBlockingDeque<>(); - - private final static Class[][] EXPECTED = { - { javax.persistence.RollbackException.class, PersistenceException.class, TransactionException.class, - RollbackException.class, StaleObjectStateException.class }, - { javax.persistence.RollbackException.class, PersistenceException.class, TransactionException.class, - RollbackException.class, PessimisticLockException.class }, - { TransactionException.class, RollbackException.class, LockAcquisitionException.class }, - { StaleStateException.class, PessimisticLockException.class }, - { StaleStateException.class, ObjectNotFoundException.class }, - { StaleStateException.class, ConstraintViolationException.class }, - { StaleStateException.class, LockAcquisitionException.class }, - { javax.persistence.RollbackException.class, PersistenceException.class, ConstraintViolationException.class }, - { PersistenceException.class, LockAcquisitionException.class }, - { javax.persistence.RollbackException.class, javax.persistence.PessimisticLockException.class, - PessimisticLockException.class }, - { javax.persistence.RollbackException.class, javax.persistence.OptimisticLockException.class, - StaleStateException.class }, - { PessimisticLockException.class }, - { StaleObjectStateException.class }, - { EntityNotFoundException.class }, - { LockAcquisitionException.class } - }; - - private final SessionFactory sessionFactory; - private final Class familyClass; - private final Function memberCtor; - private final Supplier familyCtor; - - public InfinispanCacheJPACorrectnessTestCase( - SessionFactory sessionFactory, Class familyClass, Function memberCtor, - Supplier familyCtor) { - this.sessionFactory = sessionFactory; - this.familyClass = familyClass; - this.memberCtor = memberCtor; - this.familyCtor = familyCtor; - } - - public void test() throws Exception { - ExecutorService exec = Executors.newFixedThreadPool(NUM_THREADS); - - Map>> allFamilyNames = new HashMap<>(); - Map>>> allFamilyMembers = new HashMap<>(); - - running = true; - List> futures = new ArrayList<>(); - for (int i = 0; i < NUM_THREADS; ++i) { - final int I = i; - futures.add(exec.submit(() -> { - Thread.currentThread().setName("Node" + (char) ('A') + "-thread-" + I); - while (running) { - Operation operation; - if (familyIds.size() < NUM_FAMILIES) { - operation = new InsertFamily(ThreadLocalRandom.current().nextInt(5) == 0); - } else { - operation = getOperation(); - } - try { - operation.run(); - } catch (Exception e) { - // ignore exceptions from optimistic failures and induced exceptions - if (hasCause(e, InducedException.class)) { - continue; - } else if (Stream.of(EXPECTED).anyMatch(exceptions -> matches(e, exceptions))) { - continue; - } - exceptions.add(e); - log.error("Failed " + operation.getClass().getName(), e); - } - } - synchronized (allFamilyNames) { - for (Map.Entry>> entry : familyNames.get().entrySet()) { - List> list = allFamilyNames.get(entry.getKey()); - if (list == null) - allFamilyNames.put(entry.getKey(), list = new ArrayList<>()); - list.addAll(entry.getValue()); - } - for (Map.Entry>>> entry : familyMembers.get().entrySet()) { - List>> list = allFamilyMembers.get(entry.getKey()); - if (list == null) - allFamilyMembers.put(entry.getKey(), list = new ArrayList<>()); - list.addAll(entry.getValue()); - } - } - return null; - })); - } - - Exception failure = exceptions.poll(EXECUTION_TIME, TimeUnit.MILLISECONDS); - if (failure != null) - exceptions.addFirst(failure); - running = false; - exec.shutdown(); - if (!exec.awaitTermination(1000, TimeUnit.SECONDS)) - throw new IllegalStateException(); - for (Future f : futures) { - f.get(); // check for exceptions - } - - // TODO: do we really need this? - checkForEmptyPendingPuts(); - - log.infof("Generated %d timestamps%n", timestampGenerator.get()); - AtomicInteger created = new AtomicInteger(); - AtomicInteger removed = new AtomicInteger(); - ForkJoinPool threadPool = ForkJoinPool.commonPool(); - ArrayList> tasks = new ArrayList<>(); - for (Map.Entry>> entry : allFamilyNames.entrySet()) { - tasks.add(threadPool.submit(() -> { - int familyId = entry.getKey(); - List> list = entry.getValue(); - created.incrementAndGet(); - NavigableMap>> logByTime = getWritesAtTime(list); - checkCorrectness("family_name-" + familyId + "-", list, logByTime); - if (list.stream().anyMatch(l -> l.type == LogType.WRITE && l.getValue() == null)) { - removed.incrementAndGet(); - } - })); - } - for (Map.Entry>>> entry : allFamilyMembers.entrySet()) { - tasks.add(threadPool.submit(() -> { - int familyId = entry.getKey(); - List>> list = entry.getValue(); - NavigableMap>>> logByTime = getWritesAtTime(list); - checkCorrectness("family_members-" + familyId + "-", list, logByTime); - })); - } - for (ForkJoinTask task : tasks) { - // with heavy logging this may have trouble to complete - task.get(30, TimeUnit.SECONDS); - } - if (!exceptions.isEmpty()) { - for (Exception e : exceptions) { - log.error("Test failure", e); - } - throw new IllegalStateException("There were " + exceptions.size() + " exceptions"); - } - log.infof("Created %d families, removed %d%n", created.get(), removed.get()); - } - - private static class DelayedInvalidators { - final ConcurrentMap map; - final Object key; - - public DelayedInvalidators(ConcurrentMap map, Object key) { - this.map = map; - this.key = key; - } - - public Object getPendingPutMap() { - return map.get(key); - } - } - - protected void checkForEmptyPendingPuts() throws Exception { - Field pp = PutFromLoadValidator.class.getDeclaredField("pendingPuts"); - pp.setAccessible(true); - Method getInvalidators = null; - List delayed = new LinkedList<>(); - - SessionFactoryImplementor sfi = (SessionFactoryImplementor) sessionFactory; - for (Object regionName : sfi.getCache().getCacheRegionNames()) { - PutFromLoadValidator validator = getPutFromLoadValidator(sfi, (String) regionName); - if (validator == null) { - log.warn("No validator for " + regionName); - continue; - } - ConcurrentMap map = ((com.github.benmanes.caffeine.cache.Cache) pp.get(validator)).asMap(); - for (Iterator> iterator = map.entrySet().iterator(); iterator.hasNext();) { - Map.Entry entry = iterator.next(); - if (getInvalidators == null) { - getInvalidators = entry.getValue().getClass().getMethod("getInvalidators"); - getInvalidators.setAccessible(true); - } - java.util.Collection invalidators = (java.util.Collection) getInvalidators.invoke(entry.getValue()); - if (invalidators != null && !invalidators.isEmpty()) { - delayed.add(new DelayedInvalidators(map, entry.getKey())); - } - } - } - - // poll until all invalidations come - long deadline = System.currentTimeMillis() + 30000; - while (System.currentTimeMillis() < deadline) { - iterateInvalidators(delayed, getInvalidators, (k, i) -> { - }); - if (delayed.isEmpty()) { - break; - } - Thread.sleep(1000); - } - if (!delayed.isEmpty()) { - iterateInvalidators(delayed, getInvalidators, (k, i) -> log.warnf("Left invalidators on key %s: %s", k, i)); - throw new IllegalStateException("Invalidators were not cleared: " + delayed.size()); - } - } - - private void iterateInvalidators(List delayed, Method getInvalidators, - BiConsumer invalidatorConsumer) - throws IllegalAccessException, InvocationTargetException { - for (Iterator iterator = delayed.iterator(); iterator.hasNext();) { - DelayedInvalidators entry = iterator.next(); - Object pendingPutMap = entry.getPendingPutMap(); - if (pendingPutMap == null) { - iterator.remove(); - } else { - java.util.Collection invalidators = (java.util.Collection) getInvalidators.invoke(pendingPutMap); - if (invalidators == null || invalidators.isEmpty()) { - iterator.remove(); - } - invalidatorConsumer.accept(entry.key, invalidators); - } - } - } - - private PutFromLoadValidator getPutFromLoadValidator(SessionFactoryImplementor sfi, String regionName) - throws NoSuchFieldException, IllegalAccessException { - Region region = sfi.getCache().getRegion(regionName); - if (region == null) { - return null; - } - Field validatorField = getField(region.getClass(), "validator"); - if (validatorField == null) { - return null; - } - - Object validator = validatorField.get(region); - if (validator == null) { - return null; - } - - // Non-null in strict data access patterns - return (PutFromLoadValidator) validator; - } - - private Field getField(Class clazz, String fieldName) { - Field f = null; - while (clazz != null && clazz != Object.class) { - try { - f = clazz.getDeclaredField(fieldName); - break; - } catch (NoSuchFieldException e) { - clazz = clazz.getSuperclass(); - } - } - if (f != null) { - f.setAccessible(true); - } - return f; - } - - private boolean hasCause(Throwable throwable, Class clazz) { - if (throwable == null) - return false; - Throwable cause = throwable.getCause(); - if (throwable == cause) - return false; - if (clazz.isInstance(cause)) - return true; - return hasCause(cause, clazz); - } - - private boolean matches(Throwable throwable, Class[] classes) { - return matches(throwable, classes, 0); - } - - private boolean matches(Throwable throwable, Class[] classes, int index) { - return index >= classes.length - || (classes[index].isInstance(throwable) - && matches(throwable.getCause(), classes, index + 1)); - } - - private NavigableMap>> getWritesAtTime(List> list) { - NavigableMap>> writes = new TreeMap<>(); - for (Log log : list) { - if (log.type != LogType.WRITE) - continue; - for (int time = log.before; time <= log.after; ++time) { - List> onTime = writes.get(time); - if (onTime == null) { - writes.put(time, onTime = new ArrayList<>()); - } - onTime.add(log); - } - } - return writes; - } - - private void checkCorrectness(String dumpPrefix, List> logs, NavigableMap>> writesByTime) { - Collections.sort(logs, WALL_CLOCK_TIME_COMPARATOR); - int nullReads = 0, reads = 0, writes = 0; - for (Log read : logs) { - if (read.type != LogType.READ) { - writes++; - continue; - } - if (read.getValue() == null || isEmptyCollection(read)) - nullReads++; - else - reads++; - - Map> possibleValues = new HashMap<>(); - for (List> list : writesByTime.subMap(read.before, true, read.after, true).values()) { - for (Log write : list) { - if (read.precedes(write)) - continue; - possibleValues.put(write.getValue(), write); - } - } - int startOfLastWriteBeforeRead = 0; - for (Map.Entry>> entry : writesByTime.headMap(read.before, false).descendingMap().entrySet()) { - int time = entry.getKey(); - if (time < startOfLastWriteBeforeRead) - break; - for (Log write : entry.getValue()) { - if (write.after < read.before && write.before > startOfLastWriteBeforeRead) { - startOfLastWriteBeforeRead = write.before; - } - possibleValues.put(write.getValue(), write); - } - } - - if (possibleValues.isEmpty()) { - // the entry was not created at all (first write failed) - break; - } - if (!possibleValues.containsKey(read.getValue())) { - dumpLogs(dumpPrefix, logs); - exceptions.add( - new IllegalStateException(String.format("R %s: %d .. %d (%s, %s) -> %s not in %s (%d+)", dumpPrefix, - read.before, read.after, read.threadName, - new SimpleDateFormat("HH:mm:ss,SSS").format(new Date(read.wallClockTime)), - read.getValue(), possibleValues.values(), startOfLastWriteBeforeRead))); - break; - } - } - log.infof("Checked %d null reads, %d reads and %d writes%n", nullReads, reads, writes); - } - - private static boolean isEmptyCollection(Log read) { - return read.getValue() instanceof java.util.Collection && ((java.util.Collection) read.getValue()).isEmpty(); - } - - private void dumpLogs(String prefix, List> logs) { - try { - File f = File.createTempFile(prefix, ".log"); - log.info("Dumping logs into " + f.getAbsolutePath()); - try (BufferedWriter writer = Files.newBufferedWriter(f.toPath())) { - for (Log log : logs) { - writer.write(log.toString()); - writer.write('\n'); - } - } - } catch (IOException e) { - log.error("Failed to dump family logs"); - } - } - - private Operation getOperation() { - ThreadLocalRandom random = ThreadLocalRandom.current(); - Operation operation; - int r = random.nextInt(100); - if (r == 0 && INVALIDATE_REGION) - operation = new InvalidateCache(); - else if (r < 5) - operation = new QueryFamilies(); - else if (r < 10) - operation = new RemoveFamily(r < 6); - else if (r < 20) - operation = new UpdateFamily(r < 12, random.nextInt(1, 3)); - else if (r < 35) - operation = new AddMember(r < 25); - else if (r < 50) - operation = new RemoveMember(r < 40); - else - operation = new ReadFamily(r < 75); - return operation; - } - - private class ReadFamily extends Operation { - private final boolean evict; - - public ReadFamily(boolean evict) { - super(false); - this.evict = evict; - } - - @Override - public void run() throws Exception { - withRandomFamily((s, f) -> { - if (evict) { - sessionFactory.getCache().evictEntity(familyClass, f.getId()); - } - }, Ref.empty(), Ref.empty(), null); - } - } - - private class RemoveMember extends MemberOperation { - public RemoveMember(boolean rolledBack) { - super(rolledBack); - } - - @Override - protected boolean updateMembers(Session s, ThreadLocalRandom random, Family f) { - int numMembers = f.getMembers().size(); - if (numMembers > 0) { - Iterator it = f.getMembers().iterator(); - Member member = null; - for (int i = random.nextInt(numMembers); i >= 0; --i) { - member = it.next(); - } - it.remove(); - if (member != null) { - s.delete(member); - } - return true; - } else { - return false; - } - } - } - - private class InvalidateCache extends Operation { - public InvalidateCache() { - super(false); - } - - @Override - public void run() throws Exception { - log.trace("Invalidating all caches"); - sessionFactory.getCache().evictAllRegions(); - } - } - - private abstract class MemberOperation extends Operation { - public MemberOperation(boolean rolledBack) { - super(rolledBack); - } - - @Override - public void run() throws Exception { - Ref> newMembers = new Ref<>(); - withRandomFamily((s, f) -> { - boolean updated = updateMembers(s, ThreadLocalRandom.current(), f); - if (updated) { - newMembers.set(membersToNames(f.getMembers())); - s.persist(f); - } - }, Ref.empty(), newMembers, LockMode.OPTIMISTIC_FORCE_INCREMENT); - } - - protected abstract boolean updateMembers(Session s, ThreadLocalRandom random, Family f); - } - - private class AddMember extends MemberOperation { - public AddMember(boolean rolledBack) { - super(rolledBack); - } - - protected boolean updateMembers(Session s, ThreadLocalRandom random, Family f) { - Set members = f.getMembers(); - if (members.size() < MAX_MEMBERS) { - members.add(createMember(f)); - return true; - } else { - return false; - } - } - - private T createMember(Family f) { - return (T) memberCtor.apply(f); - } - } - - private class UpdateFamily extends Operation { - private final int numUpdates; - - public UpdateFamily(boolean rolledBack, int numUpdates) { - super(rolledBack); - this.numUpdates = numUpdates; - } - - @Override - public void run() throws Exception { - String[] newNames = new String[numUpdates]; - for (int i = 0; i < numUpdates; ++i) { - newNames[i] = randomString(ThreadLocalRandom.current()); - } - withRandomFamilies(numUpdates, (s, families) -> { - for (int i = 0; i < numUpdates; ++i) { - Family f = families[i]; - if (f != null) { - f.setName(newNames[i]); - s.persist(f); - } - } - }, newNames, null, LockMode.OPTIMISTIC_FORCE_INCREMENT); - } - } - - private class RemoveFamily extends Operation { - public RemoveFamily(boolean rolledBack) { - super(rolledBack); - } - - @Override - public void run() throws Exception { - withRandomFamily((s, f) -> s.delete(f), Ref.of(null), Ref.of(Collections.EMPTY_SET), LockMode.OPTIMISTIC); - } - } - - private class QueryFamilies extends Operation { - final static int MAX_RESULTS = 10; - - public QueryFamilies() { - super(false); - } - - @Override - public void run() throws Exception { - String prefix = new StringBuilder(2) - .append((char) ThreadLocalRandom.current().nextInt('A', 'Z' + 1)).append('%').toString(); - int[] ids = new int[MAX_RESULTS]; - String[] names = new String[MAX_RESULTS]; - Set[] members = new Set[MAX_RESULTS]; - - int before = timestampGenerator.getAndIncrement(); - log.tracef("Started QueryFamilies at %d", before); - withSession(s -> { - List results = s.createCriteria(familyClass) - .add(Restrictions.like("name", prefix)) - .setMaxResults(MAX_RESULTS) - .setCacheable(true) - .list(); - int index = 0; - for (Family f : results) { - ids[index] = f.getId(); - names[index] = f.getName(); - members[index] = membersToNames(f.getMembers()); - ++index; - } - }); - - int after = timestampGenerator.getAndIncrement(); - log.tracef("Finished QueryFamilies at %d", after); - for (int index = 0; index < MAX_RESULTS; ++index) { - if (names[index] == null) - break; - getRecordList(familyNames, ids[index]).add(new Log<>(before, after, names[index], LogType.READ)); - getRecordList(familyMembers, ids[index]).add(new Log<>(before, after, members[index], LogType.READ)); - } - } - } - - private abstract class Operation { - protected final boolean rolledBack; - - public Operation(boolean rolledBack) { - this.rolledBack = rolledBack; - } - - public abstract void run() throws Exception; - - protected void withSession(Consumer consumer) throws Exception { - Session s = sessionFactory.openSession(); - Transaction tx = s.getTransaction(); - tx.begin(); - try { - consumer.accept(s); - } catch (Exception e) { - tx.markRollbackOnly(); - throw e; - } finally { - try { - if (!rolledBack && tx.getStatus() == TransactionStatus.ACTIVE) { - log.trace("Hibernate commit begin"); - tx.commit(); - log.trace("Hibernate commit end"); - } else { - log.trace("Hibernate rollback begin"); - tx.rollback(); - log.trace("Hibernate rollback end"); - } - } catch (Exception e) { - log.trace("Hibernate commit or rollback failed, status is " + tx.getStatus(), e); - if (tx.getStatus() == TransactionStatus.MARKED_ROLLBACK) { - tx.rollback(); - } - throw e; - } finally { - // cannot close before XA commit since force increment requires open connection - s.close(); - } - } - } - - protected void withRandomFamily(BiConsumer consumer, Ref familyNameUpdate, - Ref> familyMembersUpdate, LockMode lockMode) throws Exception { - int id = randomFamilyId(ThreadLocalRandom.current()); - int before = timestampGenerator.getAndIncrement(); - log.tracef("Started %s(%d, %s) at %d", getClass().getSimpleName(), id, rolledBack, before); - Log familyNameLog = new Log<>(); - Log> familyMembersLog = new Log<>(); - - boolean failure = false; - try { - withSession(s -> { - Family f = lockMode != null ? s.get(familyClass, id, lockMode) : s.get(familyClass, id); - if (f == null) { - familyNameLog.setValue(null); - familyMembersLog.setValue(Collections.EMPTY_SET); - familyNotFound(id); - } else { - familyNameLog.setValue(f.getName()); - familyMembersLog.setValue(membersToNames(f.getMembers())); - consumer.accept(s, f); - } - }); - } catch (Exception e) { - failure = true; - throw e; - } finally { - int after = timestampGenerator.getAndIncrement(); - recordReadWrite(id, before, after, failure, familyNameUpdate, familyMembersUpdate, familyNameLog, - familyMembersLog); - } - } - - protected void withRandomFamilies(int numFamilies, BiConsumer consumer, String[] familyNameUpdates, - Set[] familyMembersUpdates, LockMode lockMode) throws Exception { - int ids[] = new int[numFamilies]; - Log[] familyNameLogs = new Log[numFamilies]; - Log>[] familyMembersLogs = new Log[numFamilies]; - for (int i = 0; i < numFamilies; ++i) { - ids[i] = randomFamilyId(ThreadLocalRandom.current()); - familyNameLogs[i] = new Log<>(); - familyMembersLogs[i] = new Log<>(); - } - int before = timestampGenerator.getAndIncrement(); - log.tracef("Started %s(%s) at %d", getClass().getSimpleName(), Arrays.toString(ids), before); - - boolean failure = false; - try { - withSession(s -> { - Family[] families = new Family[numFamilies]; - for (int i = 0; i < numFamilies; ++i) { - Family f = lockMode != null ? s.get(familyClass, ids[i], lockMode) : s.get(familyClass, ids[i]); - families[i] = f; - if (f == null) { - familyNameLogs[i].setValue(null); - familyMembersLogs[i].setValue(Collections.EMPTY_SET); - familyNotFound(ids[i]); - } else { - familyNameLogs[i].setValue(f.getName()); - familyMembersLogs[i].setValue(membersToNames(f.getMembers())); - } - } - consumer.accept(s, families); - }); - } catch (Exception e) { - failure = true; - throw e; - } finally { - int after = timestampGenerator.getAndIncrement(); - for (int i = 0; i < numFamilies; ++i) { - recordReadWrite(ids[i], before, after, failure, - familyNameUpdates != null ? Ref.of(familyNameUpdates[i]) : Ref.empty(), - familyMembersUpdates != null ? Ref.of(familyMembersUpdates[i]) : Ref.empty(), - familyNameLogs[i], familyMembersLogs[i]); - } - } - } - - private void recordReadWrite(int id, int before, int after, boolean failure, Ref familyNameUpdate, - Ref> familyMembersUpdate, Log familyNameLog, Log> familyMembersLog) { - log.tracef("Finished %s at %d", getClass().getSimpleName(), after); - - LogType readType, writeType; - if (failure || rolledBack) { - writeType = LogType.WRITE_FAILURE; - readType = LogType.READ_FAILURE; - } else { - writeType = LogType.WRITE; - readType = LogType.READ; - } - - familyNameLog.setType(readType).setTimes(before, after); - familyMembersLog.setType(readType).setTimes(before, after); - - getRecordList(familyNames, id).add(familyNameLog); - getRecordList(familyMembers, id).add(familyMembersLog); - - if (familyNameLog.getValue() != null) { - if (familyNameUpdate.isSet()) { - getRecordList(familyNames, id) - .add(new Log<>(before, after, familyNameUpdate.get(), writeType, familyNameLog)); - } - if (familyMembersUpdate.isSet()) { - getRecordList(familyMembers, id) - .add(new Log<>(before, after, familyMembersUpdate.get(), writeType, familyMembersLog)); - } - } - } - } - - private class InsertFamily extends Operation { - public InsertFamily(boolean rolledBack) { - super(rolledBack); - } - - @Override - public void run() throws Exception { - Family family = familyCtor.get(); - int before = timestampGenerator.getAndIncrement(); - log.trace("Started InsertFamily at " + before); - boolean failure = false; - try { - withSession(s -> s.persist(family)); - } catch (Exception e) { - failure = true; - throw e; - } finally { - int after = timestampGenerator.getAndIncrement(); - log.trace("Finished InsertFamily at " + after + ", " + (failure ? "failed" : "success")); - familyIds.put(family.getId(), new AtomicInteger(NUM_ACCESS_AFTER_REMOVAL)); - LogType type = failure || rolledBack ? LogType.WRITE_FAILURE : LogType.WRITE; - getRecordList(familyNames, family.getId()).add(new Log<>(before, after, family.getName(), type)); - getRecordList(familyMembers, family.getId()) - .add(new Log<>(before, after, membersToNames(family.getMembers()), type)); - } - } - } - - private void familyNotFound(int id) { - AtomicInteger access = familyIds.get(id); - if (access == null) - return; - if (access.decrementAndGet() == 0) { - familyIds.remove(id); - } - } - - private static Set membersToNames(Set members) { - return members.stream().map(p -> p.getFirstName()).collect(Collectors.toSet()); - } - - private static List getRecordList(ThreadLocal>> tlListMap, int id) { - Map> map = tlListMap.get(); - List list = map.get(id); - if (list == null) - map.put(id, list = new ArrayList<>()); - return list; - } - - private int randomFamilyId(ThreadLocalRandom random) { - Map.Entry first = familyIds.firstEntry(); - Map.Entry last = familyIds.lastEntry(); - if (first == null || last == null) - return 0; - Map.Entry ceiling = familyIds.ceilingEntry(random.nextInt(first.getKey(), last.getKey() + 1)); - return ceiling == null ? 0 : ceiling.getKey(); - } - - public static String randomString(ThreadLocalRandom random) { - StringBuilder sb = new StringBuilder(10); - for (int i = 0; i < 10; ++i) { - sb.append((char) random.nextInt('A', 'Z' + 1)); - } - return sb.toString(); - } - - private enum LogType { - READ('R'), WRITE('W'), READ_FAILURE('L'), WRITE_FAILURE('F'); - - private final char shortName; - - LogType(char shortName) { - this.shortName = shortName; - } - } - - private static final class Log { - int before; - int after; - T value; - LogType type; - Log[] preceding; - String threadName; - long wallClockTime; - - public Log(int time) { - this(); - this.before = time; - this.after = time; - } - - public Log(int before, int after, T value, LogType type, Log... preceding) { - this(); - this.before = before; - this.after = after; - this.value = value; - this.type = type; - this.preceding = preceding; - } - - public Log() { - threadName = Thread.currentThread().getName(); - wallClockTime = System.currentTimeMillis(); - } - - public Log setType(LogType type) { - this.type = type; - return this; - } - - public void setTimes(int before, int after) { - this.before = before; - this.after = after; - } - - public void setValue(T value) { - this.value = value; - } - - public T getValue() { - return value; - } - - public boolean precedes(Log write) { - if (write.preceding == null) - return false; - for (Log l : write.preceding) { - if (l == this) - return true; - } - return false; - } - - @Override - public String toString() { - return String.format("%c: %5d - %5d\t(%s,\t%s)\t%s", type.shortName, before, after, - new SimpleDateFormat("HH:mm:ss,SSS").format(new Date(wallClockTime)), threadName, value); - } - } - - private static class Ref { - private static Ref EMPTY = new Ref() { - @Override - public void set(Object value) { - throw new UnsupportedOperationException(); - } - }; - private boolean set; - private T value; - - public static Ref empty() { - return EMPTY; - } - - public static Ref of(T value) { - Ref ref = new Ref(); - ref.set(value); - return ref; - } - - public boolean isSet() { - return set; - } - - public T get() { - return value; - } - - public void set(T value) { - this.value = value; - this.set = true; - } - } - - public static class InducedException extends Exception { - public InducedException(String message) { - super(message); - } - } - -} diff --git a/integration-tests/infinispan-cache-jpa-stress/src/test/java/io/quarkus/example/test/InfinispanCacheJPANonStrictCorrectnessTest.java b/integration-tests/infinispan-cache-jpa-stress/src/test/java/io/quarkus/example/test/InfinispanCacheJPANonStrictCorrectnessTest.java deleted file mode 100644 index 3134a73b85e45..0000000000000 --- a/integration-tests/infinispan-cache-jpa-stress/src/test/java/io/quarkus/example/test/InfinispanCacheJPANonStrictCorrectnessTest.java +++ /dev/null @@ -1,70 +0,0 @@ -package io.quarkus.example.test; - -import java.util.HashSet; -import java.util.Set; -import java.util.function.Function; -import java.util.function.Supplier; - -import javax.annotation.PostConstruct; -import javax.inject.Inject; -import javax.persistence.EntityManagerFactory; - -import org.hibernate.SessionFactory; -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.example.infinispancachejpa.correctness.Family; -import io.quarkus.example.infinispancachejpa.correctness.Member; -import io.quarkus.example.infinispancachejpa.correctness.readwrite.FamilyRW; -import io.quarkus.example.infinispancachejpa.correctness.readwrite.MemberRW; -import io.quarkus.test.QuarkusUnitTest; - -/** - * For logging, run with: -Dorg.jboss.logging.provider=log4j2 - */ -@Disabled -public class InfinispanCacheJPANonStrictCorrectnessTest { - - @Inject - EntityManagerFactory entityManagerFactory; - - private InfinispanCacheJPACorrectnessTestCase testCase; - - @PostConstruct - void init() { - SessionFactory sessionFactory = entityManagerFactory.unwrap(SessionFactory.class); - - // Collections don't have integrated version, these piggyback on parent's owner version (for DB). - // However, this version number isn't extractable and is not passed to cache methods. - // Hence, just use the same entities as defined for read-write. - // Entity level cache concurrency is set by configuration file. - - Function memberCtor = family -> new MemberRW(Utils.randomString(), family); - Supplier familyCtor = () -> { - String familyName = Utils.randomString(); - FamilyRW f = new FamilyRW(familyName); - Set members = new HashSet<>(); - members.add((MemberRW) memberCtor.apply(f)); - f.setMembers(members); - return f; - }; - - testCase = new InfinispanCacheJPACorrectnessTestCase(sessionFactory, FamilyRW.class, memberCtor, familyCtor); - } - - @RegisterExtension - static QuarkusUnitTest runner = new QuarkusUnitTest() - .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) - .addAsManifestResource("META-INF/nonstrict-persistence.xml", "persistence.xml") - .addAsResource("application-nonstrict.properties", - "application.properties")); - - @Test - public void test() throws Exception { - testCase.test(); - } - -} diff --git a/integration-tests/infinispan-cache-jpa-stress/src/test/java/io/quarkus/example/test/InfinispanCacheJPAReadWriteCorrectnessTest.java b/integration-tests/infinispan-cache-jpa-stress/src/test/java/io/quarkus/example/test/InfinispanCacheJPAReadWriteCorrectnessTest.java deleted file mode 100644 index 2ebc7cb67ce1f..0000000000000 --- a/integration-tests/infinispan-cache-jpa-stress/src/test/java/io/quarkus/example/test/InfinispanCacheJPAReadWriteCorrectnessTest.java +++ /dev/null @@ -1,65 +0,0 @@ -package io.quarkus.example.test; - -import java.util.HashSet; -import java.util.Set; -import java.util.function.Function; -import java.util.function.Supplier; - -import javax.annotation.PostConstruct; -import javax.inject.Inject; -import javax.persistence.EntityManagerFactory; - -import org.hibernate.SessionFactory; -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.example.infinispancachejpa.correctness.Family; -import io.quarkus.example.infinispancachejpa.correctness.Member; -import io.quarkus.example.infinispancachejpa.correctness.readwrite.FamilyRW; -import io.quarkus.example.infinispancachejpa.correctness.readwrite.MemberRW; -import io.quarkus.test.QuarkusUnitTest; - -/** - * For logging, run with: -Dorg.jboss.logging.provider=log4j2 - */ -@Disabled -public class InfinispanCacheJPAReadWriteCorrectnessTest { - - @Inject - EntityManagerFactory entityManagerFactory; - - private InfinispanCacheJPACorrectnessTestCase testCase; - - @PostConstruct - void init() { - SessionFactory sessionFactory = entityManagerFactory.unwrap(SessionFactory.class); - - Function memberCtor = family -> new MemberRW(Utils.randomString(), family); - Supplier familyCtor = () -> { - String familyName = Utils.randomString(); - FamilyRW f = new FamilyRW(familyName); - Set members = new HashSet<>(); - members.add((MemberRW) memberCtor.apply(f)); - f.setMembers(members); - return f; - }; - - testCase = new InfinispanCacheJPACorrectnessTestCase(sessionFactory, FamilyRW.class, memberCtor, familyCtor); - } - - @RegisterExtension - static QuarkusUnitTest runner = new QuarkusUnitTest() - .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) - .addAsManifestResource("META-INF/readwrite-persistence.xml", "persistence.xml") - .addAsResource("application-readwrite.properties", - "application.properties")); - - @Test - public void test() throws Exception { - testCase.test(); - } - -} diff --git a/integration-tests/infinispan-cache-jpa-stress/src/test/java/io/quarkus/example/test/Utils.java b/integration-tests/infinispan-cache-jpa-stress/src/test/java/io/quarkus/example/test/Utils.java deleted file mode 100644 index b74d8f1e61ac1..0000000000000 --- a/integration-tests/infinispan-cache-jpa-stress/src/test/java/io/quarkus/example/test/Utils.java +++ /dev/null @@ -1,16 +0,0 @@ -package io.quarkus.example.test; - -import java.util.concurrent.ThreadLocalRandom; - -public class Utils { - - public static String randomString() { - ThreadLocalRandom random = ThreadLocalRandom.current(); - StringBuilder sb = new StringBuilder(10); - for (int i = 0; i < 10; ++i) { - sb.append((char) random.nextInt('A', 'Z' + 1)); - } - return sb.toString(); - } - -} diff --git a/integration-tests/infinispan-cache-jpa-stress/src/test/resources/log4j2.xml b/integration-tests/infinispan-cache-jpa-stress/src/test/resources/log4j2.xml deleted file mode 100644 index 639e58b9ba1d7..0000000000000 --- a/integration-tests/infinispan-cache-jpa-stress/src/test/resources/log4j2.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/integration-tests/infinispan-cache-jpa/pom.xml b/integration-tests/infinispan-cache-jpa/pom.xml index ebb942f5b956e..8072f598ab75f 100644 --- a/integration-tests/infinispan-cache-jpa/pom.xml +++ b/integration-tests/infinispan-cache-jpa/pom.xml @@ -34,19 +34,16 @@ io.quarkus quarkus-hibernate-orm - provided io.quarkus quarkus-jdbc-h2 - provided io.quarkus quarkus-resteasy - provided diff --git a/integration-tests/infinispan-cache-jpa/src/main/java/io/quarkus/example/infinispancachejpa/Citizen.java b/integration-tests/infinispan-cache-jpa/src/main/java/io/quarkus/it/infinispan/cache/jpa/Citizen.java similarity index 97% rename from integration-tests/infinispan-cache-jpa/src/main/java/io/quarkus/example/infinispancachejpa/Citizen.java rename to integration-tests/infinispan-cache-jpa/src/main/java/io/quarkus/it/infinispan/cache/jpa/Citizen.java index 030c7b2a6d495..de173659b88b6 100644 --- a/integration-tests/infinispan-cache-jpa/src/main/java/io/quarkus/example/infinispancachejpa/Citizen.java +++ b/integration-tests/infinispan-cache-jpa/src/main/java/io/quarkus/it/infinispan/cache/jpa/Citizen.java @@ -6,7 +6,7 @@ */ //$Id$ -package io.quarkus.example.infinispancachejpa; +package io.quarkus.it.infinispan.cache.jpa; import javax.persistence.*; diff --git a/integration-tests/infinispan-cache-jpa/src/main/java/io/quarkus/example/infinispancachejpa/Country.java b/integration-tests/infinispan-cache-jpa/src/main/java/io/quarkus/it/infinispan/cache/jpa/Country.java similarity index 95% rename from integration-tests/infinispan-cache-jpa/src/main/java/io/quarkus/example/infinispancachejpa/Country.java rename to integration-tests/infinispan-cache-jpa/src/main/java/io/quarkus/it/infinispan/cache/jpa/Country.java index a1711c5d97578..cde352f115d51 100644 --- a/integration-tests/infinispan-cache-jpa/src/main/java/io/quarkus/example/infinispancachejpa/Country.java +++ b/integration-tests/infinispan-cache-jpa/src/main/java/io/quarkus/it/infinispan/cache/jpa/Country.java @@ -1,4 +1,4 @@ -package io.quarkus.example.infinispancachejpa; +package io.quarkus.it.infinispan.cache.jpa; import javax.persistence.Cacheable; import javax.persistence.Entity; diff --git a/integration-tests/infinispan-cache-jpa/src/main/java/io/quarkus/example/infinispancachejpa/InfinispanCacheJPAFunctionalityTestEndpoint.java b/integration-tests/infinispan-cache-jpa/src/main/java/io/quarkus/it/infinispan/cache/jpa/InfinispanCacheJPAFunctionalityTestEndpoint.java similarity index 99% rename from integration-tests/infinispan-cache-jpa/src/main/java/io/quarkus/example/infinispancachejpa/InfinispanCacheJPAFunctionalityTestEndpoint.java rename to integration-tests/infinispan-cache-jpa/src/main/java/io/quarkus/it/infinispan/cache/jpa/InfinispanCacheJPAFunctionalityTestEndpoint.java index e57af13764c5c..8579c69208ea3 100644 --- a/integration-tests/infinispan-cache-jpa/src/main/java/io/quarkus/example/infinispancachejpa/InfinispanCacheJPAFunctionalityTestEndpoint.java +++ b/integration-tests/infinispan-cache-jpa/src/main/java/io/quarkus/it/infinispan/cache/jpa/InfinispanCacheJPAFunctionalityTestEndpoint.java @@ -1,4 +1,4 @@ -package io.quarkus.example.infinispancachejpa; +package io.quarkus.it.infinispan.cache.jpa; import java.io.IOException; import java.io.PrintWriter; diff --git a/integration-tests/infinispan-cache-jpa/src/main/java/io/quarkus/example/infinispancachejpa/Item.java b/integration-tests/infinispan-cache-jpa/src/main/java/io/quarkus/it/infinispan/cache/jpa/Item.java similarity index 96% rename from integration-tests/infinispan-cache-jpa/src/main/java/io/quarkus/example/infinispancachejpa/Item.java rename to integration-tests/infinispan-cache-jpa/src/main/java/io/quarkus/it/infinispan/cache/jpa/Item.java index e69b1db74dc44..05ca67055bb2b 100644 --- a/integration-tests/infinispan-cache-jpa/src/main/java/io/quarkus/example/infinispancachejpa/Item.java +++ b/integration-tests/infinispan-cache-jpa/src/main/java/io/quarkus/it/infinispan/cache/jpa/Item.java @@ -1,4 +1,4 @@ -package io.quarkus.example.infinispancachejpa; +package io.quarkus.it.infinispan.cache.jpa; import javax.persistence.*; diff --git a/integration-tests/infinispan-cache-jpa/src/main/java/io/quarkus/example/infinispancachejpa/Person.java b/integration-tests/infinispan-cache-jpa/src/main/java/io/quarkus/it/infinispan/cache/jpa/Person.java similarity index 95% rename from integration-tests/infinispan-cache-jpa/src/main/java/io/quarkus/example/infinispancachejpa/Person.java rename to integration-tests/infinispan-cache-jpa/src/main/java/io/quarkus/it/infinispan/cache/jpa/Person.java index 7209b1766e869..815ff8cf57e4d 100644 --- a/integration-tests/infinispan-cache-jpa/src/main/java/io/quarkus/example/infinispancachejpa/Person.java +++ b/integration-tests/infinispan-cache-jpa/src/main/java/io/quarkus/it/infinispan/cache/jpa/Person.java @@ -1,4 +1,4 @@ -package io.quarkus.example.infinispancachejpa; +package io.quarkus.it.infinispan.cache.jpa; import javax.persistence.*; diff --git a/integration-tests/infinispan-cache-jpa/src/main/java/io/quarkus/example/infinispancachejpa/Pokemon.java b/integration-tests/infinispan-cache-jpa/src/main/java/io/quarkus/it/infinispan/cache/jpa/Pokemon.java similarity index 93% rename from integration-tests/infinispan-cache-jpa/src/main/java/io/quarkus/example/infinispancachejpa/Pokemon.java rename to integration-tests/infinispan-cache-jpa/src/main/java/io/quarkus/it/infinispan/cache/jpa/Pokemon.java index 52a0c0876a89c..ad4b0d0c82828 100644 --- a/integration-tests/infinispan-cache-jpa/src/main/java/io/quarkus/example/infinispancachejpa/Pokemon.java +++ b/integration-tests/infinispan-cache-jpa/src/main/java/io/quarkus/it/infinispan/cache/jpa/Pokemon.java @@ -1,4 +1,4 @@ -package io.quarkus.example.infinispancachejpa; +package io.quarkus.it.infinispan.cache.jpa; import javax.persistence.Cacheable; import javax.persistence.Entity; diff --git a/integration-tests/infinispan-cache-jpa/src/main/java/io/quarkus/example/infinispancachejpa/Trainer.java b/integration-tests/infinispan-cache-jpa/src/main/java/io/quarkus/it/infinispan/cache/jpa/Trainer.java similarity index 94% rename from integration-tests/infinispan-cache-jpa/src/main/java/io/quarkus/example/infinispancachejpa/Trainer.java rename to integration-tests/infinispan-cache-jpa/src/main/java/io/quarkus/it/infinispan/cache/jpa/Trainer.java index 9ad21cbef7148..0ab0ca5e1b839 100644 --- a/integration-tests/infinispan-cache-jpa/src/main/java/io/quarkus/example/infinispancachejpa/Trainer.java +++ b/integration-tests/infinispan-cache-jpa/src/main/java/io/quarkus/it/infinispan/cache/jpa/Trainer.java @@ -1,4 +1,4 @@ -package io.quarkus.example.infinispancachejpa; +package io.quarkus.it.infinispan.cache.jpa; import java.util.Arrays; import java.util.List; diff --git a/integration-tests/infinispan-cache-jpa/src/main/resources/application.properties b/integration-tests/infinispan-cache-jpa/src/main/resources/application.properties index bb09d5882a5df..010f945687db0 100644 --- a/integration-tests/infinispan-cache-jpa/src/main/resources/application.properties +++ b/integration-tests/infinispan-cache-jpa/src/main/resources/application.properties @@ -1,7 +1,7 @@ quarkus.datasource.url=jdbc:h2:tcp://localhost/mem:test quarkus.datasource.driver=org.h2.Driver -quarkus.datasource.maxSize=8 -quarkus.datasource.minSize=2 +quarkus.datasource.max-size=8 +quarkus.datasource.min-size=2 quarkus.hibernate-orm.database.generation=drop-and-create quarkus.hibernate-orm.statistics=true quarkus.hibernate-orm.cache."com.example.EntityA".memory.object-count=200 diff --git a/integration-tests/infinispan-cache-jpa/src/test/java/io/quarkus/example/test/InfinispanCacheJPAFunctionalityInGraalITCase.java b/integration-tests/infinispan-cache-jpa/src/test/java/io/quarkus/it/infinispan/cache/jpa/InfinispanCacheJPAFunctionalityInGraalITCase.java similarity index 83% rename from integration-tests/infinispan-cache-jpa/src/test/java/io/quarkus/example/test/InfinispanCacheJPAFunctionalityInGraalITCase.java rename to integration-tests/infinispan-cache-jpa/src/test/java/io/quarkus/it/infinispan/cache/jpa/InfinispanCacheJPAFunctionalityInGraalITCase.java index fc61b6223c825..f0809bf8e0759 100644 --- a/integration-tests/infinispan-cache-jpa/src/test/java/io/quarkus/example/test/InfinispanCacheJPAFunctionalityInGraalITCase.java +++ b/integration-tests/infinispan-cache-jpa/src/test/java/io/quarkus/it/infinispan/cache/jpa/InfinispanCacheJPAFunctionalityInGraalITCase.java @@ -1,4 +1,4 @@ -package io.quarkus.example.test; +package io.quarkus.it.infinispan.cache.jpa; import io.quarkus.test.junit.SubstrateTest; diff --git a/integration-tests/infinispan-cache-jpa/src/test/java/io/quarkus/example/test/InfinispanCacheJPAFunctionalityTest.java b/integration-tests/infinispan-cache-jpa/src/test/java/io/quarkus/it/infinispan/cache/jpa/InfinispanCacheJPAFunctionalityTest.java similarity index 95% rename from integration-tests/infinispan-cache-jpa/src/test/java/io/quarkus/example/test/InfinispanCacheJPAFunctionalityTest.java rename to integration-tests/infinispan-cache-jpa/src/test/java/io/quarkus/it/infinispan/cache/jpa/InfinispanCacheJPAFunctionalityTest.java index 161fc6509cb3d..17f438aceb4dc 100644 --- a/integration-tests/infinispan-cache-jpa/src/test/java/io/quarkus/example/test/InfinispanCacheJPAFunctionalityTest.java +++ b/integration-tests/infinispan-cache-jpa/src/test/java/io/quarkus/it/infinispan/cache/jpa/InfinispanCacheJPAFunctionalityTest.java @@ -1,4 +1,4 @@ -package io.quarkus.example.test; +package io.quarkus.it.infinispan.cache.jpa; import static org.hamcrest.Matchers.is; diff --git a/integration-tests/infinispan-cache-jpa/src/test/java/io/quarkus/example/test/TestResources.java b/integration-tests/infinispan-cache-jpa/src/test/java/io/quarkus/it/infinispan/cache/jpa/TestResources.java similarity index 94% rename from integration-tests/infinispan-cache-jpa/src/test/java/io/quarkus/example/test/TestResources.java rename to integration-tests/infinispan-cache-jpa/src/test/java/io/quarkus/it/infinispan/cache/jpa/TestResources.java index 06653064904e9..6b15f8302765f 100644 --- a/integration-tests/infinispan-cache-jpa/src/test/java/io/quarkus/example/test/TestResources.java +++ b/integration-tests/infinispan-cache-jpa/src/test/java/io/quarkus/it/infinispan/cache/jpa/TestResources.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.test; +package io.quarkus.it.infinispan.cache.jpa; import io.quarkus.test.common.QuarkusTestResource; import io.quarkus.test.h2.H2DatabaseTestResource; diff --git a/integration-tests/infinispan-client/pom.xml b/integration-tests/infinispan-client/pom.xml index 4d03c671009e6..2866bcffebda5 100644 --- a/integration-tests/infinispan-client/pom.xml +++ b/integration-tests/infinispan-client/pom.xml @@ -33,20 +33,17 @@ io.quarkus quarkus-infinispan-client - provided io.quarkus quarkus-arc - provided io.quarkus quarkus-resteasy - provided diff --git a/integration-tests/infinispan-client/src/main/java/io/quarkus/example/infinispanclient/Author.java b/integration-tests/infinispan-client/src/main/java/io/quarkus/it/infinispan/client/Author.java similarity index 94% rename from integration-tests/infinispan-client/src/main/java/io/quarkus/example/infinispanclient/Author.java rename to integration-tests/infinispan-client/src/main/java/io/quarkus/it/infinispan/client/Author.java index fb004c73dc44a..ece1d72cf63b0 100644 --- a/integration-tests/infinispan-client/src/main/java/io/quarkus/example/infinispanclient/Author.java +++ b/integration-tests/infinispan-client/src/main/java/io/quarkus/it/infinispan/client/Author.java @@ -1,4 +1,4 @@ -package io.quarkus.example.infinispanclient; +package io.quarkus.it.infinispan.client; import java.util.Objects; diff --git a/integration-tests/infinispan-client/src/main/java/io/quarkus/example/infinispanclient/AuthorMarshaller.java b/integration-tests/infinispan-client/src/main/java/io/quarkus/it/infinispan/client/AuthorMarshaller.java similarity index 94% rename from integration-tests/infinispan-client/src/main/java/io/quarkus/example/infinispanclient/AuthorMarshaller.java rename to integration-tests/infinispan-client/src/main/java/io/quarkus/it/infinispan/client/AuthorMarshaller.java index 190c3151d54c7..d3a19db073b36 100644 --- a/integration-tests/infinispan-client/src/main/java/io/quarkus/example/infinispanclient/AuthorMarshaller.java +++ b/integration-tests/infinispan-client/src/main/java/io/quarkus/it/infinispan/client/AuthorMarshaller.java @@ -1,4 +1,4 @@ -package io.quarkus.example.infinispanclient; +package io.quarkus.it.infinispan.client; import java.io.IOException; diff --git a/integration-tests/infinispan-client/src/main/java/io/quarkus/example/infinispanclient/Book.java b/integration-tests/infinispan-client/src/main/java/io/quarkus/it/infinispan/client/Book.java similarity index 76% rename from integration-tests/infinispan-client/src/main/java/io/quarkus/example/infinispanclient/Book.java rename to integration-tests/infinispan-client/src/main/java/io/quarkus/it/infinispan/client/Book.java index bcf0a27b72ac1..4912f1211b308 100644 --- a/integration-tests/infinispan-client/src/main/java/io/quarkus/example/infinispanclient/Book.java +++ b/integration-tests/infinispan-client/src/main/java/io/quarkus/it/infinispan/client/Book.java @@ -1,4 +1,4 @@ -package io.quarkus.example.infinispanclient; +package io.quarkus.it.infinispan.client; import java.util.Objects; import java.util.Set; @@ -11,12 +11,19 @@ public class Book { private final String description; private final int publicationYear; private final Set authors; + private final Type bookType; - public Book(String title, String description, int publicationYear, Set authors) { + enum Type { + FANTASY, + PROGRAMMING + } + + public Book(String title, String description, int publicationYear, Set authors, Type bookType) { this.title = Objects.requireNonNull(title); this.description = Objects.requireNonNull(description); this.publicationYear = publicationYear; this.authors = Objects.requireNonNull(authors); + this.bookType = bookType; } public String getTitle() { @@ -35,6 +42,10 @@ public Set getAuthors() { return authors; } + public Type getBookType() { + return bookType; + } + @Override public boolean equals(Object o) { if (this == o) @@ -45,11 +56,12 @@ public boolean equals(Object o) { return publicationYear == book.publicationYear && title.equals(book.title) && description.equals(book.description) && - authors.equals(book.authors); + authors.equals(book.authors) && + bookType.equals(book.bookType); } @Override public int hashCode() { - return Objects.hash(title, description, publicationYear, authors); + return Objects.hash(title, description, publicationYear, authors, bookType); } } diff --git a/integration-tests/infinispan-client/src/main/java/io/quarkus/example/infinispanclient/BookMarshaller.java b/integration-tests/infinispan-client/src/main/java/io/quarkus/it/infinispan/client/BookMarshaller.java similarity index 86% rename from integration-tests/infinispan-client/src/main/java/io/quarkus/example/infinispanclient/BookMarshaller.java rename to integration-tests/infinispan-client/src/main/java/io/quarkus/it/infinispan/client/BookMarshaller.java index 1a13e195d29e0..f1d5bf3bd6c34 100644 --- a/integration-tests/infinispan-client/src/main/java/io/quarkus/example/infinispanclient/BookMarshaller.java +++ b/integration-tests/infinispan-client/src/main/java/io/quarkus/it/infinispan/client/BookMarshaller.java @@ -1,4 +1,4 @@ -package io.quarkus.example.infinispanclient; +package io.quarkus.it.infinispan.client; import java.io.IOException; import java.util.HashSet; @@ -27,6 +27,7 @@ public void writeTo(ProtoStreamWriter writer, Book book) throws IOException { writer.writeString("description", book.getDescription()); writer.writeInt("publicationYear", book.getPublicationYear()); writer.writeCollection("authors", book.getAuthors(), Author.class); + writer.writeEnum("bookType", book.getBookType()); } @Override @@ -35,6 +36,7 @@ public Book readFrom(ProtoStreamReader reader) throws IOException { String description = reader.readString("description"); int publicationYear = reader.readInt("publicationYear"); Set authors = reader.readCollection("authors", new HashSet<>(), Author.class); - return new Book(title, description, publicationYear, authors); + Book.Type bookType = reader.readEnum("bookType", Book.Type.class); + return new Book(title, description, publicationYear, authors, bookType); } } diff --git a/integration-tests/infinispan-client/src/main/java/io/quarkus/it/infinispan/client/BookTypeMarshaller.java b/integration-tests/infinispan-client/src/main/java/io/quarkus/it/infinispan/client/BookTypeMarshaller.java new file mode 100644 index 0000000000000..b350c95b5f6a8 --- /dev/null +++ b/integration-tests/infinispan-client/src/main/java/io/quarkus/it/infinispan/client/BookTypeMarshaller.java @@ -0,0 +1,42 @@ +package io.quarkus.it.infinispan.client; + +import org.infinispan.protostream.EnumMarshaller; + +/** + * @author Katia Aresti, karesti@redhat.com + */ +public class BookTypeMarshaller implements EnumMarshaller { + + @Override + public Class getJavaClass() { + return Book.Type.class; + } + + @Override + public String getTypeName() { + return "book_sample.Book.Type"; + } + + @Override + public Book.Type decode(int enumValue) { + switch (enumValue) { + case 0: + return Book.Type.FANTASY; + case 1: + return Book.Type.PROGRAMMING; + } + return null; // unknown value + } + + @Override + public int encode(Book.Type bookType) { + switch (bookType) { + case FANTASY: + return 0; + case PROGRAMMING: + return 1; + default: + throw new IllegalArgumentException("Unexpected Book.Type value : " + bookType); + } + } +} diff --git a/integration-tests/infinispan-client/src/main/java/io/quarkus/example/infinispanclient/MarshallerConfiguration.java b/integration-tests/infinispan-client/src/main/java/io/quarkus/it/infinispan/client/MarshallerConfiguration.java similarity index 75% rename from integration-tests/infinispan-client/src/main/java/io/quarkus/example/infinispanclient/MarshallerConfiguration.java rename to integration-tests/infinispan-client/src/main/java/io/quarkus/it/infinispan/client/MarshallerConfiguration.java index 1018d0569c1d6..df345e442bcec 100644 --- a/integration-tests/infinispan-client/src/main/java/io/quarkus/example/infinispanclient/MarshallerConfiguration.java +++ b/integration-tests/infinispan-client/src/main/java/io/quarkus/it/infinispan/client/MarshallerConfiguration.java @@ -1,18 +1,25 @@ -package io.quarkus.example.infinispanclient; +package io.quarkus.it.infinispan.client; import javax.enterprise.context.ApplicationScoped; import javax.enterprise.inject.Produces; +import org.infinispan.protostream.BaseMarshaller; import org.infinispan.protostream.FileDescriptorSource; import org.infinispan.protostream.MessageMarshaller; /** * Handles configuration of marshalling code for marshalling - * + * * @author William Burns */ @ApplicationScoped public class MarshallerConfiguration { + + @Produces + BaseMarshaller bookTypeMarshaller() { + return new BookTypeMarshaller(); + } + @Produces MessageMarshaller bookMarshaller() { return new BookMarshaller(); @@ -33,6 +40,12 @@ FileDescriptorSource bookProtoDefinition() { " required int32 publicationYear = 3; // no native Date type available in Protobuf\n" + "\n" + " repeated Author authors = 4;\n" + + "\n" + + " enum Type {\n" + + " FANTASY = 0;\n" + + " PROGRAMMING = 1;\n" + + " }\n" + + " required Type bookType = 5;\n" + "}\n" + "\n" + "message Author {\n" + diff --git a/integration-tests/infinispan-client/src/main/java/io/quarkus/example/infinispanclient/MyApplication.java b/integration-tests/infinispan-client/src/main/java/io/quarkus/it/infinispan/client/MyApplication.java similarity index 77% rename from integration-tests/infinispan-client/src/main/java/io/quarkus/example/infinispanclient/MyApplication.java rename to integration-tests/infinispan-client/src/main/java/io/quarkus/it/infinispan/client/MyApplication.java index 3c30ec784c3e3..66ce3bf02a034 100644 --- a/integration-tests/infinispan-client/src/main/java/io/quarkus/example/infinispanclient/MyApplication.java +++ b/integration-tests/infinispan-client/src/main/java/io/quarkus/it/infinispan/client/MyApplication.java @@ -1,4 +1,4 @@ -package io.quarkus.example.infinispanclient; +package io.quarkus.it.infinispan.client; import javax.ws.rs.ApplicationPath; import javax.ws.rs.core.Application; diff --git a/integration-tests/infinispan-client/src/main/java/io/quarkus/example/infinispanclient/TestServlet.java b/integration-tests/infinispan-client/src/main/java/io/quarkus/it/infinispan/client/TestServlet.java similarity index 97% rename from integration-tests/infinispan-client/src/main/java/io/quarkus/example/infinispanclient/TestServlet.java rename to integration-tests/infinispan-client/src/main/java/io/quarkus/it/infinispan/client/TestServlet.java index f51a1ffc7d523..b2d12fbeb1b4a 100644 --- a/integration-tests/infinispan-client/src/main/java/io/quarkus/example/infinispanclient/TestServlet.java +++ b/integration-tests/infinispan-client/src/main/java/io/quarkus/it/infinispan/client/TestServlet.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.infinispanclient; +package io.quarkus.it.infinispan.client; import java.util.Collections; import java.util.List; @@ -117,9 +117,9 @@ public void resultUpdated(String key, Book value) { log.info("Added continuous query listener"); cache.put("book1", new Book("Game of Thrones", "Lots of people perish", 2010, - Collections.singleton(new Author("George", "Martin")))); + Collections.singleton(new Author("George", "Martin")), Book.Type.FANTASY)); cache.put("book2", new Book("Game of Thrones Path 2", "They win?", 2023, - Collections.singleton(new Author("Son", "Martin")))); + Collections.singleton(new Author("Son", "Martin")), Book.Type.FANTASY)); log.info("Inserted values"); @@ -253,7 +253,7 @@ public String nearCache() { long nearCacheInvalidations = stats.getNearCacheInvalidations(); Book nearCacheBook = new Book("Near Cache Book", "Just here to test", 2010, - Collections.emptySet()); + Collections.emptySet(), Book.Type.PROGRAMMING); String id = "nearcache"; cache.put(id, nearCacheBook); @@ -287,7 +287,7 @@ public String nearCache() { return "second retrieved book doesn't match"; } - nearCacheBook = new Book("Near Cache Book", "Just here to test", 2011, Collections.emptySet()); + nearCacheBook = new Book("Near Cache Book", "Just here to test", 2011, Collections.emptySet(), Book.Type.PROGRAMMING); cache.put(id, nearCacheBook); @@ -316,7 +316,7 @@ public String nearCache() { @Consumes(MediaType.TEXT_PLAIN) public Response createItem(String value, @PathParam("id") String id) { ensureStart(); - Book book = new Book(id, value, 2019, Collections.emptySet()); + Book book = new Book(id, value, 2019, Collections.emptySet(), Book.Type.PROGRAMMING); Book previous = cache.putIfAbsent(id, book); if (previous == null) { //status code 201 diff --git a/integration-tests/infinispan-client/src/main/resources/META-INF/library.proto b/integration-tests/infinispan-client/src/main/resources/META-INF/library.proto index 2f1d3dc356f1b..66ed1fcba40b0 100644 --- a/integration-tests/infinispan-client/src/main/resources/META-INF/library.proto +++ b/integration-tests/infinispan-client/src/main/resources/META-INF/library.proto @@ -6,6 +6,13 @@ message Book { required int32 publicationYear = 3; // no native Date type available in Protobuf repeated Author authors = 4; + + enum Type { + FANTASY = 0; + PROGRAMMING = 1; + } + + required Type bookType = 5; } message Author { diff --git a/integration-tests/infinispan-client/src/test/java/io/quarkus/example/test/InfinispanClientFunctionalityInGraalITCase.java b/integration-tests/infinispan-client/src/test/java/io/quarkus/it/infinispan/client/InfinispanClientFunctionalityInGraalITCase.java similarity index 82% rename from integration-tests/infinispan-client/src/test/java/io/quarkus/example/test/InfinispanClientFunctionalityInGraalITCase.java rename to integration-tests/infinispan-client/src/test/java/io/quarkus/it/infinispan/client/InfinispanClientFunctionalityInGraalITCase.java index 3dbb413f36748..51ee3bc0918c1 100644 --- a/integration-tests/infinispan-client/src/test/java/io/quarkus/example/test/InfinispanClientFunctionalityInGraalITCase.java +++ b/integration-tests/infinispan-client/src/test/java/io/quarkus/it/infinispan/client/InfinispanClientFunctionalityInGraalITCase.java @@ -1,4 +1,4 @@ -package io.quarkus.example.test; +package io.quarkus.it.infinispan.client; import io.quarkus.test.junit.SubstrateTest; diff --git a/integration-tests/infinispan-client/src/test/java/io/quarkus/example/test/InfinispanClientFunctionalityTest.java b/integration-tests/infinispan-client/src/test/java/io/quarkus/it/infinispan/client/InfinispanClientFunctionalityTest.java similarity index 96% rename from integration-tests/infinispan-client/src/test/java/io/quarkus/example/test/InfinispanClientFunctionalityTest.java rename to integration-tests/infinispan-client/src/test/java/io/quarkus/it/infinispan/client/InfinispanClientFunctionalityTest.java index 7e70722bae888..bc00cc56d474a 100644 --- a/integration-tests/infinispan-client/src/test/java/io/quarkus/example/test/InfinispanClientFunctionalityTest.java +++ b/integration-tests/infinispan-client/src/test/java/io/quarkus/it/infinispan/client/InfinispanClientFunctionalityTest.java @@ -1,9 +1,8 @@ -package io.quarkus.example.test; +package io.quarkus.it.infinispan.client; import static org.hamcrest.Matchers.is; import static org.junit.jupiter.api.Assertions.assertEquals; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import io.quarkus.test.common.QuarkusTestResource; diff --git a/integration-tests/infinispan-client/src/test/java/io/quarkus/example/test/InfinispanServerTestResource.java b/integration-tests/infinispan-client/src/test/java/io/quarkus/it/infinispan/client/InfinispanServerTestResource.java similarity index 87% rename from integration-tests/infinispan-client/src/test/java/io/quarkus/example/test/InfinispanServerTestResource.java rename to integration-tests/infinispan-client/src/test/java/io/quarkus/it/infinispan/client/InfinispanServerTestResource.java index dee7d03e92553..b6f8af5dece8b 100644 --- a/integration-tests/infinispan-client/src/test/java/io/quarkus/example/test/InfinispanServerTestResource.java +++ b/integration-tests/infinispan-client/src/test/java/io/quarkus/it/infinispan/client/InfinispanServerTestResource.java @@ -1,4 +1,7 @@ -package io.quarkus.example.test; +package io.quarkus.it.infinispan.client; + +import java.util.Collections; +import java.util.Map; import org.infinispan.configuration.cache.ConfigurationBuilder; import org.infinispan.configuration.global.GlobalConfigurationBuilder; @@ -14,13 +17,14 @@ public class InfinispanServerTestResource implements QuarkusTestResourceLifecycl private HotRodServer hotRodServer; @Override - public void start() { + public Map start() { TestResourceTracker.setThreadTestName("InfinispanServer"); EmbeddedCacheManager ecm = TestCacheManagerFactory.createCacheManager( new GlobalConfigurationBuilder().nonClusteredDefault().defaultCacheName("default"), new ConfigurationBuilder()); // Client connects to a non default port hotRodServer = HotRodTestingUtil.startHotRodServer(ecm, 11232); + return Collections.emptyMap(); } @Override diff --git a/integration-tests/jpa-h2/pom.xml b/integration-tests/jpa-h2/pom.xml index afb43bb217180..7f82f64d0acad 100644 --- a/integration-tests/jpa-h2/pom.xml +++ b/integration-tests/jpa-h2/pom.xml @@ -34,19 +34,16 @@ io.quarkus quarkus-hibernate-orm - provided io.quarkus quarkus-jdbc-h2 - provided io.quarkus quarkus-resteasy - provided diff --git a/integration-tests/jpa-h2/src/main/java/io/quarkus/example/jpah2/JPAFunctionalityTestEndpoint.java b/integration-tests/jpa-h2/src/main/java/io/quarkus/it/jpa/h2/JPAFunctionalityTestEndpoint.java similarity index 99% rename from integration-tests/jpa-h2/src/main/java/io/quarkus/example/jpah2/JPAFunctionalityTestEndpoint.java rename to integration-tests/jpa-h2/src/main/java/io/quarkus/it/jpa/h2/JPAFunctionalityTestEndpoint.java index dc20a28332f2e..5d23b416a8dd7 100644 --- a/integration-tests/jpa-h2/src/main/java/io/quarkus/example/jpah2/JPAFunctionalityTestEndpoint.java +++ b/integration-tests/jpa-h2/src/main/java/io/quarkus/it/jpa/h2/JPAFunctionalityTestEndpoint.java @@ -1,4 +1,4 @@ -package io.quarkus.example.jpah2; +package io.quarkus.it.jpa.h2; import java.io.IOException; import java.io.PrintWriter; diff --git a/integration-tests/jpa-mssql/src/main/java/io/quarkus/example/jpamssql/Person.java b/integration-tests/jpa-h2/src/main/java/io/quarkus/it/jpa/h2/Person.java similarity index 76% rename from integration-tests/jpa-mssql/src/main/java/io/quarkus/example/jpamssql/Person.java rename to integration-tests/jpa-h2/src/main/java/io/quarkus/it/jpa/h2/Person.java index 7fcc07e23df39..2b32e4765b499 100644 --- a/integration-tests/jpa-mssql/src/main/java/io/quarkus/example/jpamssql/Person.java +++ b/integration-tests/jpa-h2/src/main/java/io/quarkus/it/jpa/h2/Person.java @@ -1,38 +1,42 @@ -package io.quarkus.example.jpamssql; +package io.quarkus.it.jpa.h2; + +import java.util.UUID; import javax.persistence.CascadeType; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.ManyToOne; import javax.persistence.NamedQuery; +import org.hibernate.annotations.GenericGenerator; + @Entity @NamedQuery(name = "get_person_by_name", query = "select p from Person p where name = :name") public class Person { - private long id; + private UUID id; private String name; private SequencedAddress address; public Person() { } - public Person(long id, String name, SequencedAddress address) { + public Person(UUID id, String name, SequencedAddress address) { this.id = id; this.name = name; this.address = address; } @Id - @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "personSeq") - public long getId() { + @GeneratedValue(generator = "UUID") + @GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator") + public UUID getId() { return id; } - public void setId(long id) { + public void setId(UUID id) { this.id = id; } diff --git a/integration-tests/jpa-h2/src/main/java/io/quarkus/example/jpah2/SequencedAddress.java b/integration-tests/jpa-h2/src/main/java/io/quarkus/it/jpa/h2/SequencedAddress.java similarity index 96% rename from integration-tests/jpa-h2/src/main/java/io/quarkus/example/jpah2/SequencedAddress.java rename to integration-tests/jpa-h2/src/main/java/io/quarkus/it/jpa/h2/SequencedAddress.java index e3026b86fcaaf..0e836a42bc192 100644 --- a/integration-tests/jpa-h2/src/main/java/io/quarkus/example/jpah2/SequencedAddress.java +++ b/integration-tests/jpa-h2/src/main/java/io/quarkus/it/jpa/h2/SequencedAddress.java @@ -1,4 +1,4 @@ -package io.quarkus.example.jpah2; +package io.quarkus.it.jpa.h2; import javax.persistence.Entity; import javax.persistence.GeneratedValue; diff --git a/integration-tests/jpa-h2/src/test/java/io/quarkus/example/test/JPAFunctionalityInGraalITCase.java b/integration-tests/jpa-h2/src/test/java/io/quarkus/it/jpa/h2/JPAFunctionalityInGraalITCase.java similarity index 85% rename from integration-tests/jpa-h2/src/test/java/io/quarkus/example/test/JPAFunctionalityInGraalITCase.java rename to integration-tests/jpa-h2/src/test/java/io/quarkus/it/jpa/h2/JPAFunctionalityInGraalITCase.java index 31ac037685b3c..7ef56d3b8ad6b 100644 --- a/integration-tests/jpa-h2/src/test/java/io/quarkus/example/test/JPAFunctionalityInGraalITCase.java +++ b/integration-tests/jpa-h2/src/test/java/io/quarkus/it/jpa/h2/JPAFunctionalityInGraalITCase.java @@ -1,4 +1,4 @@ -package io.quarkus.example.test; +package io.quarkus.it.jpa.h2; import io.quarkus.test.junit.SubstrateTest; diff --git a/integration-tests/jpa-h2/src/test/java/io/quarkus/example/test/JPAFunctionalityTest.java b/integration-tests/jpa-h2/src/test/java/io/quarkus/it/jpa/h2/JPAFunctionalityTest.java similarity index 94% rename from integration-tests/jpa-h2/src/test/java/io/quarkus/example/test/JPAFunctionalityTest.java rename to integration-tests/jpa-h2/src/test/java/io/quarkus/it/jpa/h2/JPAFunctionalityTest.java index 90c7f5b3a1c3b..a13d79e2f7024 100644 --- a/integration-tests/jpa-h2/src/test/java/io/quarkus/example/test/JPAFunctionalityTest.java +++ b/integration-tests/jpa-h2/src/test/java/io/quarkus/it/jpa/h2/JPAFunctionalityTest.java @@ -1,4 +1,4 @@ -package io.quarkus.example.test; +package io.quarkus.it.jpa.h2; import static org.hamcrest.Matchers.is; diff --git a/integration-tests/main/src/test/java/io/quarkus/example/test/TestResources.java b/integration-tests/jpa-h2/src/test/java/io/quarkus/it/jpa/h2/TestResources.java similarity index 95% rename from integration-tests/main/src/test/java/io/quarkus/example/test/TestResources.java rename to integration-tests/jpa-h2/src/test/java/io/quarkus/it/jpa/h2/TestResources.java index 06653064904e9..93572c37777e5 100644 --- a/integration-tests/main/src/test/java/io/quarkus/example/test/TestResources.java +++ b/integration-tests/jpa-h2/src/test/java/io/quarkus/it/jpa/h2/TestResources.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.test; +package io.quarkus.it.jpa.h2; import io.quarkus.test.common.QuarkusTestResource; import io.quarkus.test.h2.H2DatabaseTestResource; diff --git a/integration-tests/jpa-mariadb/pom.xml b/integration-tests/jpa-mariadb/pom.xml index a471fc1cd5d45..3500eb9eeb209 100644 --- a/integration-tests/jpa-mariadb/pom.xml +++ b/integration-tests/jpa-mariadb/pom.xml @@ -38,17 +38,14 @@ io.quarkus quarkus-resteasy - provided io.quarkus quarkus-hibernate-orm - provided io.quarkus quarkus-jdbc-mariadb - provided diff --git a/integration-tests/jpa-mariadb/src/main/java/io/quarkus/example/jpa/mariadb/Address.java b/integration-tests/jpa-mariadb/src/main/java/io/quarkus/it/jpa/mariadb/Address.java similarity index 94% rename from integration-tests/jpa-mariadb/src/main/java/io/quarkus/example/jpa/mariadb/Address.java rename to integration-tests/jpa-mariadb/src/main/java/io/quarkus/it/jpa/mariadb/Address.java index 07c391141f850..fc56da5d3c8a1 100644 --- a/integration-tests/jpa-mariadb/src/main/java/io/quarkus/example/jpa/mariadb/Address.java +++ b/integration-tests/jpa-mariadb/src/main/java/io/quarkus/it/jpa/mariadb/Address.java @@ -1,4 +1,4 @@ -package io.quarkus.example.jpa.mariadb; +package io.quarkus.it.jpa.mariadb; import javax.persistence.Entity; import javax.persistence.GeneratedValue; diff --git a/integration-tests/jpa-mariadb/src/main/java/io/quarkus/example/jpa/mariadb/JPAFunctionalityTestEndpoint.java b/integration-tests/jpa-mariadb/src/main/java/io/quarkus/it/jpa/mariadb/JPAFunctionalityTestEndpoint.java similarity index 99% rename from integration-tests/jpa-mariadb/src/main/java/io/quarkus/example/jpa/mariadb/JPAFunctionalityTestEndpoint.java rename to integration-tests/jpa-mariadb/src/main/java/io/quarkus/it/jpa/mariadb/JPAFunctionalityTestEndpoint.java index ecda37b647f3b..0b5482b15a669 100644 --- a/integration-tests/jpa-mariadb/src/main/java/io/quarkus/example/jpa/mariadb/JPAFunctionalityTestEndpoint.java +++ b/integration-tests/jpa-mariadb/src/main/java/io/quarkus/it/jpa/mariadb/JPAFunctionalityTestEndpoint.java @@ -1,4 +1,4 @@ -package io.quarkus.example.jpa.mariadb; +package io.quarkus.it.jpa.mariadb; import java.io.IOException; import java.io.PrintWriter; diff --git a/integration-tests/jpa-mariadb/src/main/java/io/quarkus/example/jpa/mariadb/Person.java b/integration-tests/jpa-mariadb/src/main/java/io/quarkus/it/jpa/mariadb/Person.java similarity index 97% rename from integration-tests/jpa-mariadb/src/main/java/io/quarkus/example/jpa/mariadb/Person.java rename to integration-tests/jpa-mariadb/src/main/java/io/quarkus/it/jpa/mariadb/Person.java index 868675d4a45cc..c88c645e89721 100644 --- a/integration-tests/jpa-mariadb/src/main/java/io/quarkus/example/jpa/mariadb/Person.java +++ b/integration-tests/jpa-mariadb/src/main/java/io/quarkus/it/jpa/mariadb/Person.java @@ -1,4 +1,4 @@ -package io.quarkus.example.jpa.mariadb; +package io.quarkus.it.jpa.mariadb; import javax.persistence.CascadeType; import javax.persistence.Entity; diff --git a/integration-tests/jpa-postgresql/src/test/java/io/quarkus/example/test/JPAFunctionalityInGraalITCase.java b/integration-tests/jpa-mariadb/src/test/java/io/quarkus/it/jpa/mariadb/JPAFunctionalityInGraalITCase.java similarity index 95% rename from integration-tests/jpa-postgresql/src/test/java/io/quarkus/example/test/JPAFunctionalityInGraalITCase.java rename to integration-tests/jpa-mariadb/src/test/java/io/quarkus/it/jpa/mariadb/JPAFunctionalityInGraalITCase.java index eb4c1d2f50a37..59bf9d3d4b298 100644 --- a/integration-tests/jpa-postgresql/src/test/java/io/quarkus/example/test/JPAFunctionalityInGraalITCase.java +++ b/integration-tests/jpa-mariadb/src/test/java/io/quarkus/it/jpa/mariadb/JPAFunctionalityInGraalITCase.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.test; +package io.quarkus.it.jpa.mariadb; import io.quarkus.test.junit.SubstrateTest; diff --git a/integration-tests/jpa-mariadb/src/test/java/io/quarkus/example/test/JPAFunctionalityTest.java b/integration-tests/jpa-mariadb/src/test/java/io/quarkus/it/jpa/mariadb/JPAFunctionalityTest.java similarity index 96% rename from integration-tests/jpa-mariadb/src/test/java/io/quarkus/example/test/JPAFunctionalityTest.java rename to integration-tests/jpa-mariadb/src/test/java/io/quarkus/it/jpa/mariadb/JPAFunctionalityTest.java index 75e61a04cce34..364d0a94b479d 100644 --- a/integration-tests/jpa-mariadb/src/test/java/io/quarkus/example/test/JPAFunctionalityTest.java +++ b/integration-tests/jpa-mariadb/src/test/java/io/quarkus/it/jpa/mariadb/JPAFunctionalityTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.test; +package io.quarkus.it.jpa.mariadb; import static org.hamcrest.Matchers.is; diff --git a/integration-tests/jpa-mssql/pom.xml b/integration-tests/jpa-mssql/pom.xml index 7ce2ec6c523a9..b0745798e784b 100644 --- a/integration-tests/jpa-mssql/pom.xml +++ b/integration-tests/jpa-mssql/pom.xml @@ -40,19 +40,16 @@ io.quarkus quarkus-hibernate-orm - provided io.quarkus quarkus-jdbc-mssql - provided io.quarkus quarkus-resteasy - provided diff --git a/integration-tests/jpa-mssql/src/main/java/io/quarkus/example/jpamssql/JPAFunctionalityTestEndpoint.java b/integration-tests/jpa-mssql/src/main/java/io/quarkus/it/jpa/mssql/JPAFunctionalityTestEndpoint.java similarity index 99% rename from integration-tests/jpa-mssql/src/main/java/io/quarkus/example/jpamssql/JPAFunctionalityTestEndpoint.java rename to integration-tests/jpa-mssql/src/main/java/io/quarkus/it/jpa/mssql/JPAFunctionalityTestEndpoint.java index bb57d7588dd72..bd47cd3ae17a9 100644 --- a/integration-tests/jpa-mssql/src/main/java/io/quarkus/example/jpamssql/JPAFunctionalityTestEndpoint.java +++ b/integration-tests/jpa-mssql/src/main/java/io/quarkus/it/jpa/mssql/JPAFunctionalityTestEndpoint.java @@ -1,4 +1,4 @@ -package io.quarkus.example.jpamssql; +package io.quarkus.it.jpa.mssql; import java.io.IOException; import java.io.PrintWriter; diff --git a/integration-tests/jpa-h2/src/main/java/io/quarkus/example/jpah2/Person.java b/integration-tests/jpa-mssql/src/main/java/io/quarkus/it/jpa/mssql/Person.java similarity index 97% rename from integration-tests/jpa-h2/src/main/java/io/quarkus/example/jpah2/Person.java rename to integration-tests/jpa-mssql/src/main/java/io/quarkus/it/jpa/mssql/Person.java index 58873e9108e1d..c88571ec5a38e 100644 --- a/integration-tests/jpa-h2/src/main/java/io/quarkus/example/jpah2/Person.java +++ b/integration-tests/jpa-mssql/src/main/java/io/quarkus/it/jpa/mssql/Person.java @@ -1,4 +1,4 @@ -package io.quarkus.example.jpah2; +package io.quarkus.it.jpa.mssql; import javax.persistence.CascadeType; import javax.persistence.Entity; diff --git a/integration-tests/jpa-mssql/src/main/java/io/quarkus/example/jpamssql/SequencedAddress.java b/integration-tests/jpa-mssql/src/main/java/io/quarkus/it/jpa/mssql/SequencedAddress.java similarity index 95% rename from integration-tests/jpa-mssql/src/main/java/io/quarkus/example/jpamssql/SequencedAddress.java rename to integration-tests/jpa-mssql/src/main/java/io/quarkus/it/jpa/mssql/SequencedAddress.java index 0e61aec2a3d8b..23fe24839efac 100644 --- a/integration-tests/jpa-mssql/src/main/java/io/quarkus/example/jpamssql/SequencedAddress.java +++ b/integration-tests/jpa-mssql/src/main/java/io/quarkus/it/jpa/mssql/SequencedAddress.java @@ -1,4 +1,4 @@ -package io.quarkus.example.jpamssql; +package io.quarkus.it.jpa.mssql; import javax.persistence.Entity; import javax.persistence.GeneratedValue; diff --git a/integration-tests/jpa-mssql/src/test/java/io/quarkus/example/test/JPAFunctionalityInGraalITCase.java b/integration-tests/jpa-mssql/src/test/java/io/quarkus/it/jpa/mssql/JPAFunctionalityInGraalITCase.java similarity index 85% rename from integration-tests/jpa-mssql/src/test/java/io/quarkus/example/test/JPAFunctionalityInGraalITCase.java rename to integration-tests/jpa-mssql/src/test/java/io/quarkus/it/jpa/mssql/JPAFunctionalityInGraalITCase.java index 31ac037685b3c..4e87aed73e86e 100644 --- a/integration-tests/jpa-mssql/src/test/java/io/quarkus/example/test/JPAFunctionalityInGraalITCase.java +++ b/integration-tests/jpa-mssql/src/test/java/io/quarkus/it/jpa/mssql/JPAFunctionalityInGraalITCase.java @@ -1,4 +1,4 @@ -package io.quarkus.example.test; +package io.quarkus.it.jpa.mssql; import io.quarkus.test.junit.SubstrateTest; diff --git a/integration-tests/jpa-mssql/src/test/java/io/quarkus/example/test/JPAFunctionalityTest.java b/integration-tests/jpa-mssql/src/test/java/io/quarkus/it/jpa/mssql/JPAFunctionalityTest.java similarity index 95% rename from integration-tests/jpa-mssql/src/test/java/io/quarkus/example/test/JPAFunctionalityTest.java rename to integration-tests/jpa-mssql/src/test/java/io/quarkus/it/jpa/mssql/JPAFunctionalityTest.java index 65644f21a3989..ed6c835f563cf 100644 --- a/integration-tests/jpa-mssql/src/test/java/io/quarkus/example/test/JPAFunctionalityTest.java +++ b/integration-tests/jpa-mssql/src/test/java/io/quarkus/it/jpa/mssql/JPAFunctionalityTest.java @@ -1,4 +1,4 @@ -package io.quarkus.example.test; +package io.quarkus.it.jpa.mssql; import static org.hamcrest.Matchers.is; diff --git a/integration-tests/jpa-postgresql/pom.xml b/integration-tests/jpa-postgresql/pom.xml index 65b37c63039a4..cc211e9ae033b 100644 --- a/integration-tests/jpa-postgresql/pom.xml +++ b/integration-tests/jpa-postgresql/pom.xml @@ -33,17 +33,14 @@ io.quarkus quarkus-resteasy - provided io.quarkus quarkus-hibernate-orm - provided io.quarkus quarkus-jdbc-postgresql - provided diff --git a/integration-tests/jpa-postgresql/src/main/java/io/quarkus/example/jpa/Address.java b/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/Address.java similarity index 97% rename from integration-tests/jpa-postgresql/src/main/java/io/quarkus/example/jpa/Address.java rename to integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/Address.java index baf2ba24ce982..34c2e32673f45 100644 --- a/integration-tests/jpa-postgresql/src/main/java/io/quarkus/example/jpa/Address.java +++ b/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/Address.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.jpa; +package io.quarkus.it.jpa.postgresql; import javax.persistence.Embeddable; diff --git a/integration-tests/jpa-postgresql/src/main/java/io/quarkus/example/jpa/Animal.java b/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/Animal.java similarity index 95% rename from integration-tests/jpa-postgresql/src/main/java/io/quarkus/example/jpa/Animal.java rename to integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/Animal.java index e7be38dc13ece..22d6bd26cf11f 100644 --- a/integration-tests/jpa-postgresql/src/main/java/io/quarkus/example/jpa/Animal.java +++ b/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/Animal.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.jpa; +package io.quarkus.it.jpa.postgresql; /** * @author Emmanuel Bernard emmanuel@hibernate.org diff --git a/integration-tests/jpa-postgresql/src/main/java/io/quarkus/example/jpa/Customer.java b/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/Customer.java similarity index 97% rename from integration-tests/jpa-postgresql/src/main/java/io/quarkus/example/jpa/Customer.java rename to integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/Customer.java index 6c347a4dab5ca..a8e4e5182f8a1 100644 --- a/integration-tests/jpa-postgresql/src/main/java/io/quarkus/example/jpa/Customer.java +++ b/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/Customer.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.jpa; +package io.quarkus.it.jpa.postgresql; import javax.persistence.Embedded; import javax.persistence.Entity; diff --git a/integration-tests/jpa-postgresql/src/main/java/io/quarkus/example/jpa/Human.java b/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/Human.java similarity index 96% rename from integration-tests/jpa-postgresql/src/main/java/io/quarkus/example/jpa/Human.java rename to integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/Human.java index e06b5f2445e9e..06e6fea1ecf09 100644 --- a/integration-tests/jpa-postgresql/src/main/java/io/quarkus/example/jpa/Human.java +++ b/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/Human.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.jpa; +package io.quarkus.it.jpa.postgresql; import javax.persistence.MappedSuperclass; diff --git a/integration-tests/jpa-postgresql/src/main/java/io/quarkus/example/jpa/JPAFunctionalityTestEndpoint.java b/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/JPAFunctionalityTestEndpoint.java similarity index 99% rename from integration-tests/jpa-postgresql/src/main/java/io/quarkus/example/jpa/JPAFunctionalityTestEndpoint.java rename to integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/JPAFunctionalityTestEndpoint.java index bfb48e4c2e271..15733044b4528 100644 --- a/integration-tests/jpa-postgresql/src/main/java/io/quarkus/example/jpa/JPAFunctionalityTestEndpoint.java +++ b/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/JPAFunctionalityTestEndpoint.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.jpa; +package io.quarkus.it.jpa.postgresql; import java.io.IOException; import java.io.PrintWriter; diff --git a/integration-tests/jpa-postgresql/src/main/java/io/quarkus/example/jpa/JPATestReflectionEndpoint.java b/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/JPATestReflectionEndpoint.java similarity index 89% rename from integration-tests/jpa-postgresql/src/main/java/io/quarkus/example/jpa/JPATestReflectionEndpoint.java rename to integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/JPATestReflectionEndpoint.java index e0f598d19dbbb..9ff0cb9bf881e 100644 --- a/integration-tests/jpa-postgresql/src/main/java/io/quarkus/example/jpa/JPATestReflectionEndpoint.java +++ b/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/JPATestReflectionEndpoint.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.jpa; +package io.quarkus.it.jpa.postgresql; import java.io.IOException; import java.io.PrintWriter; @@ -39,8 +39,9 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO makeSureEntitiesAreAccessibleViaReflection(resp); makeSureNonAnnotatedEmbeddableAreAccessibleViaReflection(resp); makeSureAnnotatedEmbeddableAreAccessibleViaReflection(resp); - makeSureClassAreAccessibleViaReflection("io.quarkus.example.jpa.Human", "Unable to enlist @MappedSuperclass", resp); - makeSureClassAreAccessibleViaReflection("io.quarkus.example.jpa.Animal", "Unable to enlist entity superclass", resp); + String packageName = this.getClass().getPackage().getName(); + makeSureClassAreAccessibleViaReflection(packageName + ".Human", "Unable to enlist @MappedSuperclass", resp); + makeSureClassAreAccessibleViaReflection(packageName + ".Animal", "Unable to enlist entity superclass", resp); resp.getWriter().write("OK"); } @@ -58,7 +59,7 @@ private void makeSureClassAreAccessibleViaReflection(String className, String er private void makeSureEntitiesAreAccessibleViaReflection(HttpServletResponse resp) throws IOException { try { - String className = getTrickedClassName(io.quarkus.example.jpa.Customer.class.getName()); + String className = getTrickedClassName(Customer.class.getName()); Class custClass = Class.forName(className); Object instance = custClass.getDeclaredConstructor().newInstance(); @@ -80,7 +81,7 @@ private void makeSureEntitiesAreAccessibleViaReflection(HttpServletResponse resp private void makeSureAnnotatedEmbeddableAreAccessibleViaReflection(HttpServletResponse resp) throws IOException { try { - String className = getTrickedClassName(io.quarkus.example.jpa.WorkAddress.class.getName()); + String className = getTrickedClassName(WorkAddress.class.getName()); Class custClass = Class.forName(className); Object instance = custClass.getDeclaredConstructor().newInstance(); @@ -97,7 +98,7 @@ private void makeSureAnnotatedEmbeddableAreAccessibleViaReflection(HttpServletRe private void makeSureNonAnnotatedEmbeddableAreAccessibleViaReflection(HttpServletResponse resp) throws IOException { try { - String className = getTrickedClassName(io.quarkus.example.jpa.Address.class.getName()); + String className = getTrickedClassName(Address.class.getName()); Class custClass = Class.forName(className); Object instance = custClass.getDeclaredConstructor().newInstance(); @@ -114,7 +115,7 @@ private void makeSureNonAnnotatedEmbeddableAreAccessibleViaReflection(HttpServle private void makeSureNonEntityAreDCE(HttpServletResponse resp) { try { - String className = getTrickedClassName(io.quarkus.example.jpa.NotAnEntityNotReferenced.class.getName()); + String className = getTrickedClassName(NotAnEntityNotReferenced.class.getName()); Class custClass = Class.forName(className); resp.getWriter().write("Should not be able to find a non referenced non entity class"); diff --git a/integration-tests/main/src/main/java/io/quarkus/example/jpa/NotAnEntityNotReferenced.java b/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/NotAnEntityNotReferenced.java similarity index 95% rename from integration-tests/main/src/main/java/io/quarkus/example/jpa/NotAnEntityNotReferenced.java rename to integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/NotAnEntityNotReferenced.java index d6a6280cf5de6..1286a8036f39b 100644 --- a/integration-tests/main/src/main/java/io/quarkus/example/jpa/NotAnEntityNotReferenced.java +++ b/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/NotAnEntityNotReferenced.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.jpa; +package io.quarkus.it.jpa.postgresql; /** * Should not be referenced by the code diff --git a/integration-tests/jpa-postgresql/src/main/java/io/quarkus/example/jpa/Person.java b/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/Person.java similarity index 98% rename from integration-tests/jpa-postgresql/src/main/java/io/quarkus/example/jpa/Person.java rename to integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/Person.java index da07cdb4a7f90..440ea6603ecc7 100644 --- a/integration-tests/jpa-postgresql/src/main/java/io/quarkus/example/jpa/Person.java +++ b/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/Person.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.jpa; +package io.quarkus.it.jpa.postgresql; import javax.persistence.CascadeType; import javax.persistence.Entity; diff --git a/integration-tests/jpa-postgresql/src/main/java/io/quarkus/example/jpa/SequencedAddress.java b/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/SequencedAddress.java similarity index 97% rename from integration-tests/jpa-postgresql/src/main/java/io/quarkus/example/jpa/SequencedAddress.java rename to integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/SequencedAddress.java index 4f6724bde26c7..138c4251e8152 100644 --- a/integration-tests/jpa-postgresql/src/main/java/io/quarkus/example/jpa/SequencedAddress.java +++ b/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/SequencedAddress.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.jpa; +package io.quarkus.it.jpa.postgresql; import javax.persistence.Entity; import javax.persistence.GeneratedValue; diff --git a/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/example/panache/Status.java b/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/Status.java similarity index 90% rename from integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/example/panache/Status.java rename to integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/Status.java index ad5fde3ee3f60..ac0e0e814003f 100644 --- a/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/example/panache/Status.java +++ b/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/Status.java @@ -14,8 +14,9 @@ * limitations under the License. */ -package io.quarkus.example.panache; +package io.quarkus.it.jpa.postgresql; public enum Status { - LIVING, DECEASED + LIVING, + DECEASED } diff --git a/integration-tests/jpa-postgresql/src/main/java/io/quarkus/example/jpa/WorkAddress.java b/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/WorkAddress.java similarity index 96% rename from integration-tests/jpa-postgresql/src/main/java/io/quarkus/example/jpa/WorkAddress.java rename to integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/WorkAddress.java index 20cf00e5faddb..deeb8e749e8ec 100644 --- a/integration-tests/jpa-postgresql/src/main/java/io/quarkus/example/jpa/WorkAddress.java +++ b/integration-tests/jpa-postgresql/src/main/java/io/quarkus/it/jpa/postgresql/WorkAddress.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.jpa; +package io.quarkus.it.jpa.postgresql; import javax.persistence.Embeddable; diff --git a/integration-tests/jpa-mariadb/src/test/java/io/quarkus/example/test/JPAFunctionalityInGraalITCase.java b/integration-tests/jpa-postgresql/src/test/java/io/quarkus/it/jpa/postgresql/JPAFunctionalityInGraalITCase.java similarity index 95% rename from integration-tests/jpa-mariadb/src/test/java/io/quarkus/example/test/JPAFunctionalityInGraalITCase.java rename to integration-tests/jpa-postgresql/src/test/java/io/quarkus/it/jpa/postgresql/JPAFunctionalityInGraalITCase.java index eb4c1d2f50a37..fa13cf5362d95 100644 --- a/integration-tests/jpa-mariadb/src/test/java/io/quarkus/example/test/JPAFunctionalityInGraalITCase.java +++ b/integration-tests/jpa-postgresql/src/test/java/io/quarkus/it/jpa/postgresql/JPAFunctionalityInGraalITCase.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.test; +package io.quarkus.it.jpa.postgresql; import io.quarkus.test.junit.SubstrateTest; diff --git a/integration-tests/jpa-postgresql/src/test/java/io/quarkus/example/test/JPAFunctionalityTest.java b/integration-tests/jpa-postgresql/src/test/java/io/quarkus/it/jpa/postgresql/JPAFunctionalityTest.java similarity index 96% rename from integration-tests/jpa-postgresql/src/test/java/io/quarkus/example/test/JPAFunctionalityTest.java rename to integration-tests/jpa-postgresql/src/test/java/io/quarkus/it/jpa/postgresql/JPAFunctionalityTest.java index ed4ff587063bf..5ea2293d37724 100644 --- a/integration-tests/jpa-postgresql/src/test/java/io/quarkus/example/test/JPAFunctionalityTest.java +++ b/integration-tests/jpa-postgresql/src/test/java/io/quarkus/it/jpa/postgresql/JPAFunctionalityTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.test; +package io.quarkus.it.jpa.postgresql; import static org.hamcrest.Matchers.is; diff --git a/integration-tests/jpa-postgresql/src/test/java/io/quarkus/example/test/JPAReflectionInGraalITCase.java b/integration-tests/jpa-postgresql/src/test/java/io/quarkus/it/jpa/postgresql/JPAReflectionInGraalITCase.java similarity index 96% rename from integration-tests/jpa-postgresql/src/test/java/io/quarkus/example/test/JPAReflectionInGraalITCase.java rename to integration-tests/jpa-postgresql/src/test/java/io/quarkus/it/jpa/postgresql/JPAReflectionInGraalITCase.java index 8b2e287601ba8..9099a87ae7a5c 100644 --- a/integration-tests/jpa-postgresql/src/test/java/io/quarkus/example/test/JPAReflectionInGraalITCase.java +++ b/integration-tests/jpa-postgresql/src/test/java/io/quarkus/it/jpa/postgresql/JPAReflectionInGraalITCase.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.test; +package io.quarkus.it.jpa.postgresql; import static org.hamcrest.Matchers.is; diff --git a/integration-tests/jpa/pom.xml b/integration-tests/jpa/pom.xml index 4ad89ddb3e274..8af4ab180f22a 100644 --- a/integration-tests/jpa/pom.xml +++ b/integration-tests/jpa/pom.xml @@ -17,17 +17,14 @@ io.quarkus quarkus-resteasy - provided io.quarkus quarkus-hibernate-orm - provided io.quarkus quarkus-jdbc-h2 - provided diff --git a/integration-tests/jpa/src/main/java/io/quarkus/jpa/tests/configurationless/CRUDResource.java b/integration-tests/jpa/src/main/java/io/quarkus/it/jpa/configurationless/CRUDResource.java similarity index 98% rename from integration-tests/jpa/src/main/java/io/quarkus/jpa/tests/configurationless/CRUDResource.java rename to integration-tests/jpa/src/main/java/io/quarkus/it/jpa/configurationless/CRUDResource.java index 2fa402edd10e5..70de7897869e4 100644 --- a/integration-tests/jpa/src/main/java/io/quarkus/jpa/tests/configurationless/CRUDResource.java +++ b/integration-tests/jpa/src/main/java/io/quarkus/it/jpa/configurationless/CRUDResource.java @@ -1,4 +1,4 @@ -package io.quarkus.jpa.tests.configurationless; +package io.quarkus.it.jpa.configurationless; import java.util.HashMap; import java.util.Map; diff --git a/integration-tests/jpa/src/main/java/io/quarkus/jpa/tests/configurationless/Cake.java b/integration-tests/jpa/src/main/java/io/quarkus/it/jpa/configurationless/Cake.java similarity index 93% rename from integration-tests/jpa/src/main/java/io/quarkus/jpa/tests/configurationless/Cake.java rename to integration-tests/jpa/src/main/java/io/quarkus/it/jpa/configurationless/Cake.java index 08ea2f0724558..25c68153e1b56 100644 --- a/integration-tests/jpa/src/main/java/io/quarkus/jpa/tests/configurationless/Cake.java +++ b/integration-tests/jpa/src/main/java/io/quarkus/it/jpa/configurationless/Cake.java @@ -1,4 +1,4 @@ -package io.quarkus.jpa.tests.configurationless; +package io.quarkus.it.jpa.configurationless; import javax.persistence.Entity; import javax.persistence.GeneratedValue; diff --git a/integration-tests/jpa/src/main/java/io/quarkus/jpa/tests/configurationless/ConfigurationlessApp.java b/integration-tests/jpa/src/main/java/io/quarkus/it/jpa/configurationless/ConfigurationlessApp.java similarity index 82% rename from integration-tests/jpa/src/main/java/io/quarkus/jpa/tests/configurationless/ConfigurationlessApp.java rename to integration-tests/jpa/src/main/java/io/quarkus/it/jpa/configurationless/ConfigurationlessApp.java index ac014c104a708..d0c8f04159548 100644 --- a/integration-tests/jpa/src/main/java/io/quarkus/jpa/tests/configurationless/ConfigurationlessApp.java +++ b/integration-tests/jpa/src/main/java/io/quarkus/it/jpa/configurationless/ConfigurationlessApp.java @@ -1,4 +1,4 @@ -package io.quarkus.jpa.tests.configurationless; +package io.quarkus.it.jpa.configurationless; import javax.ws.rs.ApplicationPath; import javax.ws.rs.core.Application; diff --git a/integration-tests/jpa/src/main/java/io/quarkus/jpa/tests/configurationless/Gift.java b/integration-tests/jpa/src/main/java/io/quarkus/it/jpa/configurationless/Gift.java similarity index 92% rename from integration-tests/jpa/src/main/java/io/quarkus/jpa/tests/configurationless/Gift.java rename to integration-tests/jpa/src/main/java/io/quarkus/it/jpa/configurationless/Gift.java index a4b67dcae12c3..a3d125095ff0b 100644 --- a/integration-tests/jpa/src/main/java/io/quarkus/jpa/tests/configurationless/Gift.java +++ b/integration-tests/jpa/src/main/java/io/quarkus/it/jpa/configurationless/Gift.java @@ -1,4 +1,4 @@ -package io.quarkus.jpa.tests.configurationless; +package io.quarkus.it.jpa.configurationless; import javax.persistence.Entity; import javax.persistence.GeneratedValue; diff --git a/integration-tests/jpa/src/main/java/io/quarkus/jpa/tests/configurationless/StartupCakeManager.java b/integration-tests/jpa/src/main/java/io/quarkus/it/jpa/configurationless/StartupCakeManager.java similarity index 92% rename from integration-tests/jpa/src/main/java/io/quarkus/jpa/tests/configurationless/StartupCakeManager.java rename to integration-tests/jpa/src/main/java/io/quarkus/it/jpa/configurationless/StartupCakeManager.java index 6645be0d22304..5d310d66fd756 100644 --- a/integration-tests/jpa/src/main/java/io/quarkus/jpa/tests/configurationless/StartupCakeManager.java +++ b/integration-tests/jpa/src/main/java/io/quarkus/it/jpa/configurationless/StartupCakeManager.java @@ -1,4 +1,4 @@ -package io.quarkus.jpa.tests.configurationless; +package io.quarkus.it.jpa.configurationless; import javax.enterprise.context.Dependent; import javax.enterprise.event.Observes; diff --git a/integration-tests/jpa/src/test/java/io/quarkus/jpa/tests/configurationless/JPAConfigurationlessTest.java b/integration-tests/jpa/src/test/java/io/quarkus/it/jpa/configurationless/JPAConfigurationlessTest.java similarity index 92% rename from integration-tests/jpa/src/test/java/io/quarkus/jpa/tests/configurationless/JPAConfigurationlessTest.java rename to integration-tests/jpa/src/test/java/io/quarkus/it/jpa/configurationless/JPAConfigurationlessTest.java index 05ed6ae0bbddf..8db415be9da60 100644 --- a/integration-tests/jpa/src/test/java/io/quarkus/jpa/tests/configurationless/JPAConfigurationlessTest.java +++ b/integration-tests/jpa/src/test/java/io/quarkus/it/jpa/configurationless/JPAConfigurationlessTest.java @@ -1,4 +1,4 @@ -package io.quarkus.jpa.tests.configurationless; +package io.quarkus.it.jpa.configurationless; import static org.hamcrest.core.StringContains.containsString; diff --git a/integration-tests/jpa/src/test/java/io/quarkus/jpa/tests/configurationless/JPAConfigurationlessTestInGraalITCase.java b/integration-tests/jpa/src/test/java/io/quarkus/it/jpa/configurationless/JPAConfigurationlessTestInGraalITCase.java similarity index 75% rename from integration-tests/jpa/src/test/java/io/quarkus/jpa/tests/configurationless/JPAConfigurationlessTestInGraalITCase.java rename to integration-tests/jpa/src/test/java/io/quarkus/it/jpa/configurationless/JPAConfigurationlessTestInGraalITCase.java index 5c1f48e26952a..828720c82746c 100644 --- a/integration-tests/jpa/src/test/java/io/quarkus/jpa/tests/configurationless/JPAConfigurationlessTestInGraalITCase.java +++ b/integration-tests/jpa/src/test/java/io/quarkus/it/jpa/configurationless/JPAConfigurationlessTestInGraalITCase.java @@ -1,4 +1,4 @@ -package io.quarkus.jpa.tests.configurationless; +package io.quarkus.it.jpa.configurationless; import io.quarkus.test.junit.SubstrateTest; diff --git a/integration-tests/jpa/src/test/java/io/quarkus/jpa/tests/configurationless/JPALoadScriptTest.java b/integration-tests/jpa/src/test/java/io/quarkus/it/jpa/configurationless/JPALoadScriptTest.java similarity index 90% rename from integration-tests/jpa/src/test/java/io/quarkus/jpa/tests/configurationless/JPALoadScriptTest.java rename to integration-tests/jpa/src/test/java/io/quarkus/it/jpa/configurationless/JPALoadScriptTest.java index 32ce6000926b4..e812bf396fb5e 100644 --- a/integration-tests/jpa/src/test/java/io/quarkus/jpa/tests/configurationless/JPALoadScriptTest.java +++ b/integration-tests/jpa/src/test/java/io/quarkus/it/jpa/configurationless/JPALoadScriptTest.java @@ -1,4 +1,4 @@ -package io.quarkus.jpa.tests.configurationless; +package io.quarkus.it.jpa.configurationless; import static org.hamcrest.core.StringContains.containsString; diff --git a/integration-tests/jpa/src/test/java/io/quarkus/jpa/tests/configurationless/JPALoadScriptTestInGraalITCase.java b/integration-tests/jpa/src/test/java/io/quarkus/it/jpa/configurationless/JPALoadScriptTestInGraalITCase.java similarity index 80% rename from integration-tests/jpa/src/test/java/io/quarkus/jpa/tests/configurationless/JPALoadScriptTestInGraalITCase.java rename to integration-tests/jpa/src/test/java/io/quarkus/it/jpa/configurationless/JPALoadScriptTestInGraalITCase.java index 5b469655aea44..22bdf3fcaa991 100644 --- a/integration-tests/jpa/src/test/java/io/quarkus/jpa/tests/configurationless/JPALoadScriptTestInGraalITCase.java +++ b/integration-tests/jpa/src/test/java/io/quarkus/it/jpa/configurationless/JPALoadScriptTestInGraalITCase.java @@ -1,4 +1,4 @@ -package io.quarkus.jpa.tests.configurationless; +package io.quarkus.it.jpa.configurationless; import io.quarkus.test.junit.SubstrateTest; diff --git a/integration-tests/jpa/src/test/java/io/quarkus/jpa/tests/configurationless/JPAOnStartupTest.java b/integration-tests/jpa/src/test/java/io/quarkus/it/jpa/configurationless/JPAOnStartupTest.java similarity index 90% rename from integration-tests/jpa/src/test/java/io/quarkus/jpa/tests/configurationless/JPAOnStartupTest.java rename to integration-tests/jpa/src/test/java/io/quarkus/it/jpa/configurationless/JPAOnStartupTest.java index 418b8db8448ce..a945174ba6c7c 100644 --- a/integration-tests/jpa/src/test/java/io/quarkus/jpa/tests/configurationless/JPAOnStartupTest.java +++ b/integration-tests/jpa/src/test/java/io/quarkus/it/jpa/configurationless/JPAOnStartupTest.java @@ -1,4 +1,4 @@ -package io.quarkus.jpa.tests.configurationless; +package io.quarkus.it.jpa.configurationless; import static org.hamcrest.core.StringContains.containsString; diff --git a/integration-tests/jpa/src/test/java/io/quarkus/it/jpa/configurationless/TestResources.java b/integration-tests/jpa/src/test/java/io/quarkus/it/jpa/configurationless/TestResources.java new file mode 100644 index 0000000000000..f82ccb9cbbdfc --- /dev/null +++ b/integration-tests/jpa/src/test/java/io/quarkus/it/jpa/configurationless/TestResources.java @@ -0,0 +1,24 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Licensed 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.it.jpa.configurationless; + +import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.h2.H2DatabaseTestResource; + +@QuarkusTestResource(H2DatabaseTestResource.class) +public class TestResources { +} diff --git a/integration-tests/keycloak/README.md b/integration-tests/keycloak/README.md new file mode 100644 index 0000000000000..0fb20fef25488 --- /dev/null +++ b/integration-tests/keycloak/README.md @@ -0,0 +1,27 @@ +# JAX-RS example using Keycloak adapter to protect resources + +## Running the tests + +By default, the tests of this module are disabled. + +To run the tests in a standard JVM with Keycloak Server started as a Docker container, you can run the following command: + +``` +mvn clean install -Dtest-keycloak -Ddocker +``` + +Additionally, you can generate a native image and run the tests for this native image by adding `-Dnative`: + +``` +mvn clean install -Dtest-keycloak -Ddocker -Dnative +``` + +If you don't want to run Keycloak Server as a Docker container, you can start your own Keycloak server. It needs to listen on the default port `8180`. + +You can then run the tests as follows (either with `-Dnative` or not): + +``` +mvn clean install -Dtest-keycloak +``` + +If you have specific requirements, you can define a specific connection URL with `-Dkeycloak.url=http://keycloak.server.domain:8180/auth`. diff --git a/integration-tests/keycloak/pom.xml b/integration-tests/keycloak/pom.xml new file mode 100644 index 0000000000000..aa3f13e65f5f8 --- /dev/null +++ b/integration-tests/keycloak/pom.xml @@ -0,0 +1,261 @@ + + + + + + quarkus-integration-tests-parent + io.quarkus + 999-SNAPSHOT + ../ + + 4.0.0 + + quarkus-integration-test-keycloak + Quarkus - Integration Tests - Keycloak + Module that contains Keycloak related tests + + + http://localhost:8180/auth + + + + + io.quarkus + quarkus-keycloak + + + io.quarkus + quarkus-resteasy-jsonb + + + + + io.quarkus + quarkus-junit5 + test + + + io.rest-assured + rest-assured + test + + + + + + + src/main/resources + true + + + + + maven-surefire-plugin + + true + + + + maven-failsafe-plugin + + true + + + + ${project.groupId} + quarkus-maven-plugin + ${project.version} + + + + build + + + + + + + + + + test-keycloak + + + test-keycloak + + + + + + maven-surefire-plugin + + false + + + + maven-failsafe-plugin + + false + + + + ${project.groupId} + quarkus-maven-plugin + + + + build + + + + + + + + + + native-image + + + native + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + verify + + + + ${project.build.directory}/${project.build.finalName}-runner + + + + + + + ${project.groupId} + quarkus-maven-plugin + + + native-image + + native-image + + + false + true + true + false + false + + ${graalvmHome} + false + false + + + + + + + + + + docker-keycloak + + + docker + + + + http://localhost:8180/auth + + + + + io.fabric8 + docker-maven-plugin + 0.28.0 + + + + quay.io/keycloak/keycloak + quarkus-test-keycloak + + + 8180:8080 + + + admin + admin + + + Keycloak: + default + cyan + + + + + direct + + 8080 + + + + + + + + true + + + + docker-start + compile + + stop + start + + + + docker-stop + post-integration-test + + stop + + + + + + + + + + + diff --git a/integration-tests/keycloak/src/main/java/io/quarkus/it/keycloak/AdminResource.java b/integration-tests/keycloak/src/main/java/io/quarkus/it/keycloak/AdminResource.java new file mode 100644 index 0000000000000..29935b72dadc0 --- /dev/null +++ b/integration-tests/keycloak/src/main/java/io/quarkus/it/keycloak/AdminResource.java @@ -0,0 +1,36 @@ +/** + * Copyright 2019 Red Hat, Inc, and individual contributors. + *

+ * Licensed 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.it.keycloak; + +import javax.annotation.security.RolesAllowed; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +/** + * @author Pedro Igor + */ +@Path("/api/admin") +public class AdminResource { + + @GET + @RolesAllowed("admin") + @Produces(MediaType.APPLICATION_JSON) + public String admin() { + return "granted"; + } +} diff --git a/integration-tests/keycloak/src/main/java/io/quarkus/it/keycloak/ConfidentialResource.java b/integration-tests/keycloak/src/main/java/io/quarkus/it/keycloak/ConfidentialResource.java new file mode 100644 index 0000000000000..09ad8eb6e4919 --- /dev/null +++ b/integration-tests/keycloak/src/main/java/io/quarkus/it/keycloak/ConfidentialResource.java @@ -0,0 +1,34 @@ +/** + * Copyright 2019 Red Hat, Inc, and individual contributors. + *

+ * Licensed 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.it.keycloak; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +/** + * @author Pedro Igor + */ +@Path("/api/confidential") +public class ConfidentialResource { + + @GET + @Produces(MediaType.APPLICATION_JSON) + public String confidential() { + return "confidential"; + } +} diff --git a/integration-tests/keycloak/src/main/java/io/quarkus/it/keycloak/PermissionResource.java b/integration-tests/keycloak/src/main/java/io/quarkus/it/keycloak/PermissionResource.java new file mode 100644 index 0000000000000..898254a624ca8 --- /dev/null +++ b/integration-tests/keycloak/src/main/java/io/quarkus/it/keycloak/PermissionResource.java @@ -0,0 +1,28 @@ +package io.quarkus.it.keycloak; + +import java.util.List; + +import javax.annotation.security.RolesAllowed; +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.keycloak.KeycloakSecurityContext; +import org.keycloak.representations.idm.authorization.Permission; + +@Path("/api/permission") +public class PermissionResource { + + @Inject + KeycloakSecurityContext keycloakSecurityContext; + + @GET + @Path("/{name}") + @RolesAllowed("user") + @Produces(MediaType.APPLICATION_JSON) + public List permissions() { + return keycloakSecurityContext.getAuthorizationContext().getPermissions(); + } +} diff --git a/integration-tests/keycloak/src/main/java/io/quarkus/it/keycloak/TestConfigResolver.java b/integration-tests/keycloak/src/main/java/io/quarkus/it/keycloak/TestConfigResolver.java new file mode 100644 index 0000000000000..a760dd38e8225 --- /dev/null +++ b/integration-tests/keycloak/src/main/java/io/quarkus/it/keycloak/TestConfigResolver.java @@ -0,0 +1,39 @@ +package io.quarkus.it.keycloak; + +import java.util.HashMap; +import java.util.Map; + +import javax.enterprise.context.ApplicationScoped; + +import org.keycloak.adapters.KeycloakConfigResolver; +import org.keycloak.adapters.KeycloakDeployment; +import org.keycloak.adapters.KeycloakDeploymentBuilder; +import org.keycloak.adapters.spi.HttpFacade; +import org.keycloak.representations.adapters.config.AdapterConfig; + +@ApplicationScoped +public class TestConfigResolver implements KeycloakConfigResolver { + @Override + public KeycloakDeployment resolve(HttpFacade.Request facade) { + String tenant = facade.getHeader("tenant"); + + // if tenant-1 disables policy enforcement + if (tenant != null && tenant.equals("tenant-1")) { + AdapterConfig adapterConfig = new AdapterConfig(); + + adapterConfig.setRealm("quarkus"); + adapterConfig.setResource("quarkus-app"); + adapterConfig.setAuthServerUrl(System.getProperty("keycloak.url", "http://localhost:8180/auth")); + adapterConfig.setBearerOnly(true); + Map credentials = new HashMap<>(); + + credentials.put("secret", "secret"); + + adapterConfig.setCredentials(credentials); + + return KeycloakDeploymentBuilder.build(adapterConfig); + } + + return null; + } +} diff --git a/integration-tests/keycloak/src/main/java/io/quarkus/it/keycloak/UsersResource.java b/integration-tests/keycloak/src/main/java/io/quarkus/it/keycloak/UsersResource.java new file mode 100644 index 0000000000000..6751c35c0d446 --- /dev/null +++ b/integration-tests/keycloak/src/main/java/io/quarkus/it/keycloak/UsersResource.java @@ -0,0 +1,56 @@ +/** + * Copyright 2019 Red Hat, Inc, and individual contributors. + *

+ * Licensed 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.it.keycloak; + +import javax.annotation.security.RolesAllowed; +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.keycloak.KeycloakSecurityContext; + +/** + * @author Pedro Igor + */ +@Path("/api/users") +public class UsersResource { + + @Inject + KeycloakSecurityContext keycloakSecurityContext; + + @GET + @Path("/me") + @RolesAllowed("user") + @Produces(MediaType.APPLICATION_JSON) + public User me() { + return new User(keycloakSecurityContext); + } + + public class User { + + private final String userName; + + User(KeycloakSecurityContext securityContext) { + this.userName = securityContext.getToken().getPreferredUsername(); + } + + public String getUserName() { + return userName; + } + } +} diff --git a/integration-tests/keycloak/src/main/resources/application.properties b/integration-tests/keycloak/src/main/resources/application.properties new file mode 100644 index 0000000000000..7f1e4ad1e315e --- /dev/null +++ b/integration-tests/keycloak/src/main/resources/application.properties @@ -0,0 +1,25 @@ +# Configuration file +quarkus.keycloak.auth-server-url=${keycloak.url} +quarkus.keycloak.realm=quarkus +quarkus.keycloak.resource=quarkus-app +quarkus.keycloak.bearer-only=true +quarkus.keycloak.credentials.secret=secret +quarkus.keycloak.policy-enforcer.enforcement-mode=PERMISSIVE +quarkus.keycloak.policy-enforcer.lazy-load-paths=true +quarkus.keycloak.policy-enforcer.claim-information-point.claims.global-claim=global-claim +quarkus.keycloak.policy-enforcer.paths.0.path=/api/a +quarkus.keycloak.policy-enforcer.paths.0.enforcement-mode=DISABLED +quarkus.keycloak.policy-enforcer.paths.1.path=/api/b +quarkus.keycloak.policy-enforcer.paths.1.enforcement-mode=ENFORCING +quarkus.keycloak.policy-enforcer.paths.1.methods.0.method=POST +quarkus.keycloak.policy-enforcer.paths.1.methods.0.scopes=read,write +quarkus.keycloak.policy-enforcer.paths.2.name=Permission Resource +quarkus.keycloak.policy-enforcer.paths.2.path=/api/permission/claims-cip +quarkus.keycloak.policy-enforcer.paths.2.claim-information-point.claims.claim-a=claim-a +quarkus.keycloak.policy-enforcer.paths.3.name=Permission Resource +quarkus.keycloak.policy-enforcer.paths.3.path=/api/permission/http-cip +quarkus.keycloak.policy-enforcer.paths.3.claim-information-point.http.claims.user-name=/userName +quarkus.keycloak.policy-enforcer.paths.3.claim-information-point.http.url=http://localhost:8081/api/users/me +quarkus.keycloak.policy-enforcer.paths.3.claim-information-point.http.method=GET +quarkus.keycloak.policy-enforcer.paths.3.claim-information-point.http.headers.Content-Type=application/x-www-form-urlencoded +quarkus.keycloak.policy-enforcer.paths.3.claim-information-point.http.headers.Authorization=Bearer {keycloak.access_token} \ No newline at end of file diff --git a/integration-tests/keycloak/src/main/resources/keycloak.json b/integration-tests/keycloak/src/main/resources/keycloak.json new file mode 100644 index 0000000000000..373bfe7e58dd3 --- /dev/null +++ b/integration-tests/keycloak/src/main/resources/keycloak.json @@ -0,0 +1,9 @@ +{ + "realm": "quarkus", + "auth-server-url": "${keycloak.url}", + "resource": "quarkus-app", + "bearer-only" : true, + "credentials": { + "secret": "secret" + } +} \ No newline at end of file diff --git a/integration-tests/keycloak/src/test/java/io/quarkus/it/keycloak/BearerTokenAuthorizationInGraalITCase.java b/integration-tests/keycloak/src/test/java/io/quarkus/it/keycloak/BearerTokenAuthorizationInGraalITCase.java new file mode 100644 index 0000000000000..dc1c40ee48ab9 --- /dev/null +++ b/integration-tests/keycloak/src/test/java/io/quarkus/it/keycloak/BearerTokenAuthorizationInGraalITCase.java @@ -0,0 +1,27 @@ +/** + * Copyright 2019 Red Hat, Inc, and individual contributors. + *

+ * Licensed 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.it.keycloak; + +import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.junit.SubstrateTest; + +/** + * @author Pedro Igor + */ +@QuarkusTestResource(KeycloakTestResource.class) +@SubstrateTest +public class BearerTokenAuthorizationInGraalITCase extends BearerTokenAuthorizationTest { +} diff --git a/integration-tests/keycloak/src/test/java/io/quarkus/it/keycloak/BearerTokenAuthorizationTest.java b/integration-tests/keycloak/src/test/java/io/quarkus/it/keycloak/BearerTokenAuthorizationTest.java new file mode 100644 index 0000000000000..2427146d8341e --- /dev/null +++ b/integration-tests/keycloak/src/test/java/io/quarkus/it/keycloak/BearerTokenAuthorizationTest.java @@ -0,0 +1,349 @@ +/** + * Copyright 2019 Red Hat, Inc, and individual contributors. + *

+ * Licensed 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.it.keycloak; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.everyItem; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; + +import org.hamcrest.Matchers; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.keycloak.representations.AccessTokenResponse; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.idm.CredentialRepresentation; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.idm.RoleRepresentation; +import org.keycloak.representations.idm.RolesRepresentation; +import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.representations.idm.authorization.PolicyRepresentation; +import org.keycloak.representations.idm.authorization.ResourceRepresentation; +import org.keycloak.representations.idm.authorization.ResourceServerRepresentation; +import org.keycloak.util.JsonSerialization; + +import io.quarkus.it.keycloak.TestConfigResolver; +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.RestAssured; + +/** + * @author Pedro Igor + */ +@QuarkusTest +public class BearerTokenAuthorizationTest { + + private static final String KEYCLOAK_SERVER_URL = System.getProperty("keycloak.url", "http://localhost:8180/auth"); + private static final String KEYCLOAK_REALM = "quarkus"; + + @BeforeAll + public static void configureKeycloakRealm() throws IOException { + RealmRepresentation realm = createRealm(KEYCLOAK_REALM); + + realm.getClients().add(createClient("quarkus-app")); + realm.getUsers().add(createUser("alice", "user")); + realm.getUsers().add(createUser("admin", "user", "admin")); + realm.getUsers().add(createUser("jdoe", "user", "confidential")); + + RestAssured + .given() + .auth().oauth2(getAdminAccessToken()) + .contentType("application/json") + .body(JsonSerialization.writeValueAsBytes(realm)) + .when() + .post(KEYCLOAK_SERVER_URL + "/admin/realms").then() + .statusCode(201); + } + + @AfterAll + public static void removeKeycloakRealm() { + RestAssured + .given() + .auth().oauth2(getAdminAccessToken()) + .when() + .delete(KEYCLOAK_SERVER_URL + "/admin/realms/" + KEYCLOAK_REALM).then().statusCode(204); + } + + private static String getAdminAccessToken() { + return RestAssured + .given() + .param("grant_type", "password") + .param("username", "admin") + .param("password", "admin") + .param("client_id", "admin-cli") + .when() + .post(KEYCLOAK_SERVER_URL + "/realms/master/protocol/openid-connect/token") + .as(AccessTokenResponse.class).getToken(); + } + + private static RealmRepresentation createRealm(String name) { + RealmRepresentation realm = new RealmRepresentation(); + + realm.setRealm(name); + realm.setEnabled(true); + realm.setUsers(new ArrayList<>()); + realm.setClients(new ArrayList<>()); + + RolesRepresentation roles = new RolesRepresentation(); + List realmRoles = new ArrayList<>(); + + roles.setRealm(realmRoles); + realm.setRoles(roles); + + realm.getRoles().getRealm().add(new RoleRepresentation("user", null, false)); + realm.getRoles().getRealm().add(new RoleRepresentation("admin", null, false)); + realm.getRoles().getRealm().add(new RoleRepresentation("confidential", null, false)); + + return realm; + } + + private static ClientRepresentation createClient(String clientId) { + ClientRepresentation client = new ClientRepresentation(); + + client.setClientId(clientId); + client.setPublicClient(false); + client.setSecret("secret"); + client.setDirectAccessGrantsEnabled(true); + client.setEnabled(true); + + client.setAuthorizationServicesEnabled(true); + + ResourceServerRepresentation authorizationSettings = new ResourceServerRepresentation(); + + authorizationSettings.setResources(new ArrayList<>()); + authorizationSettings.setPolicies(new ArrayList<>()); + + configureConfidentialResourcePermission(authorizationSettings); + configurePermissionResourcePermission(authorizationSettings); + + client.setAuthorizationSettings(authorizationSettings); + + return client; + } + + private static void configureConfidentialResourcePermission(ResourceServerRepresentation authorizationSettings) { + ResourceRepresentation resource = new ResourceRepresentation("Confidential Resource"); + + resource.setUri("/api/confidential"); + + authorizationSettings.getResources().add(resource); + + PolicyRepresentation policy = new PolicyRepresentation(); + + policy.setName("Confidential Policy"); + policy.setType("js"); + policy.setConfig(new HashMap<>()); + policy.getConfig().put("code", + "var identity = $evaluation.context.identity;\n" + + "\n" + + "if (identity.hasRealmRole(\"confidential\")) {\n" + + "$evaluation.grant();\n" + + "}"); + + authorizationSettings.getPolicies().add(policy); + + PolicyRepresentation permission = new PolicyRepresentation(); + + permission.setName("Confidential Permission"); + permission.setType("resource"); + permission.setResources(new HashSet<>()); + permission.getResources().add(resource.getName()); + permission.setPolicies(new HashSet<>()); + permission.getPolicies().add(policy.getName()); + + authorizationSettings.getPolicies().add(permission); + } + + private static void configurePermissionResourcePermission(ResourceServerRepresentation authorizationSettings) { + ResourceRepresentation resource = new ResourceRepresentation("Permission Resource"); + + resource.setUri("/api/permission"); + + authorizationSettings.getResources().add(resource); + + PolicyRepresentation policy = new PolicyRepresentation(); + + policy.setName("Permission Policy"); + policy.setType("js"); + policy.setConfig(new HashMap<>()); + policy.getConfig().put("code", "$evaluation.grant();"); + + authorizationSettings.getPolicies().add(policy); + + PolicyRepresentation permission = new PolicyRepresentation(); + + permission.setName("Permission Resource Permission"); + permission.setType("resource"); + permission.setResources(new HashSet<>()); + permission.getResources().add(resource.getName()); + permission.setPolicies(new HashSet<>()); + permission.getPolicies().add(policy.getName()); + + authorizationSettings.getPolicies().add(permission); + } + + private static UserRepresentation createUser(String username, String... realmRoles) { + UserRepresentation user = new UserRepresentation(); + + user.setUsername(username); + user.setEnabled(true); + user.setCredentials(new ArrayList<>()); + user.setRealmRoles(Arrays.asList(realmRoles)); + + CredentialRepresentation credential = new CredentialRepresentation(); + + credential.setType(CredentialRepresentation.PASSWORD); + credential.setValue(username); + credential.setTemporary(false); + + user.getCredentials().add(credential); + + return user; + } + + @Test + public void testSecureAccessSuccess() { + for (String username : Arrays.asList("alice", "jdoe", "admin")) { + RestAssured.given().auth().oauth2(getAccessToken(username)) + .when().get("/api/users/me") + .then() + .statusCode(200) + .body("userName", equalTo(username)); + } + } + + @Test + public void testAccessAdminResource() { + RestAssured.given().auth().oauth2(getAccessToken("admin")) + .when().get("/api/admin") + .then() + .statusCode(200) + .body(Matchers.containsString("granted")); + } + + @Test + public void testPermissionClaimsInformationProvider() { + RestAssured.given().auth().oauth2(getAccessToken("alice")) + .when().get("/api/permission/claims-cip") + .then() + .statusCode(200) + .body("claims", everyItem(Matchers.hasKey("claim-a"))); + RestAssured.given().auth().oauth2(getAccessToken("alice")) + .when().get("/api/permission/claims-cip") + .then() + .statusCode(200) + .body("claims", everyItem(Matchers.hasKey("global-claim"))); + RestAssured.given().auth().oauth2(getAccessToken("admin")) + .when().get("/api/permission/claims-cip") + .then() + .statusCode(200) + .body("claims", everyItem(Matchers.hasKey("global-claim"))); + } + + @Test + public void testPermissionHttpInformationProvider() { + RestAssured.given().auth().oauth2(getAccessToken("alice")) + .when().get("/api/permission/http-cip") + .then() + .statusCode(200) + .body("claims", everyItem(Matchers.hasKey("user-name"))); + } + + @Test + public void testDeniedAccessAdminResource() { + RestAssured.given().auth().oauth2(getAccessToken("alice")) + .when().get("/api/admin") + .then() + .statusCode(403); + } + + @Test + public void testDeniedAccessConfigEnforcingAccess() { + RestAssured.given().auth().oauth2(getAccessToken("alice")) + .when().get("/api/b") + .then() + .statusCode(403); + } + + @Test + public void testAllowAccessConfigDisablingEnforcer() { + RestAssured.given().auth().oauth2(getAccessToken("alice")) + .when().get("/api/a") + .then() + .statusCode(404); + } + + @Test + public void testAccessConfidentialResource() { + RestAssured.given().auth().oauth2(getAccessToken("jdoe")) + .when().get("/api/confidential") + .then() + .statusCode(200) + .body(Matchers.containsString("confidential")); + } + + /** + * This test make sure multi-tenancy is working so that applications can define their + * {@link org.keycloak.adapters.KeycloakConfigResolver} + * classes as regular CDI beans. See {@link TestConfigResolver} + */ + @Test + public void testAccessConfidentialResourceUsingTenantConfig() { + RestAssured.given().auth().oauth2(getAccessToken("alice")) + .when().header("tenant", "tenant-1").get("/api/confidential") + .then() + .statusCode(200) + .body(Matchers.containsString("confidential")); + } + + @Test + public void testDeniedAccessConfidentialResource() { + RestAssured.given().auth().oauth2(getAccessToken("alice")) + .when().get("/api/confidential") + .then() + .statusCode(403); + RestAssured.given().auth().oauth2(getAccessToken("admin")) + .when().get("/api/confidential") + .then() + .statusCode(403); + testAccessConfidentialResource(); + } + + @Test + public void testDeniedNoBearerToken() { + RestAssured.given() + .when().get("/api/users/me").then() + .statusCode(403); + } + + private String getAccessToken(String userName) { + return RestAssured + .given() + .param("grant_type", "password") + .param("username", userName) + .param("password", userName) + .param("client_id", "quarkus-app") + .param("client_secret", "secret") + .when() + .post(KEYCLOAK_SERVER_URL + "/realms/" + KEYCLOAK_REALM + "/protocol/openid-connect/token") + .as(AccessTokenResponse.class).getToken(); + } +} diff --git a/integration-tests/keycloak/src/test/java/io/quarkus/it/keycloak/KeycloakTestResource.java b/integration-tests/keycloak/src/test/java/io/quarkus/it/keycloak/KeycloakTestResource.java new file mode 100644 index 0000000000000..12efe03006421 --- /dev/null +++ b/integration-tests/keycloak/src/test/java/io/quarkus/it/keycloak/KeycloakTestResource.java @@ -0,0 +1,24 @@ +package io.quarkus.it.keycloak; + +import java.util.HashMap; +import java.util.Map; + +import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; + +public class KeycloakTestResource implements QuarkusTestResourceLifecycleManager { + @Override + public Map start() { + HashMap map = new HashMap<>(); + + // a workaround to set system properties defined when executing tests. Looks like this commit introduced an + // unexpected behavior: 3ca0b323dd1c6d80edb66136eb42be7f9bde3310 + map.put("keycloak.url", System.getProperty("keycloak.url")); + + return map; + } + + @Override + public void stop() { + + } +} diff --git a/integration-tests/main/pom.xml b/integration-tests/main/pom.xml index 888e86630867d..d4ad97ac46937 100644 --- a/integration-tests/main/pom.xml +++ b/integration-tests/main/pom.xml @@ -42,7 +42,6 @@ io.quarkus quarkus-integration-test-class-transformer - provided io.quarkus @@ -51,88 +50,65 @@ io.quarkus quarkus-smallrye-metrics - provided io.quarkus quarkus-smallrye-openapi - provided io.quarkus quarkus-smallrye-opentracing - provided - - - io.quarkus - quarkus-amazon-lambda - provided io.quarkus quarkus-arc - provided io.quarkus quarkus-smallrye-health - provided io.quarkus quarkus-smallrye-reactive-streams-operators - provided io.quarkus quarkus-hibernate-validator - provided io.quarkus quarkus-narayana-jta - provided io.quarkus quarkus-undertow-websockets - provided io.quarkus quarkus-smallrye-fault-tolerance - provided io.quarkus quarkus-scheduler - provided io.quarkus quarkus-elytron-security - provided io.quarkus quarkus-resteasy - provided io.quarkus quarkus-resteasy-jsonb - provided org.jboss.resteasy resteasy-jaxb-provider - - org.glassfish - javax.json - org.jboss.resteasy resteasy-rxjava2 @@ -140,19 +116,16 @@ io.quarkus quarkus-smallrye-rest-client - provided io.quarkus quarkus-hibernate-orm - provided io.quarkus quarkus-jdbc-h2 - provided io.quarkus @@ -162,7 +135,6 @@ io.quarkus quarkus-kafka-client - provided @@ -197,6 +169,10 @@ kafka_2.12 test + + org.webjars + bootstrap + @@ -242,7 +218,6 @@ io.quarkus quarkus-jdbc-postgresql - provided diff --git a/integration-tests/main/src/main/java/io/quarkus/example/lambda/HelloGreeter.java b/integration-tests/main/src/main/java/io/quarkus/example/lambda/HelloGreeter.java deleted file mode 100644 index 1bdb4c78adacd..0000000000000 --- a/integration-tests/main/src/main/java/io/quarkus/example/lambda/HelloGreeter.java +++ /dev/null @@ -1,12 +0,0 @@ -package io.quarkus.example.lambda; - -import javax.enterprise.context.ApplicationScoped; - -@ApplicationScoped -public class HelloGreeter { - - public String greet(String first, String last) { - return String.format("Hello %s %s.", first, last); - } - -} diff --git a/integration-tests/main/src/main/java/io/quarkus/example/lambda/HelloLambda.java b/integration-tests/main/src/main/java/io/quarkus/example/lambda/HelloLambda.java deleted file mode 100644 index 088901374bee4..0000000000000 --- a/integration-tests/main/src/main/java/io/quarkus/example/lambda/HelloLambda.java +++ /dev/null @@ -1,17 +0,0 @@ -package io.quarkus.example.lambda; - -import javax.inject.Inject; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.RequestHandler; - -public class HelloLambda implements RequestHandler { - - @Inject - HelloGreeter greeter; - - @Override - public String handleRequest(HelloRequest request, Context context) { - return greeter.greet(request.firstName, request.lastName); - } -} diff --git a/integration-tests/main/src/main/java/io/quarkus/example/lambda/HelloRequest.java b/integration-tests/main/src/main/java/io/quarkus/example/lambda/HelloRequest.java deleted file mode 100644 index db78f62372804..0000000000000 --- a/integration-tests/main/src/main/java/io/quarkus/example/lambda/HelloRequest.java +++ /dev/null @@ -1,22 +0,0 @@ -package io.quarkus.example.lambda; - -class HelloRequest { - String firstName; - String lastName; - - public String getFirstName() { - return firstName; - } - - public void setFirstName(String firstName) { - this.firstName = firstName; - } - - public String getLastName() { - return lastName; - } - - public void setLastName(String lastName) { - this.lastName = lastName; - } -} diff --git a/integration-tests/main/src/main/java/io/quarkus/example/arc/RequestScopedBean.java b/integration-tests/main/src/main/java/io/quarkus/it/arc/RequestScopedBean.java similarity index 90% rename from integration-tests/main/src/main/java/io/quarkus/example/arc/RequestScopedBean.java rename to integration-tests/main/src/main/java/io/quarkus/it/arc/RequestScopedBean.java index a2da93936b80e..f6724b41572e5 100644 --- a/integration-tests/main/src/main/java/io/quarkus/example/arc/RequestScopedBean.java +++ b/integration-tests/main/src/main/java/io/quarkus/it/arc/RequestScopedBean.java @@ -14,11 +14,11 @@ * limitations under the License. */ -package io.quarkus.example.arc; +package io.quarkus.it.arc; import javax.enterprise.context.RequestScoped; -import io.quarkus.example.arc.somepackage.Superclass; +import io.quarkus.it.arc.somepackage.Superclass; @RequestScoped public class RequestScopedBean extends Superclass { diff --git a/integration-tests/main/src/main/java/io/quarkus/example/arc/TestRequestScopeEndpoint.java b/integration-tests/main/src/main/java/io/quarkus/it/arc/TestRequestScopeEndpoint.java similarity index 96% rename from integration-tests/main/src/main/java/io/quarkus/example/arc/TestRequestScopeEndpoint.java rename to integration-tests/main/src/main/java/io/quarkus/it/arc/TestRequestScopeEndpoint.java index 3fa51d1e4bf1f..60a5a91fb6059 100644 --- a/integration-tests/main/src/main/java/io/quarkus/example/arc/TestRequestScopeEndpoint.java +++ b/integration-tests/main/src/main/java/io/quarkus/it/arc/TestRequestScopeEndpoint.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.arc; +package io.quarkus.it.arc; import javax.inject.Inject; import javax.ws.rs.GET; diff --git a/integration-tests/main/src/main/java/io/quarkus/it/arc/UnusedBean.java b/integration-tests/main/src/main/java/io/quarkus/it/arc/UnusedBean.java new file mode 100644 index 0000000000000..074237c917807 --- /dev/null +++ b/integration-tests/main/src/main/java/io/quarkus/it/arc/UnusedBean.java @@ -0,0 +1,17 @@ +package io.quarkus.it.arc; + +import javax.enterprise.context.Dependent; +import javax.enterprise.inject.spi.InjectionPoint; +import javax.inject.Inject; + +@Dependent +public class UnusedBean { + + @Inject + InjectionPoint injectionPoint; + + public InjectionPoint getInjectionPoint() { + return injectionPoint; + } + +} diff --git a/integration-tests/main/src/main/java/io/quarkus/example/arc/somepackage/Superclass.java b/integration-tests/main/src/main/java/io/quarkus/it/arc/somepackage/Superclass.java similarity index 93% rename from integration-tests/main/src/main/java/io/quarkus/example/arc/somepackage/Superclass.java rename to integration-tests/main/src/main/java/io/quarkus/it/arc/somepackage/Superclass.java index c4950a183071b..170ffcd65c2f5 100644 --- a/integration-tests/main/src/main/java/io/quarkus/example/arc/somepackage/Superclass.java +++ b/integration-tests/main/src/main/java/io/quarkus/it/arc/somepackage/Superclass.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.arc.somepackage; +package io.quarkus.it.arc.somepackage; public class Superclass { diff --git a/integration-tests/main/src/main/java/io/quarkus/it/config/MicroProfileConfigResource.java b/integration-tests/main/src/main/java/io/quarkus/it/config/MicroProfileConfigResource.java new file mode 100644 index 0000000000000..fa43e6dcc77ac --- /dev/null +++ b/integration-tests/main/src/main/java/io/quarkus/it/config/MicroProfileConfigResource.java @@ -0,0 +1,47 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Licensed 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.it.config; + +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; + +import org.eclipse.microprofile.config.Config; + +/** + * Test some MicroProfile config primitives. + *

+ * It needs to be there as RESTEasy recently broke MicroProfile Config + * so we want to test this with RESTEasy in the classpath. + */ +@Path("/microprofile-config") +public class MicroProfileConfigResource { + + @Inject + Config config; + + @GET + @Path("/get-property-names") + public String manual() throws Exception { + if (!config.getPropertyNames().iterator().hasNext()) { + return "No config property found. Some were expected."; + } + + return "OK"; + } + +} diff --git a/integration-tests/main/src/main/java/io/quarkus/example/corestuff/CharSetSupport.java b/integration-tests/main/src/main/java/io/quarkus/it/corestuff/CharSetSupport.java similarity index 93% rename from integration-tests/main/src/main/java/io/quarkus/example/corestuff/CharSetSupport.java rename to integration-tests/main/src/main/java/io/quarkus/it/corestuff/CharSetSupport.java index 31396210020e4..c3aeb78b38931 100644 --- a/integration-tests/main/src/main/java/io/quarkus/example/corestuff/CharSetSupport.java +++ b/integration-tests/main/src/main/java/io/quarkus/it/corestuff/CharSetSupport.java @@ -1,4 +1,4 @@ -package io.quarkus.example.corestuff; +package io.quarkus.it.corestuff; import java.io.IOException; import java.nio.charset.Charset; diff --git a/integration-tests/main/src/main/java/io/quarkus/it/corestuff/CustomConfigSource.java b/integration-tests/main/src/main/java/io/quarkus/it/corestuff/CustomConfigSource.java new file mode 100644 index 0000000000000..34995a5073ccd --- /dev/null +++ b/integration-tests/main/src/main/java/io/quarkus/it/corestuff/CustomConfigSource.java @@ -0,0 +1,23 @@ +package io.quarkus.it.corestuff; + +import java.util.Collections; +import java.util.Map; + +import org.eclipse.microprofile.config.spi.ConfigSource; + +public final class CustomConfigSource implements ConfigSource { + + private static final Map THE_MAP = Collections.singletonMap("test.custom.config", "custom"); + + public Map getProperties() { + return THE_MAP; + } + + public String getValue(final String propertyName) { + return THE_MAP.get(propertyName); + } + + public String getName() { + return "Custom config source"; + } +} diff --git a/integration-tests/main/src/main/java/io/quarkus/it/corestuff/CustomConfigTesting.java b/integration-tests/main/src/main/java/io/quarkus/it/corestuff/CustomConfigTesting.java new file mode 100644 index 0000000000000..458478240525d --- /dev/null +++ b/integration-tests/main/src/main/java/io/quarkus/it/corestuff/CustomConfigTesting.java @@ -0,0 +1,21 @@ +package io.quarkus.it.corestuff; + +import java.io.IOException; +import java.util.Optional; + +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.microprofile.config.ConfigProvider; + +@WebServlet(name = "CustomConfigTestingEndpoint", urlPatterns = "/core/config-test") +public class CustomConfigTesting extends HttpServlet { + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { + final Optional strVal = ConfigProvider.getConfig().getOptionalValue("test.custom.config", String.class); + resp.getWriter().write(strVal.isPresent() && strVal.get().equals("custom") ? "OK" : "KO"); + } +} diff --git a/integration-tests/main/src/main/java/io/quarkus/example/corestuff/ReflectionTestEndpoint.java b/integration-tests/main/src/main/java/io/quarkus/it/corestuff/ReflectionTestEndpoint.java similarity index 98% rename from integration-tests/main/src/main/java/io/quarkus/example/corestuff/ReflectionTestEndpoint.java rename to integration-tests/main/src/main/java/io/quarkus/it/corestuff/ReflectionTestEndpoint.java index 7f986791b6921..a364dce16cfdb 100644 --- a/integration-tests/main/src/main/java/io/quarkus/example/corestuff/ReflectionTestEndpoint.java +++ b/integration-tests/main/src/main/java/io/quarkus/it/corestuff/ReflectionTestEndpoint.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.corestuff; +package io.quarkus.it.corestuff; import java.io.IOException; import java.io.PrintWriter; diff --git a/integration-tests/main/src/main/java/io/quarkus/example/corestuff/SomeReflectionObject.java b/integration-tests/main/src/main/java/io/quarkus/it/corestuff/SomeReflectionObject.java similarity index 95% rename from integration-tests/main/src/main/java/io/quarkus/example/corestuff/SomeReflectionObject.java rename to integration-tests/main/src/main/java/io/quarkus/it/corestuff/SomeReflectionObject.java index bd5bf4812b6c9..bcb36ca5f00f9 100644 --- a/integration-tests/main/src/main/java/io/quarkus/example/corestuff/SomeReflectionObject.java +++ b/integration-tests/main/src/main/java/io/quarkus/it/corestuff/SomeReflectionObject.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.corestuff; +package io.quarkus.it.corestuff; public class SomeReflectionObject { diff --git a/integration-tests/main/src/main/java/io/quarkus/example/datasource/DatasourceResource.java b/integration-tests/main/src/main/java/io/quarkus/it/datasource/DatasourceResource.java similarity index 99% rename from integration-tests/main/src/main/java/io/quarkus/example/datasource/DatasourceResource.java rename to integration-tests/main/src/main/java/io/quarkus/it/datasource/DatasourceResource.java index cafc55fa9515e..4f228e85a20d6 100644 --- a/integration-tests/main/src/main/java/io/quarkus/example/datasource/DatasourceResource.java +++ b/integration-tests/main/src/main/java/io/quarkus/it/datasource/DatasourceResource.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.datasource; +package io.quarkus.it.datasource; import java.sql.Connection; import java.sql.ResultSet; diff --git a/integration-tests/main/src/main/java/io/quarkus/example/datasource/DatasourceSetup.java b/integration-tests/main/src/main/java/io/quarkus/it/datasource/DatasourceSetup.java similarity index 97% rename from integration-tests/main/src/main/java/io/quarkus/example/datasource/DatasourceSetup.java rename to integration-tests/main/src/main/java/io/quarkus/it/datasource/DatasourceSetup.java index a932f67519280..6bddb5a8b818d 100644 --- a/integration-tests/main/src/main/java/io/quarkus/example/datasource/DatasourceSetup.java +++ b/integration-tests/main/src/main/java/io/quarkus/it/datasource/DatasourceSetup.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.datasource; +package io.quarkus.it.datasource; import java.sql.Connection; import java.sql.Statement; diff --git a/integration-tests/main/src/main/java/io/quarkus/example/faulttolerance/Service.java b/integration-tests/main/src/main/java/io/quarkus/it/faulttolerance/Service.java similarity index 96% rename from integration-tests/main/src/main/java/io/quarkus/example/faulttolerance/Service.java rename to integration-tests/main/src/main/java/io/quarkus/it/faulttolerance/Service.java index c0fe6f4f5557c..84a6dfbbf3bb5 100644 --- a/integration-tests/main/src/main/java/io/quarkus/example/faulttolerance/Service.java +++ b/integration-tests/main/src/main/java/io/quarkus/it/faulttolerance/Service.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.faulttolerance; +package io.quarkus.it.faulttolerance; import java.util.concurrent.atomic.AtomicInteger; diff --git a/integration-tests/main/src/main/java/io/quarkus/example/faulttolerance/TestResource.java b/integration-tests/main/src/main/java/io/quarkus/it/faulttolerance/TestResource.java similarity index 95% rename from integration-tests/main/src/main/java/io/quarkus/example/faulttolerance/TestResource.java rename to integration-tests/main/src/main/java/io/quarkus/it/faulttolerance/TestResource.java index 32476ad479a3b..8e996607e8fcb 100644 --- a/integration-tests/main/src/main/java/io/quarkus/example/faulttolerance/TestResource.java +++ b/integration-tests/main/src/main/java/io/quarkus/it/faulttolerance/TestResource.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.faulttolerance; +package io.quarkus.it.faulttolerance; import java.util.concurrent.atomic.AtomicInteger; diff --git a/integration-tests/main/src/main/java/io/quarkus/example/health/SimpleHealthCheck.java b/integration-tests/main/src/main/java/io/quarkus/it/health/SimpleHealthCheck.java similarity index 97% rename from integration-tests/main/src/main/java/io/quarkus/example/health/SimpleHealthCheck.java rename to integration-tests/main/src/main/java/io/quarkus/it/health/SimpleHealthCheck.java index 7f7e47d0d59ba..d64c3c5ba2f97 100644 --- a/integration-tests/main/src/main/java/io/quarkus/example/health/SimpleHealthCheck.java +++ b/integration-tests/main/src/main/java/io/quarkus/it/health/SimpleHealthCheck.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.health; +package io.quarkus.it.health; import java.util.Map; import java.util.Optional; diff --git a/integration-tests/main/src/main/java/io/quarkus/example/health/SimpleHealthCheckWithBuilder.java b/integration-tests/main/src/main/java/io/quarkus/it/health/SimpleHealthCheckWithBuilder.java similarity index 91% rename from integration-tests/main/src/main/java/io/quarkus/example/health/SimpleHealthCheckWithBuilder.java rename to integration-tests/main/src/main/java/io/quarkus/it/health/SimpleHealthCheckWithBuilder.java index c3d8f0b2cd279..765987cbda2d9 100644 --- a/integration-tests/main/src/main/java/io/quarkus/example/health/SimpleHealthCheckWithBuilder.java +++ b/integration-tests/main/src/main/java/io/quarkus/it/health/SimpleHealthCheckWithBuilder.java @@ -1,4 +1,4 @@ -package io.quarkus.example.health; +package io.quarkus.it.health; import org.eclipse.microprofile.health.Health; import org.eclipse.microprofile.health.HealthCheck; diff --git a/integration-tests/main/src/main/java/io/quarkus/example/jpa/Address.java b/integration-tests/main/src/main/java/io/quarkus/it/jpa/Address.java similarity index 97% rename from integration-tests/main/src/main/java/io/quarkus/example/jpa/Address.java rename to integration-tests/main/src/main/java/io/quarkus/it/jpa/Address.java index baf2ba24ce982..b05241a07138b 100644 --- a/integration-tests/main/src/main/java/io/quarkus/example/jpa/Address.java +++ b/integration-tests/main/src/main/java/io/quarkus/it/jpa/Address.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.jpa; +package io.quarkus.it.jpa; import javax.persistence.Embeddable; diff --git a/integration-tests/main/src/main/java/io/quarkus/example/jpa/Animal.java b/integration-tests/main/src/main/java/io/quarkus/it/jpa/Animal.java similarity index 96% rename from integration-tests/main/src/main/java/io/quarkus/example/jpa/Animal.java rename to integration-tests/main/src/main/java/io/quarkus/it/jpa/Animal.java index e7be38dc13ece..e5119a6d7f650 100644 --- a/integration-tests/main/src/main/java/io/quarkus/example/jpa/Animal.java +++ b/integration-tests/main/src/main/java/io/quarkus/it/jpa/Animal.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.jpa; +package io.quarkus.it.jpa; /** * @author Emmanuel Bernard emmanuel@hibernate.org diff --git a/integration-tests/main/src/main/java/io/quarkus/example/jpa/Customer.java b/integration-tests/main/src/main/java/io/quarkus/it/jpa/Customer.java similarity index 97% rename from integration-tests/main/src/main/java/io/quarkus/example/jpa/Customer.java rename to integration-tests/main/src/main/java/io/quarkus/it/jpa/Customer.java index dfcb8ed83ad05..71857594e164d 100644 --- a/integration-tests/main/src/main/java/io/quarkus/example/jpa/Customer.java +++ b/integration-tests/main/src/main/java/io/quarkus/it/jpa/Customer.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.jpa; +package io.quarkus.it.jpa; import javax.persistence.Embedded; import javax.persistence.Entity; diff --git a/integration-tests/main/src/main/java/io/quarkus/example/jpa/Human.java b/integration-tests/main/src/main/java/io/quarkus/it/jpa/Human.java similarity index 96% rename from integration-tests/main/src/main/java/io/quarkus/example/jpa/Human.java rename to integration-tests/main/src/main/java/io/quarkus/it/jpa/Human.java index e06b5f2445e9e..8ccf783f4b417 100644 --- a/integration-tests/main/src/main/java/io/quarkus/example/jpa/Human.java +++ b/integration-tests/main/src/main/java/io/quarkus/it/jpa/Human.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.jpa; +package io.quarkus.it.jpa; import javax.persistence.MappedSuperclass; diff --git a/integration-tests/main/src/main/java/io/quarkus/example/jpa/JPATestBootstrapEndpoint.java b/integration-tests/main/src/main/java/io/quarkus/it/jpa/JPATestBootstrapEndpoint.java similarity index 98% rename from integration-tests/main/src/main/java/io/quarkus/example/jpa/JPATestBootstrapEndpoint.java rename to integration-tests/main/src/main/java/io/quarkus/it/jpa/JPATestBootstrapEndpoint.java index 2558d870592c4..d2575eef0fa35 100644 --- a/integration-tests/main/src/main/java/io/quarkus/example/jpa/JPATestBootstrapEndpoint.java +++ b/integration-tests/main/src/main/java/io/quarkus/it/jpa/JPATestBootstrapEndpoint.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.jpa; +package io.quarkus.it.jpa; import java.io.IOException; import java.io.PrintWriter; @@ -34,7 +34,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import io.quarkus.examples.common.Clown; +import io.quarkus.it.common.Clown; /** * Various tests for the JPA integration. diff --git a/integration-tests/main/src/main/java/io/quarkus/example/jpa/JPATestEMInjectionEndpoint.java b/integration-tests/main/src/main/java/io/quarkus/it/jpa/JPATestEMInjectionEndpoint.java similarity index 99% rename from integration-tests/main/src/main/java/io/quarkus/example/jpa/JPATestEMInjectionEndpoint.java rename to integration-tests/main/src/main/java/io/quarkus/it/jpa/JPATestEMInjectionEndpoint.java index 7e054142742ee..09ea546e05024 100644 --- a/integration-tests/main/src/main/java/io/quarkus/example/jpa/JPATestEMInjectionEndpoint.java +++ b/integration-tests/main/src/main/java/io/quarkus/it/jpa/JPATestEMInjectionEndpoint.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.jpa; +package io.quarkus.it.jpa; import java.io.IOException; import java.io.PrintWriter; diff --git a/integration-tests/main/src/main/java/io/quarkus/example/jpa/JPATestReflectionEndpoint.java b/integration-tests/main/src/main/java/io/quarkus/it/jpa/JPATestReflectionEndpoint.java similarity index 89% rename from integration-tests/main/src/main/java/io/quarkus/example/jpa/JPATestReflectionEndpoint.java rename to integration-tests/main/src/main/java/io/quarkus/it/jpa/JPATestReflectionEndpoint.java index 4ceacb3290374..d2b9c188f7462 100644 --- a/integration-tests/main/src/main/java/io/quarkus/example/jpa/JPATestReflectionEndpoint.java +++ b/integration-tests/main/src/main/java/io/quarkus/it/jpa/JPATestReflectionEndpoint.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.jpa; +package io.quarkus.it.jpa; import java.io.IOException; import java.io.PrintWriter; @@ -39,8 +39,9 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IO makeSureEntitiesAreAccessibleViaReflection(resp); makeSureNonAnnotatedEmbeddableAreAccessibleViaReflection(resp); makeSureAnnotatedEmbeddableAreAccessibleViaReflection(resp); - makeSureClassAreAccessibleViaReflection("io.quarkus.example.jpa.Human", "Unable to enlist @MappedSuperclass", resp); - makeSureClassAreAccessibleViaReflection("io.quarkus.example.jpa.Animal", "Unable to enlist entity superclass", resp); + String packageName = this.getClass().getPackage().getName(); + makeSureClassAreAccessibleViaReflection(packageName + ".Human", "Unable to enlist @MappedSuperclass", resp); + makeSureClassAreAccessibleViaReflection(packageName + ".Animal", "Unable to enlist entity superclass", resp); resp.getWriter().write("OK"); } @@ -58,7 +59,7 @@ private void makeSureClassAreAccessibleViaReflection(String className, String er private void makeSureEntitiesAreAccessibleViaReflection(HttpServletResponse resp) throws IOException { try { - String className = getTrickedClassName(io.quarkus.example.jpa.Customer.class.getName()); + String className = getTrickedClassName(Customer.class.getName()); Class custClass = Class.forName(className); Object instance = custClass.getDeclaredConstructor().newInstance(); @@ -80,7 +81,7 @@ private void makeSureEntitiesAreAccessibleViaReflection(HttpServletResponse resp private void makeSureAnnotatedEmbeddableAreAccessibleViaReflection(HttpServletResponse resp) throws IOException { try { - String className = getTrickedClassName(io.quarkus.example.jpa.WorkAddress.class.getName()); + String className = getTrickedClassName(WorkAddress.class.getName()); Class custClass = Class.forName(className); Object instance = custClass.getDeclaredConstructor().newInstance(); @@ -97,7 +98,7 @@ private void makeSureAnnotatedEmbeddableAreAccessibleViaReflection(HttpServletRe private void makeSureNonAnnotatedEmbeddableAreAccessibleViaReflection(HttpServletResponse resp) throws IOException { try { - String className = getTrickedClassName(io.quarkus.example.jpa.Address.class.getName()); + String className = getTrickedClassName(Address.class.getName()); Class custClass = Class.forName(className); Object instance = custClass.getDeclaredConstructor().newInstance(); @@ -114,7 +115,7 @@ private void makeSureNonAnnotatedEmbeddableAreAccessibleViaReflection(HttpServle private void makeSureNonEntityAreDCE(HttpServletResponse resp) { try { - String className = getTrickedClassName(io.quarkus.example.jpa.NotAnEntityNotReferenced.class.getName()); + String className = getTrickedClassName(NotAnEntityNotReferenced.class.getName()); Class custClass = Class.forName(className); resp.getWriter().write("Should not be able to find a non referenced non entity class"); diff --git a/integration-tests/main/src/main/java/io/quarkus/example/jpa/JpaProducer.java b/integration-tests/main/src/main/java/io/quarkus/it/jpa/JpaProducer.java similarity index 97% rename from integration-tests/main/src/main/java/io/quarkus/example/jpa/JpaProducer.java rename to integration-tests/main/src/main/java/io/quarkus/it/jpa/JpaProducer.java index e0cab6868a651..39f5830a7dea7 100644 --- a/integration-tests/main/src/main/java/io/quarkus/example/jpa/JpaProducer.java +++ b/integration-tests/main/src/main/java/io/quarkus/it/jpa/JpaProducer.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.jpa; +package io.quarkus.it.jpa; import javax.enterprise.context.Dependent; import javax.enterprise.inject.Produces; diff --git a/integration-tests/jpa-postgresql/src/main/java/io/quarkus/example/jpa/NotAnEntityNotReferenced.java b/integration-tests/main/src/main/java/io/quarkus/it/jpa/NotAnEntityNotReferenced.java similarity index 96% rename from integration-tests/jpa-postgresql/src/main/java/io/quarkus/example/jpa/NotAnEntityNotReferenced.java rename to integration-tests/main/src/main/java/io/quarkus/it/jpa/NotAnEntityNotReferenced.java index d6a6280cf5de6..88c18314f72e0 100644 --- a/integration-tests/jpa-postgresql/src/main/java/io/quarkus/example/jpa/NotAnEntityNotReferenced.java +++ b/integration-tests/main/src/main/java/io/quarkus/it/jpa/NotAnEntityNotReferenced.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.jpa; +package io.quarkus.it.jpa; /** * Should not be referenced by the code diff --git a/integration-tests/main/src/main/java/io/quarkus/example/jpa/Person.java b/integration-tests/main/src/main/java/io/quarkus/it/jpa/Person.java similarity index 98% rename from integration-tests/main/src/main/java/io/quarkus/example/jpa/Person.java rename to integration-tests/main/src/main/java/io/quarkus/it/jpa/Person.java index 3874d10168c1f..205645b9839aa 100644 --- a/integration-tests/main/src/main/java/io/quarkus/example/jpa/Person.java +++ b/integration-tests/main/src/main/java/io/quarkus/it/jpa/Person.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.jpa; +package io.quarkus.it.jpa; import javax.persistence.CascadeType; import javax.persistence.Entity; diff --git a/integration-tests/main/src/main/java/io/quarkus/example/jpa/SequencedAddress.java b/integration-tests/main/src/main/java/io/quarkus/it/jpa/SequencedAddress.java similarity index 97% rename from integration-tests/main/src/main/java/io/quarkus/example/jpa/SequencedAddress.java rename to integration-tests/main/src/main/java/io/quarkus/it/jpa/SequencedAddress.java index 4f6724bde26c7..84a2e385d7a66 100644 --- a/integration-tests/main/src/main/java/io/quarkus/example/jpa/SequencedAddress.java +++ b/integration-tests/main/src/main/java/io/quarkus/it/jpa/SequencedAddress.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.jpa; +package io.quarkus.it.jpa; import javax.persistence.Entity; import javax.persistence.GeneratedValue; diff --git a/integration-tests/main/src/main/java/io/quarkus/example/jpa/WorkAddress.java b/integration-tests/main/src/main/java/io/quarkus/it/jpa/WorkAddress.java similarity index 96% rename from integration-tests/main/src/main/java/io/quarkus/example/jpa/WorkAddress.java rename to integration-tests/main/src/main/java/io/quarkus/it/jpa/WorkAddress.java index 20cf00e5faddb..eda16630adc85 100644 --- a/integration-tests/main/src/main/java/io/quarkus/example/jpa/WorkAddress.java +++ b/integration-tests/main/src/main/java/io/quarkus/it/jpa/WorkAddress.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.jpa; +package io.quarkus.it.jpa; import javax.persistence.Embeddable; diff --git a/integration-tests/main/src/main/java/io/quarkus/example/kafka/KafkaConsumerManager.java b/integration-tests/main/src/main/java/io/quarkus/it/kafka/KafkaConsumerManager.java similarity index 97% rename from integration-tests/main/src/main/java/io/quarkus/example/kafka/KafkaConsumerManager.java rename to integration-tests/main/src/main/java/io/quarkus/it/kafka/KafkaConsumerManager.java index fc8382b31d757..6261a509d98a6 100644 --- a/integration-tests/main/src/main/java/io/quarkus/example/kafka/KafkaConsumerManager.java +++ b/integration-tests/main/src/main/java/io/quarkus/it/kafka/KafkaConsumerManager.java @@ -1,4 +1,4 @@ -package io.quarkus.example.kafka; +package io.quarkus.it.kafka; import java.util.Collections; import java.util.Properties; diff --git a/integration-tests/main/src/main/java/io/quarkus/example/kafka/KafkaEndpoint.java b/integration-tests/main/src/main/java/io/quarkus/it/kafka/KafkaEndpoint.java similarity index 92% rename from integration-tests/main/src/main/java/io/quarkus/example/kafka/KafkaEndpoint.java rename to integration-tests/main/src/main/java/io/quarkus/it/kafka/KafkaEndpoint.java index a0aef9b45f837..fd429d6fcd109 100644 --- a/integration-tests/main/src/main/java/io/quarkus/example/kafka/KafkaEndpoint.java +++ b/integration-tests/main/src/main/java/io/quarkus/it/kafka/KafkaEndpoint.java @@ -1,4 +1,4 @@ -package io.quarkus.example.kafka; +package io.quarkus.it.kafka; import javax.inject.Inject; import javax.ws.rs.GET; diff --git a/integration-tests/main/src/main/java/io/quarkus/example/kafka/KafkaProducerManager.java b/integration-tests/main/src/main/java/io/quarkus/it/kafka/KafkaProducerManager.java similarity index 97% rename from integration-tests/main/src/main/java/io/quarkus/example/kafka/KafkaProducerManager.java rename to integration-tests/main/src/main/java/io/quarkus/it/kafka/KafkaProducerManager.java index c836cffe2b440..3870abf0d9190 100644 --- a/integration-tests/main/src/main/java/io/quarkus/example/kafka/KafkaProducerManager.java +++ b/integration-tests/main/src/main/java/io/quarkus/it/kafka/KafkaProducerManager.java @@ -1,4 +1,4 @@ -package io.quarkus.example.kafka; +package io.quarkus.it.kafka; import java.util.Properties; diff --git a/integration-tests/main/src/main/java/io/quarkus/example/metrics/MetricsResource.java b/integration-tests/main/src/main/java/io/quarkus/it/metrics/MetricsResource.java similarity index 88% rename from integration-tests/main/src/main/java/io/quarkus/example/metrics/MetricsResource.java rename to integration-tests/main/src/main/java/io/quarkus/it/metrics/MetricsResource.java index 3558f4ad9801e..4457742b1a3fb 100644 --- a/integration-tests/main/src/main/java/io/quarkus/example/metrics/MetricsResource.java +++ b/integration-tests/main/src/main/java/io/quarkus/it/metrics/MetricsResource.java @@ -14,10 +14,11 @@ * limitations under the License. */ -package io.quarkus.example.metrics; +package io.quarkus.it.metrics; import javax.inject.Inject; import javax.ws.rs.GET; +import javax.ws.rs.NotFoundException; import javax.ws.rs.Path; import org.eclipse.microprofile.metrics.Histogram; @@ -84,4 +85,11 @@ public String counterWithTags() { return "TEST"; } + @GET + @Path("/counter-throwing-not-found-exception") + @Counted(monotonic = true, name = "counter_404") + public String counterWithTagsThrowingNotFound() { + throw new NotFoundException(); + } + } diff --git a/integration-tests/main/src/main/java/io/quarkus/example/opentracing/OpenTracingResource.java b/integration-tests/main/src/main/java/io/quarkus/it/opentracing/OpenTracingResource.java similarity index 95% rename from integration-tests/main/src/main/java/io/quarkus/example/opentracing/OpenTracingResource.java rename to integration-tests/main/src/main/java/io/quarkus/it/opentracing/OpenTracingResource.java index 65defdff21dc4..56a341a41daca 100644 --- a/integration-tests/main/src/main/java/io/quarkus/example/opentracing/OpenTracingResource.java +++ b/integration-tests/main/src/main/java/io/quarkus/it/opentracing/OpenTracingResource.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.opentracing; +package io.quarkus.it.opentracing; import javax.ws.rs.GET; import javax.ws.rs.Path; diff --git a/integration-tests/main/src/main/java/io/quarkus/example/reactive/ReactiveStreamOpsResource.java b/integration-tests/main/src/main/java/io/quarkus/it/reactive/ReactiveStreamOpsResource.java similarity index 97% rename from integration-tests/main/src/main/java/io/quarkus/example/reactive/ReactiveStreamOpsResource.java rename to integration-tests/main/src/main/java/io/quarkus/it/reactive/ReactiveStreamOpsResource.java index 729cb51f527e0..f0c227c00b174 100644 --- a/integration-tests/main/src/main/java/io/quarkus/example/reactive/ReactiveStreamOpsResource.java +++ b/integration-tests/main/src/main/java/io/quarkus/it/reactive/ReactiveStreamOpsResource.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.reactive; +package io.quarkus.it.reactive; import javax.ws.rs.GET; import javax.ws.rs.Path; diff --git a/integration-tests/main/src/main/java/io/quarkus/example/rest/AnnotatedInterface.java b/integration-tests/main/src/main/java/io/quarkus/it/rest/AnnotatedInterface.java similarity index 80% rename from integration-tests/main/src/main/java/io/quarkus/example/rest/AnnotatedInterface.java rename to integration-tests/main/src/main/java/io/quarkus/it/rest/AnnotatedInterface.java index 054d653f42ded..19cb378f6cf07 100644 --- a/integration-tests/main/src/main/java/io/quarkus/example/rest/AnnotatedInterface.java +++ b/integration-tests/main/src/main/java/io/quarkus/it/rest/AnnotatedInterface.java @@ -1,4 +1,4 @@ -package io.quarkus.example.rest; +package io.quarkus.it.rest; import javax.ws.rs.GET; import javax.ws.rs.Path; diff --git a/integration-tests/main/src/main/java/io/quarkus/example/rest/ApplicationFooProvider.java b/integration-tests/main/src/main/java/io/quarkus/it/rest/ApplicationFooProvider.java similarity index 96% rename from integration-tests/main/src/main/java/io/quarkus/example/rest/ApplicationFooProvider.java rename to integration-tests/main/src/main/java/io/quarkus/it/rest/ApplicationFooProvider.java index 2cc154382277b..4cf039f525e2e 100644 --- a/integration-tests/main/src/main/java/io/quarkus/example/rest/ApplicationFooProvider.java +++ b/integration-tests/main/src/main/java/io/quarkus/it/rest/ApplicationFooProvider.java @@ -1,4 +1,4 @@ -package io.quarkus.example.rest; +package io.quarkus.it.rest; import java.io.IOException; import java.io.OutputStream; diff --git a/integration-tests/main/src/main/java/io/quarkus/example/rest/ClientResource.java b/integration-tests/main/src/main/java/io/quarkus/it/rest/ClientResource.java similarity index 66% rename from integration-tests/main/src/main/java/io/quarkus/example/rest/ClientResource.java rename to integration-tests/main/src/main/java/io/quarkus/it/rest/ClientResource.java index a45baa50a4311..c6322231dae57 100644 --- a/integration-tests/main/src/main/java/io/quarkus/example/rest/ClientResource.java +++ b/integration-tests/main/src/main/java/io/quarkus/it/rest/ClientResource.java @@ -14,10 +14,11 @@ * limitations under the License. */ -package io.quarkus.example.rest; +package io.quarkus.it.rest; import java.net.URL; import java.util.List; +import java.util.Map; import javax.inject.Inject; import javax.ws.rs.GET; @@ -37,15 +38,15 @@ public class ClientResource { @GET @Path("/manual") public String manual() throws Exception { - RestInterface iface = RestClientBuilder.newBuilder() + ProgrammaticRestInterface iface = RestClientBuilder.newBuilder() .baseUrl(new URL(System.getProperty("test.url"))) - .build(RestInterface.class); + .build(ProgrammaticRestInterface.class); return iface.get(); } @GET @Path("/cdi") - public String cdi() throws Exception { + public String cdi() { return restInterface.get(); } @@ -53,9 +54,9 @@ public String cdi() throws Exception { @Path("manual/jackson") @Produces("application/json") public TestResource.MyData getDataManual() throws Exception { - RestInterface iface = RestClientBuilder.newBuilder() + ProgrammaticRestInterface iface = RestClientBuilder.newBuilder() .baseUrl(new URL(System.getProperty("test.url"))) - .build(RestInterface.class); + .build(ProgrammaticRestInterface.class); System.out.println(iface.getData()); return iface.getData(); } @@ -71,9 +72,9 @@ public TestResource.MyData getDataCdi() { @Path("/manual/complex") @Produces("application/json") public List complexManual() throws Exception { - RestInterface iface = RestClientBuilder.newBuilder() + ProgrammaticRestInterface iface = RestClientBuilder.newBuilder() .baseUrl(new URL(System.getProperty("test.url"))) - .build(RestInterface.class); + .build(ProgrammaticRestInterface.class); System.out.println(iface.complex()); return iface.complex(); } @@ -85,4 +86,21 @@ public List complexCdi() { return restInterface.complex(); } + @GET + @Path("/manual/headers") + @Produces("application/json") + public Map getAllHeaders(String headerValue) throws Exception { + ProgrammaticRestInterface client = RestClientBuilder.newBuilder() + .baseUrl(new URL(System.getProperty("test.url"))) + .build(ProgrammaticRestInterface.class); + return client.getAllHeaders(); + } + + @GET + @Path("/cdi/headers") + @Produces("application/json") + public Map getAllHeadersCdi(String headerValue) { + return restInterface.getAllHeaders(); + } + } diff --git a/integration-tests/main/src/main/java/io/quarkus/example/rest/CollectionType.java b/integration-tests/main/src/main/java/io/quarkus/it/rest/CollectionType.java similarity index 96% rename from integration-tests/main/src/main/java/io/quarkus/example/rest/CollectionType.java rename to integration-tests/main/src/main/java/io/quarkus/it/rest/CollectionType.java index 0d1ad79b8f88b..a010fedac525b 100644 --- a/integration-tests/main/src/main/java/io/quarkus/example/rest/CollectionType.java +++ b/integration-tests/main/src/main/java/io/quarkus/it/rest/CollectionType.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.rest; +package io.quarkus.it.rest; public class CollectionType { diff --git a/integration-tests/main/src/main/java/io/quarkus/example/rest/ComponentType.java b/integration-tests/main/src/main/java/io/quarkus/it/rest/ComponentType.java similarity index 97% rename from integration-tests/main/src/main/java/io/quarkus/example/rest/ComponentType.java rename to integration-tests/main/src/main/java/io/quarkus/it/rest/ComponentType.java index 23c461202573e..5539b6b7615e6 100644 --- a/integration-tests/main/src/main/java/io/quarkus/example/rest/ComponentType.java +++ b/integration-tests/main/src/main/java/io/quarkus/it/rest/ComponentType.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.rest; +package io.quarkus.it.rest; import java.util.HashSet; import java.util.Set; diff --git a/integration-tests/main/src/main/java/io/quarkus/example/rest/ExternalService.java b/integration-tests/main/src/main/java/io/quarkus/it/rest/ExternalService.java similarity index 83% rename from integration-tests/main/src/main/java/io/quarkus/example/rest/ExternalService.java rename to integration-tests/main/src/main/java/io/quarkus/it/rest/ExternalService.java index a637a0367df7d..12c528fb65dfb 100644 --- a/integration-tests/main/src/main/java/io/quarkus/example/rest/ExternalService.java +++ b/integration-tests/main/src/main/java/io/quarkus/it/rest/ExternalService.java @@ -1,4 +1,4 @@ -package io.quarkus.example.rest; +package io.quarkus.it.rest; import javax.enterprise.context.ApplicationScoped; diff --git a/integration-tests/main/src/main/java/io/quarkus/example/rest/NonAnnotatedImplementation.java b/integration-tests/main/src/main/java/io/quarkus/it/rest/NonAnnotatedImplementation.java similarity index 82% rename from integration-tests/main/src/main/java/io/quarkus/example/rest/NonAnnotatedImplementation.java rename to integration-tests/main/src/main/java/io/quarkus/it/rest/NonAnnotatedImplementation.java index ed49dca2d84bb..e1e43a2e80892 100644 --- a/integration-tests/main/src/main/java/io/quarkus/example/rest/NonAnnotatedImplementation.java +++ b/integration-tests/main/src/main/java/io/quarkus/it/rest/NonAnnotatedImplementation.java @@ -1,4 +1,4 @@ -package io.quarkus.example.rest; +package io.quarkus.it.rest; public class NonAnnotatedImplementation implements AnnotatedInterface { @Override diff --git a/integration-tests/main/src/main/java/io/quarkus/it/rest/ProgrammaticRestInterface.java b/integration-tests/main/src/main/java/io/quarkus/it/rest/ProgrammaticRestInterface.java new file mode 100644 index 0000000000000..aa550be6bab15 --- /dev/null +++ b/integration-tests/main/src/main/java/io/quarkus/it/rest/ProgrammaticRestInterface.java @@ -0,0 +1,37 @@ +package io.quarkus.it.rest; + +import java.util.List; +import java.util.Map; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; + +import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders; + +/** + * A version of {@link RestInterface} that doesn't have {@link org.eclipse.microprofile.rest.client.inject.RegisterRestClient} + * and can be used only programmatically, i.e. with the builder. + */ +@Path("/test") +@RegisterClientHeaders +public interface ProgrammaticRestInterface { + + @GET + String get(); + + @GET + @Path("/jackson") + @Produces("application/json") + TestResource.MyData getData(); + + @GET + @Path("/complex") + @Produces("application/json") + List complex(); + + @GET + @Path("/headers") + @Produces("application/json") + Map getAllHeaders(); +} diff --git a/integration-tests/main/src/main/java/io/quarkus/example/rest/RestInterface.java b/integration-tests/main/src/main/java/io/quarkus/it/rest/RestInterface.java similarity index 80% rename from integration-tests/main/src/main/java/io/quarkus/example/rest/RestInterface.java rename to integration-tests/main/src/main/java/io/quarkus/it/rest/RestInterface.java index 25a3fae1d8928..287aca09428b6 100644 --- a/integration-tests/main/src/main/java/io/quarkus/example/rest/RestInterface.java +++ b/integration-tests/main/src/main/java/io/quarkus/it/rest/RestInterface.java @@ -14,18 +14,21 @@ * limitations under the License. */ -package io.quarkus.example.rest; +package io.quarkus.it.rest; import java.util.List; +import java.util.Map; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; +import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders; import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; @RegisterRestClient @Path("/test") +@RegisterClientHeaders public interface RestInterface { @GET @@ -40,4 +43,9 @@ public interface RestInterface { @Path("/complex") @Produces("application/json") List complex(); + + @GET + @Path("/headers") + @Produces("application/json") + Map getAllHeaders(); } diff --git a/integration-tests/main/src/main/java/io/quarkus/it/rest/ServiceWithConfig.java b/integration-tests/main/src/main/java/io/quarkus/it/rest/ServiceWithConfig.java new file mode 100644 index 0000000000000..bf2dbbf48aae5 --- /dev/null +++ b/integration-tests/main/src/main/java/io/quarkus/it/rest/ServiceWithConfig.java @@ -0,0 +1,23 @@ +package io.quarkus.it.rest; + +import javax.enterprise.context.ApplicationScoped; + +import org.eclipse.microprofile.config.inject.ConfigProperty; + +@ApplicationScoped +public class ServiceWithConfig { + + @ConfigProperty(name = "quarkus.http.host") + String quarkusHost; + + @ConfigProperty(name = "web-message") + String message; + + public String host() { + return quarkusHost; + } + + public String message() { + return message; + } +} diff --git a/integration-tests/main/src/main/java/io/quarkus/it/rest/Someservice.java b/integration-tests/main/src/main/java/io/quarkus/it/rest/Someservice.java new file mode 100644 index 0000000000000..86a62f7de79de --- /dev/null +++ b/integration-tests/main/src/main/java/io/quarkus/it/rest/Someservice.java @@ -0,0 +1,11 @@ +package io.quarkus.it.rest; + +import javax.enterprise.context.ApplicationScoped; + +@ApplicationScoped +public class Someservice { + + public String name() { + return "some"; + } +} diff --git a/integration-tests/main/src/main/java/io/quarkus/example/rest/SslClientResource.java b/integration-tests/main/src/main/java/io/quarkus/it/rest/SslClientResource.java similarity index 96% rename from integration-tests/main/src/main/java/io/quarkus/example/rest/SslClientResource.java rename to integration-tests/main/src/main/java/io/quarkus/it/rest/SslClientResource.java index 37db71079f7cb..29dda6e09c98e 100644 --- a/integration-tests/main/src/main/java/io/quarkus/example/rest/SslClientResource.java +++ b/integration-tests/main/src/main/java/io/quarkus/it/rest/SslClientResource.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.rest; +package io.quarkus.it.rest; import javax.inject.Inject; import javax.ws.rs.GET; diff --git a/integration-tests/main/src/main/java/io/quarkus/example/rest/SslRestInterface.java b/integration-tests/main/src/main/java/io/quarkus/it/rest/SslRestInterface.java similarity index 96% rename from integration-tests/main/src/main/java/io/quarkus/example/rest/SslRestInterface.java rename to integration-tests/main/src/main/java/io/quarkus/it/rest/SslRestInterface.java index 77aebd7706563..aade595865692 100644 --- a/integration-tests/main/src/main/java/io/quarkus/example/rest/SslRestInterface.java +++ b/integration-tests/main/src/main/java/io/quarkus/it/rest/SslRestInterface.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.rest; +package io.quarkus.it.rest; import javax.ws.rs.GET; diff --git a/integration-tests/main/src/main/java/io/quarkus/example/rest/SubComponent.java b/integration-tests/main/src/main/java/io/quarkus/it/rest/SubComponent.java similarity index 96% rename from integration-tests/main/src/main/java/io/quarkus/example/rest/SubComponent.java rename to integration-tests/main/src/main/java/io/quarkus/it/rest/SubComponent.java index aa852a2a2cd6f..fc99a841602d2 100644 --- a/integration-tests/main/src/main/java/io/quarkus/example/rest/SubComponent.java +++ b/integration-tests/main/src/main/java/io/quarkus/it/rest/SubComponent.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.rest; +package io.quarkus.it.rest; import java.util.ArrayList; import java.util.List; diff --git a/integration-tests/main/src/main/java/io/quarkus/example/rest/TestResource.java b/integration-tests/main/src/main/java/io/quarkus/it/rest/TestResource.java similarity index 65% rename from integration-tests/main/src/main/java/io/quarkus/example/rest/TestResource.java rename to integration-tests/main/src/main/java/io/quarkus/it/rest/TestResource.java index ff1c15300b79e..20e9a626b9dae 100644 --- a/integration-tests/main/src/main/java/io/quarkus/example/rest/TestResource.java +++ b/integration-tests/main/src/main/java/io/quarkus/it/rest/TestResource.java @@ -14,10 +14,12 @@ * limitations under the License. */ -package io.quarkus.example.rest; +package io.quarkus.it.rest; import java.util.Collections; +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.atomic.AtomicInteger; @@ -34,14 +36,22 @@ import javax.ws.rs.GET; import javax.ws.rs.HeaderParam; import javax.ws.rs.MatrixParam; +import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; +import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.Response; import javax.xml.bind.annotation.XmlRootElement; +import org.eclipse.microprofile.openapi.annotations.enums.SchemaType; +import org.eclipse.microprofile.openapi.annotations.media.Content; +import org.eclipse.microprofile.openapi.annotations.media.Schema; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponse; +import org.eclipse.microprofile.openapi.annotations.responses.APIResponses; + import io.quarkus.runtime.annotations.RegisterForReflection; import io.reactivex.Single; @@ -54,6 +64,9 @@ public class TestResource { @Inject ExternalService service; + @Inject + ServiceWithConfig config; + private final AtomicInteger count = new AtomicInteger(0); @GET @@ -67,6 +80,18 @@ public String service() { return service.service(); } + @GET + @Path("/config/host") + public String configPort() { + return config.host(); + } + + @GET + @Path("/config/message") + public String configMessage() { + return config.message(); + } + @GET @Path("/count") public int count() { @@ -141,6 +166,16 @@ public List complex() { return Collections.singletonList(ret); } + @GET + @Path("/headers") + @Produces("application/json") + public Map getAllHeaders(@Context HttpHeaders headers) { + Map resultMap = new HashMap<>(); + headers.getRequestHeaders().forEach( + (key, values) -> resultMap.put(key, String.join(",", values))); + return resultMap; + } + @GET @Path("/subclass") @Produces("application/json") @@ -171,6 +206,59 @@ public Response response() { return Response.ok(entity).build(); } + @GET + @Path("/openapi/responses") + @Produces("application/json") + @APIResponse(content = @Content(mediaType = "application/json", schema = @Schema(type = SchemaType.OBJECT, implementation = MyOpenApiEntityV1.class))) + public Response openApiResponse() { + MyOpenApiEntityV1 entity = new MyOpenApiEntityV1(); + entity.setName("my openapi entity name"); + return Response.ok(entity).build(); + } + + @GET + @Path("/openapi/responses/{version}") + @Produces("application/json") + @APIResponses({ + @APIResponse(content = @Content(mediaType = "application/json", schema = @Schema(type = SchemaType.OBJECT, implementation = MyOpenApiEntityV1.class))), + @APIResponse(content = @Content(mediaType = "application/json", schema = @Schema(type = SchemaType.OBJECT, implementation = MyOpenApiEntityV2.class))) + }) + public Response openApiResponses(@PathParam("version") String version) { + if ("v1".equals(version)) { + MyOpenApiEntityV1 entityV1 = new MyOpenApiEntityV1(); + entityV1.setName("my openapi entity version one name"); + return Response.ok(entityV1).build(); + } + + MyOpenApiEntityV2 entityV2 = new MyOpenApiEntityV2(); + entityV2.setName("my openapi entity version two name"); + entityV2.setValue(version); + return Response.ok(entityV2).build(); + } + + @GET + @Path("/openapi/schema") + @Produces("application/json") + public Response openApiSchemaResponses() { + MyOpenApiSchemaEntity entity = new MyOpenApiSchemaEntity(); + entity.setName("my openapi schema"); + return Response.ok(entity).build(); + } + + @GET + @APIResponses({ @APIResponse(responseCode = "204", description = "APIResponses with a no content response") }) + @Path("/openapi/no-content/api-responses") + public Response apiResponsesNoContent() { + return Response.noContent().build(); + } + + @GET + @APIResponse(responseCode = "204", description = "APIResponse with no content response") + @Path("/openapi/no-content/api-response") + public Response apiResponseNoContent() { + return Response.noContent().build(); + } + @GET @Path("/fooprovider") @Produces("application/foo") @@ -214,6 +302,12 @@ public void resteasyParams(@org.jboss.resteasy.annotations.jaxrs.PathParam Strin @org.jboss.resteasy.annotations.jaxrs.QueryParam String query) { } + @POST + @Path("/gzip") + public String gzip(byte[] message) { + return "gzipped:" + new String(message); + } + @XmlRootElement public static class XmlObject { @@ -324,4 +418,51 @@ public void setValue(String value) { this.value = value; } } + + public static class MyOpenApiEntityV1 { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + public static class MyOpenApiEntityV2 { + private String value; + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } + + @Schema() + public static class MyOpenApiSchemaEntity { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + } diff --git a/integration-tests/main/src/main/java/io/quarkus/it/rest/TestResourceWithConstructorInjection.java b/integration-tests/main/src/main/java/io/quarkus/it/rest/TestResourceWithConstructorInjection.java new file mode 100644 index 0000000000000..c6233de4b45a6 --- /dev/null +++ b/integration-tests/main/src/main/java/io/quarkus/it/rest/TestResourceWithConstructorInjection.java @@ -0,0 +1,20 @@ +package io.quarkus.it.rest; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; + +@Path("/testCtor") +public class TestResourceWithConstructorInjection { + + final Someservice service; + + public TestResourceWithConstructorInjection(Someservice service) { + this.service = service; + } + + @GET + @Path("/service") + public String service() { + return service.name(); + } +} diff --git a/integration-tests/main/src/main/java/io/quarkus/it/rest/TestResourceWithTwoConstructors.java b/integration-tests/main/src/main/java/io/quarkus/it/rest/TestResourceWithTwoConstructors.java new file mode 100644 index 0000000000000..048bd02664e29 --- /dev/null +++ b/integration-tests/main/src/main/java/io/quarkus/it/rest/TestResourceWithTwoConstructors.java @@ -0,0 +1,25 @@ +package io.quarkus.it.rest; + +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; + +@Path("/testCtor2") +public class TestResourceWithTwoConstructors { + + Someservice service; + + public TestResourceWithTwoConstructors() { + } + + @Inject + public TestResourceWithTwoConstructors(Someservice service) { + this.service = service; + } + + @GET + @Path("/service") + public String service() { + return service.name(); + } +} diff --git a/integration-tests/main/src/main/java/io/quarkus/example/scheduler/ExampleJobs.java b/integration-tests/main/src/main/java/io/quarkus/it/scheduler/ExampleJobs.java similarity index 96% rename from integration-tests/main/src/main/java/io/quarkus/example/scheduler/ExampleJobs.java rename to integration-tests/main/src/main/java/io/quarkus/it/scheduler/ExampleJobs.java index 249e458fa808e..ecd446c2a813e 100644 --- a/integration-tests/main/src/main/java/io/quarkus/example/scheduler/ExampleJobs.java +++ b/integration-tests/main/src/main/java/io/quarkus/it/scheduler/ExampleJobs.java @@ -1,4 +1,4 @@ -package io.quarkus.example.scheduler; +package io.quarkus.it.scheduler; import java.util.concurrent.TimeUnit; diff --git a/integration-tests/main/src/main/java/io/quarkus/example/transaction/TransactionResource.java b/integration-tests/main/src/main/java/io/quarkus/it/transaction/TransactionResource.java similarity index 97% rename from integration-tests/main/src/main/java/io/quarkus/example/transaction/TransactionResource.java rename to integration-tests/main/src/main/java/io/quarkus/it/transaction/TransactionResource.java index 4d7743571e970..3a19762190015 100644 --- a/integration-tests/main/src/main/java/io/quarkus/example/transaction/TransactionResource.java +++ b/integration-tests/main/src/main/java/io/quarkus/it/transaction/TransactionResource.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.transaction; +package io.quarkus.it.transaction; import java.util.concurrent.atomic.AtomicBoolean; diff --git a/integration-tests/main/src/main/java/io/quarkus/example/validator/TestValidatorEndpoint.java b/integration-tests/main/src/main/java/io/quarkus/it/validator/TestValidatorEndpoint.java similarity index 97% rename from integration-tests/main/src/main/java/io/quarkus/example/validator/TestValidatorEndpoint.java rename to integration-tests/main/src/main/java/io/quarkus/it/validator/TestValidatorEndpoint.java index d74fc5e9da6fe..b573d5e357b7c 100644 --- a/integration-tests/main/src/main/java/io/quarkus/example/validator/TestValidatorEndpoint.java +++ b/integration-tests/main/src/main/java/io/quarkus/it/validator/TestValidatorEndpoint.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.validator; +package io.quarkus.it.validator; import java.util.Set; diff --git a/integration-tests/main/src/main/java/io/quarkus/example/web/TestFilter.java b/integration-tests/main/src/main/java/io/quarkus/it/web/TestFilter.java similarity index 97% rename from integration-tests/main/src/main/java/io/quarkus/example/web/TestFilter.java rename to integration-tests/main/src/main/java/io/quarkus/it/web/TestFilter.java index 7c12466e19c23..835990c537454 100644 --- a/integration-tests/main/src/main/java/io/quarkus/example/web/TestFilter.java +++ b/integration-tests/main/src/main/java/io/quarkus/it/web/TestFilter.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.web; +package io.quarkus.it.web; import java.io.IOException; diff --git a/integration-tests/main/src/main/java/io/quarkus/example/web/TestSecureServlet.java b/integration-tests/main/src/main/java/io/quarkus/it/web/TestSecureServlet.java similarity index 97% rename from integration-tests/main/src/main/java/io/quarkus/example/web/TestSecureServlet.java rename to integration-tests/main/src/main/java/io/quarkus/it/web/TestSecureServlet.java index db3dee2a4e464..3b40e1f66949f 100644 --- a/integration-tests/main/src/main/java/io/quarkus/example/web/TestSecureServlet.java +++ b/integration-tests/main/src/main/java/io/quarkus/it/web/TestSecureServlet.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.web; +package io.quarkus.it.web; import java.io.IOException; diff --git a/integration-tests/main/src/main/java/io/quarkus/example/web/TestServlet.java b/integration-tests/main/src/main/java/io/quarkus/it/web/TestServlet.java similarity index 97% rename from integration-tests/main/src/main/java/io/quarkus/example/web/TestServlet.java rename to integration-tests/main/src/main/java/io/quarkus/it/web/TestServlet.java index 4f9bc58a4f25f..0aad600653fe3 100644 --- a/integration-tests/main/src/main/java/io/quarkus/example/web/TestServlet.java +++ b/integration-tests/main/src/main/java/io/quarkus/it/web/TestServlet.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.web; +package io.quarkus.it.web; import java.io.IOException; diff --git a/integration-tests/main/src/main/java/io/quarkus/example/websocket/EchoSocket.java b/integration-tests/main/src/main/java/io/quarkus/it/websocket/EchoSocket.java similarity index 95% rename from integration-tests/main/src/main/java/io/quarkus/example/websocket/EchoSocket.java rename to integration-tests/main/src/main/java/io/quarkus/it/websocket/EchoSocket.java index 7ce9f516ef38b..e40046243fa03 100644 --- a/integration-tests/main/src/main/java/io/quarkus/example/websocket/EchoSocket.java +++ b/integration-tests/main/src/main/java/io/quarkus/it/websocket/EchoSocket.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.websocket; +package io.quarkus.it.websocket; import javax.websocket.OnMessage; import javax.websocket.server.ServerEndpoint; diff --git a/integration-tests/main/src/main/resources/META-INF/persistence.xml b/integration-tests/main/src/main/resources/META-INF/persistence.xml index fd68110f59a85..570fdc0e5d804 100644 --- a/integration-tests/main/src/main/resources/META-INF/persistence.xml +++ b/integration-tests/main/src/main/resources/META-INF/persistence.xml @@ -24,7 +24,7 @@ Hibernate test case template Persistence Unit - io.quarkus.examples.common.Clown + io.quarkus.it.common.Clown diff --git a/integration-tests/main/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource b/integration-tests/main/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource new file mode 100644 index 0000000000000..635168de2cccc --- /dev/null +++ b/integration-tests/main/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource @@ -0,0 +1 @@ +io.quarkus.it.corestuff.CustomConfigSource diff --git a/integration-tests/main/src/main/resources/application.properties b/integration-tests/main/src/main/resources/application.properties index 01d5dda431f7d..26fcd7f243e56 100644 --- a/integration-tests/main/src/main/resources/application.properties +++ b/integration-tests/main/src/main/resources/application.properties @@ -14,10 +14,11 @@ # limitations under the License. # -io.quarkus.example.rest.RestInterface/mp-rest/url=${test.url} +io.quarkus.it.rest.RestInterface/mp-rest/url=${test.url} +org.eclipse.microprofile.rest.client.propagateHeaders=header-name # Disabled by default as it establishes external connections. # Uncomment when you want to test SSL support. -#io.quarkus.example.rest.SslRestInterface/mp-rest/url=https://www.example.com/ +#io.quarkus.it.rest.SslRestInterface/mp-rest/url=https://www.example.com/ # used to test the client behavior when SSL is disabled # we probably won't be able to test SSL proper so it seems reasonable to set it globally @@ -28,10 +29,11 @@ quarkus.datasource.driver: ${datasource.driver} quarkus.datasource.username: ${datasource.username} quarkus.datasource.password: ${datasource.password} quarkus.security.file.enabled=true - +quarkus.resteasy.gzip.enabled=true +quarkus.resteasy.gzip.max-input=10 web-message=A message schedulerservice.cron.expr=0/10 * * * * ? quarkus.log.category.kafka.level=WARN quarkus.log.category.\"org.apache.kafka\".level=WARN -quarkus.log.category.\"org.apache.zookeeper\".level=WARN \ No newline at end of file +quarkus.log.category.\"org.apache.zookeeper\".level=WARN diff --git a/integration-tests/main/src/test/java/io/quarkus/example/test/LambdaITCase.java b/integration-tests/main/src/test/java/io/quarkus/example/test/LambdaITCase.java deleted file mode 100644 index 7437d9cc88e59..0000000000000 --- a/integration-tests/main/src/test/java/io/quarkus/example/test/LambdaITCase.java +++ /dev/null @@ -1,7 +0,0 @@ -package io.quarkus.example.test; - -import io.quarkus.test.junit.SubstrateTest; - -@SubstrateTest -public class LambdaITCase extends LambdaTestCase { -} diff --git a/integration-tests/main/src/test/java/io/quarkus/it/main/BeanOnlyInTestCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/BeanOnlyInTestCase.java new file mode 100644 index 0000000000000..87c856ded7c66 --- /dev/null +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/BeanOnlyInTestCase.java @@ -0,0 +1,33 @@ +package io.quarkus.it.main; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.lang.reflect.Field; + +import javax.enterprise.inject.spi.InjectionPoint; +import javax.inject.Inject; + +import org.junit.jupiter.api.Test; + +import io.quarkus.it.arc.UnusedBean; +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +class BeanOnlyInTestCase { + + @Inject + UnusedBean unusedBean; + + @Test + public void testBeanIsInjected() { + assertNotNull(unusedBean); + InjectionPoint injectionPoint = unusedBean.getInjectionPoint(); + assertNotNull(injectionPoint); + assertEquals(UnusedBean.class, injectionPoint.getType()); + assertTrue(injectionPoint.getQualifiers().isEmpty()); + assertTrue(injectionPoint.getMember() instanceof Field); + assertTrue(injectionPoint.getAnnotated().isAnnotationPresent(Inject.class)); + } +} diff --git a/integration-tests/main/src/test/java/io/quarkus/example/test/CharacterSetSupportITCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/CharacterSetSupportITCase.java similarity index 92% rename from integration-tests/main/src/test/java/io/quarkus/example/test/CharacterSetSupportITCase.java rename to integration-tests/main/src/test/java/io/quarkus/it/main/CharacterSetSupportITCase.java index 586d0db790e2e..139160a41eb38 100644 --- a/integration-tests/main/src/test/java/io/quarkus/example/test/CharacterSetSupportITCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/CharacterSetSupportITCase.java @@ -1,4 +1,4 @@ -package io.quarkus.example.test; +package io.quarkus.it.main; import static org.hamcrest.Matchers.is; diff --git a/integration-tests/main/src/test/java/io/quarkus/example/test/CoreReflectionInGraalITCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/CoreReflectionInGraalITCase.java similarity index 96% rename from integration-tests/main/src/test/java/io/quarkus/example/test/CoreReflectionInGraalITCase.java rename to integration-tests/main/src/test/java/io/quarkus/it/main/CoreReflectionInGraalITCase.java index abcb5af39bdc3..03989a4b39343 100644 --- a/integration-tests/main/src/test/java/io/quarkus/example/test/CoreReflectionInGraalITCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/CoreReflectionInGraalITCase.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.test; +package io.quarkus.it.main; import static org.hamcrest.Matchers.is; diff --git a/integration-tests/main/src/test/java/io/quarkus/it/main/CustomConfigSourceITCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/CustomConfigSourceITCase.java new file mode 100644 index 0000000000000..798fa0755fe37 --- /dev/null +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/CustomConfigSourceITCase.java @@ -0,0 +1,7 @@ +package io.quarkus.it.main; + +import io.quarkus.test.junit.SubstrateTest; + +@SubstrateTest +public class CustomConfigSourceITCase extends CustomConfigSourceTestCase { +} diff --git a/integration-tests/main/src/test/java/io/quarkus/it/main/CustomConfigSourceTestCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/CustomConfigSourceTestCase.java new file mode 100644 index 0000000000000..c5310c47248c5 --- /dev/null +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/CustomConfigSourceTestCase.java @@ -0,0 +1,18 @@ +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 CustomConfigSourceTestCase { + + @Test + public void testCustomConfig() { + RestAssured.when().get("/core/config-test").then() + .body(is("OK")); + } +} diff --git a/integration-tests/main/src/test/java/io/quarkus/example/test/DataSourceTransactionITCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/DataSourceTransactionITCase.java similarity index 95% rename from integration-tests/main/src/test/java/io/quarkus/example/test/DataSourceTransactionITCase.java rename to integration-tests/main/src/test/java/io/quarkus/it/main/DataSourceTransactionITCase.java index fb60e74e4867c..39fc81250f94b 100644 --- a/integration-tests/main/src/test/java/io/quarkus/example/test/DataSourceTransactionITCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/DataSourceTransactionITCase.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.test; +package io.quarkus.it.main; import io.quarkus.test.junit.SubstrateTest; diff --git a/integration-tests/main/src/test/java/io/quarkus/example/test/DataSourceTransactionTestCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/DataSourceTransactionTestCase.java similarity index 97% rename from integration-tests/main/src/test/java/io/quarkus/example/test/DataSourceTransactionTestCase.java rename to integration-tests/main/src/test/java/io/quarkus/it/main/DataSourceTransactionTestCase.java index 542b317068830..5e53ab8522222 100644 --- a/integration-tests/main/src/test/java/io/quarkus/example/test/DataSourceTransactionTestCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/DataSourceTransactionTestCase.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.test; +package io.quarkus.it.main; import static org.hamcrest.Matchers.is; diff --git a/integration-tests/main/src/test/java/io/quarkus/example/test/DatasourceITCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/DatasourceITCase.java similarity index 95% rename from integration-tests/main/src/test/java/io/quarkus/example/test/DatasourceITCase.java rename to integration-tests/main/src/test/java/io/quarkus/it/main/DatasourceITCase.java index 7c69f9c1f926c..d59dbf5d2894c 100644 --- a/integration-tests/main/src/test/java/io/quarkus/example/test/DatasourceITCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/DatasourceITCase.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.test; +package io.quarkus.it.main; import io.quarkus.test.junit.SubstrateTest; diff --git a/integration-tests/main/src/test/java/io/quarkus/example/test/DatasourceTestCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/DatasourceTestCase.java similarity index 97% rename from integration-tests/main/src/test/java/io/quarkus/example/test/DatasourceTestCase.java rename to integration-tests/main/src/test/java/io/quarkus/it/main/DatasourceTestCase.java index de39ec9423b16..a62a7cadecb85 100644 --- a/integration-tests/main/src/test/java/io/quarkus/example/test/DatasourceTestCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/DatasourceTestCase.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.test; +package io.quarkus.it.main; import static org.hamcrest.Matchers.is; diff --git a/integration-tests/main/src/test/java/io/quarkus/example/test/ExternalIndexITCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/ExternalIndexITCase.java similarity index 95% rename from integration-tests/main/src/test/java/io/quarkus/example/test/ExternalIndexITCase.java rename to integration-tests/main/src/test/java/io/quarkus/it/main/ExternalIndexITCase.java index 92256c8d4886b..cf0d011c39a1b 100644 --- a/integration-tests/main/src/test/java/io/quarkus/example/test/ExternalIndexITCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/ExternalIndexITCase.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.test; +package io.quarkus.it.main; import io.quarkus.test.junit.SubstrateTest; diff --git a/integration-tests/main/src/test/java/io/quarkus/example/test/ExternalIndexTestCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/ExternalIndexTestCase.java similarity index 96% rename from integration-tests/main/src/test/java/io/quarkus/example/test/ExternalIndexTestCase.java rename to integration-tests/main/src/test/java/io/quarkus/it/main/ExternalIndexTestCase.java index 96ca24e9c2772..54b96f77efa1a 100644 --- a/integration-tests/main/src/test/java/io/quarkus/example/test/ExternalIndexTestCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/ExternalIndexTestCase.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.test; +package io.quarkus.it.main; import static org.hamcrest.Matchers.is; diff --git a/integration-tests/main/src/test/java/io/quarkus/example/test/FaultToleranceITCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/FaultToleranceITCase.java similarity index 95% rename from integration-tests/main/src/test/java/io/quarkus/example/test/FaultToleranceITCase.java rename to integration-tests/main/src/test/java/io/quarkus/it/main/FaultToleranceITCase.java index ed3adb2da42e1..026604789b529 100644 --- a/integration-tests/main/src/test/java/io/quarkus/example/test/FaultToleranceITCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/FaultToleranceITCase.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.test; +package io.quarkus.it.main; import io.quarkus.test.junit.SubstrateTest; diff --git a/integration-tests/main/src/test/java/io/quarkus/example/test/FaultToleranceTestCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/FaultToleranceTestCase.java similarity index 97% rename from integration-tests/main/src/test/java/io/quarkus/example/test/FaultToleranceTestCase.java rename to integration-tests/main/src/test/java/io/quarkus/it/main/FaultToleranceTestCase.java index 341bb2ea18414..6b5f543766256 100644 --- a/integration-tests/main/src/test/java/io/quarkus/example/test/FaultToleranceTestCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/FaultToleranceTestCase.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.test; +package io.quarkus.it.main; import java.io.ByteArrayOutputStream; import java.io.InputStream; diff --git a/integration-tests/main/src/test/java/io/quarkus/example/test/HealthCheckTestCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/HealthCheckTestCase.java similarity index 93% rename from integration-tests/main/src/test/java/io/quarkus/example/test/HealthCheckTestCase.java rename to integration-tests/main/src/test/java/io/quarkus/it/main/HealthCheckTestCase.java index e70cc3751bcbf..92c20ccb2b1e8 100644 --- a/integration-tests/main/src/test/java/io/quarkus/example/test/HealthCheckTestCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/HealthCheckTestCase.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.test; +package io.quarkus.it.main; import javax.inject.Inject; @@ -24,7 +24,7 @@ import org.junit.jupiter.api.Test; import org.wildfly.common.Assert; -import io.quarkus.example.health.SimpleHealthCheck; +import io.quarkus.it.health.SimpleHealthCheck; import io.quarkus.test.junit.QuarkusTest; import io.quarkus.test.junit.SubstrateTest; diff --git a/integration-tests/main/src/test/java/io/quarkus/example/test/HealthITCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/HealthITCase.java similarity index 95% rename from integration-tests/main/src/test/java/io/quarkus/example/test/HealthITCase.java rename to integration-tests/main/src/test/java/io/quarkus/it/main/HealthITCase.java index 2bf388976c1f1..43ac702d97fac 100644 --- a/integration-tests/main/src/test/java/io/quarkus/example/test/HealthITCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/HealthITCase.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.test; +package io.quarkus.it.main; import io.quarkus.test.junit.SubstrateTest; diff --git a/integration-tests/main/src/test/java/io/quarkus/example/test/HealthTestCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/HealthTestCase.java similarity index 97% rename from integration-tests/main/src/test/java/io/quarkus/example/test/HealthTestCase.java rename to integration-tests/main/src/test/java/io/quarkus/it/main/HealthTestCase.java index 3b9fad0725bb3..d9cbfbd6f6793 100644 --- a/integration-tests/main/src/test/java/io/quarkus/example/test/HealthTestCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/HealthTestCase.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.test; +package io.quarkus.it.main; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.is; diff --git a/integration-tests/main/src/test/java/io/quarkus/example/test/JPABootstrapITCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/JPABootstrapITCase.java similarity index 95% rename from integration-tests/main/src/test/java/io/quarkus/example/test/JPABootstrapITCase.java rename to integration-tests/main/src/test/java/io/quarkus/it/main/JPABootstrapITCase.java index 4a7718781d858..2cd770b76e413 100644 --- a/integration-tests/main/src/test/java/io/quarkus/example/test/JPABootstrapITCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/JPABootstrapITCase.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.test; +package io.quarkus.it.main; import io.quarkus.test.junit.SubstrateTest; diff --git a/integration-tests/main/src/test/java/io/quarkus/example/test/JPABootstrapTestCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/JPABootstrapTestCase.java similarity index 97% rename from integration-tests/main/src/test/java/io/quarkus/example/test/JPABootstrapTestCase.java rename to integration-tests/main/src/test/java/io/quarkus/it/main/JPABootstrapTestCase.java index 991d83486d142..39093eb362b11 100644 --- a/integration-tests/main/src/test/java/io/quarkus/example/test/JPABootstrapTestCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/JPABootstrapTestCase.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.test; +package io.quarkus.it.main; import static org.hamcrest.Matchers.is; diff --git a/integration-tests/main/src/test/java/io/quarkus/example/test/JPAEntityManagerInjectionTestCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/JPAEntityManagerInjectionTestCase.java similarity index 97% rename from integration-tests/main/src/test/java/io/quarkus/example/test/JPAEntityManagerInjectionTestCase.java rename to integration-tests/main/src/test/java/io/quarkus/it/main/JPAEntityManagerInjectionTestCase.java index 7139bd4e9d4fc..5eba0591e6435 100644 --- a/integration-tests/main/src/test/java/io/quarkus/example/test/JPAEntityManagerInjectionTestCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/JPAEntityManagerInjectionTestCase.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.test; +package io.quarkus.it.main; import static org.hamcrest.Matchers.is; diff --git a/integration-tests/main/src/test/java/io/quarkus/example/test/JPAReflectionInGraalITCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/JPAReflectionInGraalITCase.java similarity index 97% rename from integration-tests/main/src/test/java/io/quarkus/example/test/JPAReflectionInGraalITCase.java rename to integration-tests/main/src/test/java/io/quarkus/it/main/JPAReflectionInGraalITCase.java index c66aabf644595..c5b451aa88131 100644 --- a/integration-tests/main/src/test/java/io/quarkus/example/test/JPAReflectionInGraalITCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/JPAReflectionInGraalITCase.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.test; +package io.quarkus.it.main; import static org.hamcrest.Matchers.is; diff --git a/integration-tests/main/src/test/java/io/quarkus/it/main/JUnit5PerClassLifecycleTest.java b/integration-tests/main/src/test/java/io/quarkus/it/main/JUnit5PerClassLifecycleTest.java new file mode 100644 index 0000000000000..8c196459760cb --- /dev/null +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/JUnit5PerClassLifecycleTest.java @@ -0,0 +1,60 @@ +package io.quarkus.it.main; + +import javax.inject.Inject; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.wildfly.common.Assert; + +import io.quarkus.it.arc.UnusedBean; +import io.quarkus.test.junit.QuarkusTest; + +/** + * Tests JUnit 5 extension when test lifecycle is PER_CLASS. This means extension events get fired in slightly different + * order and Quarkus/Arc bootstrap and instance injection have to account for that. + * + * Test verifies that bootstrap works and that you can use injection even in before/after methods. + */ +@QuarkusTest +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public class JUnit5PerClassLifecycleTest { + + // any IP just to verify it was performed + @Inject + UnusedBean bean; + + @BeforeEach + public void beforeEach() { + Assertions.assertNotNull(bean); + Assert.assertNotNull(bean.getInjectionPoint()); + } + + @BeforeAll + public void beforeAll() { + Assertions.assertNotNull(bean); + Assert.assertNotNull(bean.getInjectionPoint()); + } + + @AfterEach + public void afterEach() { + Assertions.assertNotNull(bean); + Assert.assertNotNull(bean.getInjectionPoint()); + } + + @AfterAll + public void afterAll() { + Assertions.assertNotNull(bean); + Assert.assertNotNull(bean.getInjectionPoint()); + } + + @Test + public void testQuarkusWasBootedAndInjectionWorks() { + Assertions.assertNotNull(bean); + Assert.assertNotNull(bean.getInjectionPoint()); + } +} diff --git a/integration-tests/main/src/test/java/io/quarkus/example/test/JaxRSITCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/JaxRSITCase.java similarity index 95% rename from integration-tests/main/src/test/java/io/quarkus/example/test/JaxRSITCase.java rename to integration-tests/main/src/test/java/io/quarkus/it/main/JaxRSITCase.java index cea54efaae82f..94363e11ddd22 100644 --- a/integration-tests/main/src/test/java/io/quarkus/example/test/JaxRSITCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/JaxRSITCase.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.test; +package io.quarkus.it.main; import io.quarkus.test.junit.SubstrateTest; diff --git a/integration-tests/main/src/test/java/io/quarkus/example/test/JaxRSTestCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/JaxRSTestCase.java similarity index 66% rename from integration-tests/main/src/test/java/io/quarkus/example/test/JaxRSTestCase.java rename to integration-tests/main/src/test/java/io/quarkus/it/main/JaxRSTestCase.java index d2dc9d4ff65bc..a1af99525b59f 100644 --- a/integration-tests/main/src/test/java/io/quarkus/example/test/JaxRSTestCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/JaxRSTestCase.java @@ -14,9 +14,13 @@ * limitations under the License. */ -package io.quarkus.example.test; +package io.quarkus.it.main; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.isEmptyString; + +import java.io.ByteArrayOutputStream; +import java.util.zip.GZIPOutputStream; import org.junit.jupiter.api.Test; @@ -37,6 +41,16 @@ public void testInteger() { RestAssured.when().get("/test/int/10").then().body(is("11")); } + @Test + public void testConfigInjectionOfPort() { + RestAssured.when().get("/test/config/host").then().body(is("0.0.0.0")); + } + + @Test + public void testConfigInjectionOfMessage() { + RestAssured.when().get("/test/config/message").then().body(is("A message")); + } + @Test public void testAnnotatedInterface() { RestAssured.when().get("/interface").then().body(is("interface endpoint")); @@ -135,4 +149,55 @@ public void testFromJson() { .body("name", is("my entity name"), "value", is("my entity value")); } + + @Test + public void testOpenApiSchemaResponse() { + RestAssured.when().get("/test/openapi/responses").then() + .body("name", is("my openapi entity name")); + } + + @Test + public void testOpenApiSchemaResponsesV1() { + RestAssured.when().get("/test/openapi/responses/v1").then() + .body("name", is("my openapi entity version one name")); + } + + @Test + public void testOpenApiSchemaResponseV2() { + RestAssured.when().get("/test/openapi/responses/v2").then() + .body("name", is("my openapi entity version two name")); + } + + @Test + public void testOpenApiSchema() { + RestAssured.when().get("/test/openapi/schema").then() + .body("name", is("my openapi schema")); + } + + @Test + public void testOpenApiResponsesWithNoContent() { + RestAssured.when().get("/test/openapi/no-content/api-responses").then() + .body(isEmptyString()); + } + + @Test + public void testOpenApiResponseWithNoContent() { + RestAssured.when().get("/test/openapi/no-content/api-response").then() + .body(isEmptyString()); + } + + @Test + public void testGzipConfig() throws Exception { + //gzip.maxInput set to 10 and expects 413 status code + ByteArrayOutputStream obj = new ByteArrayOutputStream(12); + GZIPOutputStream gzip = new GZIPOutputStream(obj); + gzip.write("1234567890AB".getBytes("UTF-8")); + gzip.close(); + RestAssured.given() + .header("Content-Encoding", "gzip") + .body(obj.toByteArray()) + .post("/test/gzip") + .then().statusCode(413); + obj.close(); + } } diff --git a/integration-tests/main/src/test/java/io/quarkus/example/test/KafkaConsumerITCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/KafkaConsumerITCase.java similarity index 79% rename from integration-tests/main/src/test/java/io/quarkus/example/test/KafkaConsumerITCase.java rename to integration-tests/main/src/test/java/io/quarkus/it/main/KafkaConsumerITCase.java index e46d7a3532059..42134366cd911 100644 --- a/integration-tests/main/src/test/java/io/quarkus/example/test/KafkaConsumerITCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/KafkaConsumerITCase.java @@ -1,4 +1,4 @@ -package io.quarkus.example.test; +package io.quarkus.it.main; import io.quarkus.test.junit.SubstrateTest; diff --git a/integration-tests/main/src/test/java/io/quarkus/example/test/KafkaConsumerTest.java b/integration-tests/main/src/test/java/io/quarkus/it/main/KafkaConsumerTest.java similarity index 97% rename from integration-tests/main/src/test/java/io/quarkus/example/test/KafkaConsumerTest.java rename to integration-tests/main/src/test/java/io/quarkus/it/main/KafkaConsumerTest.java index 23812e1dd81f3..6c77f2bad54c9 100644 --- a/integration-tests/main/src/test/java/io/quarkus/example/test/KafkaConsumerTest.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/KafkaConsumerTest.java @@ -1,4 +1,4 @@ -package io.quarkus.example.test; +package io.quarkus.it.main; import java.util.Properties; diff --git a/integration-tests/main/src/test/java/io/quarkus/example/test/KafkaProducerITCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/KafkaProducerITCase.java similarity index 79% rename from integration-tests/main/src/test/java/io/quarkus/example/test/KafkaProducerITCase.java rename to integration-tests/main/src/test/java/io/quarkus/it/main/KafkaProducerITCase.java index 81bbc9c985fc6..7bf5b3c6d7734 100644 --- a/integration-tests/main/src/test/java/io/quarkus/example/test/KafkaProducerITCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/KafkaProducerITCase.java @@ -1,4 +1,4 @@ -package io.quarkus.example.test; +package io.quarkus.it.main; import io.quarkus.test.junit.SubstrateTest; diff --git a/integration-tests/main/src/test/java/io/quarkus/example/test/KafkaProducerTest.java b/integration-tests/main/src/test/java/io/quarkus/it/main/KafkaProducerTest.java similarity index 98% rename from integration-tests/main/src/test/java/io/quarkus/example/test/KafkaProducerTest.java rename to integration-tests/main/src/test/java/io/quarkus/it/main/KafkaProducerTest.java index c26a7fea87ee5..76423e1ea0e27 100644 --- a/integration-tests/main/src/test/java/io/quarkus/example/test/KafkaProducerTest.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/KafkaProducerTest.java @@ -1,4 +1,4 @@ -package io.quarkus.example.test; +package io.quarkus.it.main; import java.util.Collections; import java.util.Properties; diff --git a/integration-tests/main/src/test/java/io/quarkus/example/test/KafkaTestResource.java b/integration-tests/main/src/test/java/io/quarkus/it/main/KafkaTestResource.java similarity index 86% rename from integration-tests/main/src/test/java/io/quarkus/example/test/KafkaTestResource.java rename to integration-tests/main/src/test/java/io/quarkus/it/main/KafkaTestResource.java index dde8d691b66d9..5fa03a9b8822a 100644 --- a/integration-tests/main/src/test/java/io/quarkus/example/test/KafkaTestResource.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/KafkaTestResource.java @@ -1,6 +1,8 @@ -package io.quarkus.example.test; +package io.quarkus.it.main; import java.io.File; +import java.util.Collections; +import java.util.Map; import java.util.Properties; import io.debezium.kafka.KafkaCluster; @@ -12,7 +14,7 @@ public class KafkaTestResource implements QuarkusTestResourceLifecycleManager { private KafkaCluster kafka; @Override - public void start() { + public Map start() { try { Properties props = new Properties(); props.setProperty("zookeeper.connection.timeout.ms", "10000"); @@ -27,6 +29,7 @@ public void start() { } catch (Exception e) { throw new RuntimeException(e); } + return Collections.emptyMap(); } @Override diff --git a/integration-tests/main/src/test/java/io/quarkus/example/test/MetricsITCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/MetricsITCase.java similarity index 95% rename from integration-tests/main/src/test/java/io/quarkus/example/test/MetricsITCase.java rename to integration-tests/main/src/test/java/io/quarkus/it/main/MetricsITCase.java index ce19b3b1ada76..ec16c7b77c87a 100644 --- a/integration-tests/main/src/test/java/io/quarkus/example/test/MetricsITCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/MetricsITCase.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.test; +package io.quarkus.it.main; import io.quarkus.test.junit.SubstrateTest; diff --git a/integration-tests/main/src/test/java/io/quarkus/example/test/MetricsTestCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/MetricsTestCase.java similarity index 55% rename from integration-tests/main/src/test/java/io/quarkus/example/test/MetricsTestCase.java rename to integration-tests/main/src/test/java/io/quarkus/it/main/MetricsTestCase.java index 78a234e804d0f..b99b14d8ed946 100644 --- a/integration-tests/main/src/test/java/io/quarkus/example/test/MetricsTestCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/MetricsTestCase.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.test; +package io.quarkus.it.main; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; @@ -26,62 +26,63 @@ @QuarkusTest public class MetricsTestCase { + private static final String metricsPrefix = "application:io_quarkus_it_metrics_"; @Test public void testCounter() { invokeCounter(); - assertMetricExactValue("application:io_quarkus_example_metrics_metrics_resource_a_counted_resource", "1.0"); + assertMetricExactValue(metricsPrefix + "metrics_resource_a_counted_resource", "1.0"); } @Test public void testGauge() { invokeGauge(); - assertMetricExactValue("application:io_quarkus_example_metrics_metrics_resource_gauge", "42.0"); + assertMetricExactValue(metricsPrefix + "metrics_resource_gauge", "42.0"); } @Test public void testMeter() { invokeMeter(); - assertMetricExactValue("application:io_quarkus_example_metrics_metrics_resource_meter_total", "1.0"); - assertMetricExists("application:io_quarkus_example_metrics_metrics_resource_meter_rate_per_second"); - assertMetricExists("application:io_quarkus_example_metrics_metrics_resource_meter_one_min_rate_per_second"); - assertMetricExists("application:io_quarkus_example_metrics_metrics_resource_meter_five_min_rate_per_second"); - assertMetricExists("application:io_quarkus_example_metrics_metrics_resource_meter_fifteen_min_rate_per_second"); + assertMetricExactValue(metricsPrefix + "metrics_resource_meter_total", "1.0"); + assertMetricExists(metricsPrefix + "metrics_resource_meter_rate_per_second"); + assertMetricExists(metricsPrefix + "metrics_resource_meter_one_min_rate_per_second"); + assertMetricExists(metricsPrefix + "metrics_resource_meter_five_min_rate_per_second"); + assertMetricExists(metricsPrefix + "metrics_resource_meter_fifteen_min_rate_per_second"); } @Test public void testTimer() { invokeTimer(); - assertMetricExactValue("application:io_quarkus_example_metrics_metrics_resource_timer_metric_seconds_count", "1.0"); - assertMetricExists("application:io_quarkus_example_metrics_metrics_resource_timer_metric_seconds{quantile=\"0.5\"}"); - assertMetricExists("application:io_quarkus_example_metrics_metrics_resource_timer_metric_seconds{quantile=\"0.75\"}"); - assertMetricExists("application:io_quarkus_example_metrics_metrics_resource_timer_metric_seconds{quantile=\"0.95\"}"); - assertMetricExists("application:io_quarkus_example_metrics_metrics_resource_timer_metric_seconds{quantile=\"0.98\"}"); - assertMetricExists("application:io_quarkus_example_metrics_metrics_resource_timer_metric_seconds{quantile=\"0.99\"}"); - assertMetricExists("application:io_quarkus_example_metrics_metrics_resource_timer_metric_seconds{quantile=\"0.999\"}"); - assertMetricExists("application:io_quarkus_example_metrics_metrics_resource_timer_metric_one_min_rate_per_second"); - assertMetricExists("application:io_quarkus_example_metrics_metrics_resource_timer_metric_five_min_rate_per_second"); - assertMetricExists("application:io_quarkus_example_metrics_metrics_resource_timer_metric_fifteen_min_rate_per_second"); - assertMetricExists("application:io_quarkus_example_metrics_metrics_resource_timer_metric_min_seconds"); - assertMetricExists("application:io_quarkus_example_metrics_metrics_resource_timer_metric_max_seconds"); - assertMetricExists("application:io_quarkus_example_metrics_metrics_resource_timer_metric_mean_seconds"); - assertMetricExists("application:io_quarkus_example_metrics_metrics_resource_timer_metric_stddev_seconds"); + assertMetricExactValue(metricsPrefix + "metrics_resource_timer_metric_seconds_count", "1.0"); + assertMetricExists(metricsPrefix + "metrics_resource_timer_metric_seconds{quantile=\"0.5\"}"); + assertMetricExists(metricsPrefix + "metrics_resource_timer_metric_seconds{quantile=\"0.75\"}"); + assertMetricExists(metricsPrefix + "metrics_resource_timer_metric_seconds{quantile=\"0.95\"}"); + assertMetricExists(metricsPrefix + "metrics_resource_timer_metric_seconds{quantile=\"0.98\"}"); + assertMetricExists(metricsPrefix + "metrics_resource_timer_metric_seconds{quantile=\"0.99\"}"); + assertMetricExists(metricsPrefix + "metrics_resource_timer_metric_seconds{quantile=\"0.999\"}"); + assertMetricExists(metricsPrefix + "metrics_resource_timer_metric_one_min_rate_per_second"); + assertMetricExists(metricsPrefix + "metrics_resource_timer_metric_five_min_rate_per_second"); + assertMetricExists(metricsPrefix + "metrics_resource_timer_metric_fifteen_min_rate_per_second"); + assertMetricExists(metricsPrefix + "metrics_resource_timer_metric_min_seconds"); + assertMetricExists(metricsPrefix + "metrics_resource_timer_metric_max_seconds"); + assertMetricExists(metricsPrefix + "metrics_resource_timer_metric_mean_seconds"); + assertMetricExists(metricsPrefix + "metrics_resource_timer_metric_stddev_seconds"); } @Test public void testHistogram() { invokeHistogram(); - assertMetricExactValue("application:io_quarkus_example_metrics_metrics_resource_histogram_count", "1.0"); - assertMetricExactValue("application:io_quarkus_example_metrics_metrics_resource_histogram_min", "42.0"); - assertMetricExactValue("application:io_quarkus_example_metrics_metrics_resource_histogram_max", "42.0"); - assertMetricExactValue("application:io_quarkus_example_metrics_metrics_resource_histogram_mean", "42.0"); - assertMetricExactValue("application:io_quarkus_example_metrics_metrics_resource_histogram_stddev", "0.0"); - assertMetricExactValue("application:io_quarkus_example_metrics_metrics_resource_histogram{quantile=\"0.5\"}", "42.0"); - assertMetricExactValue("application:io_quarkus_example_metrics_metrics_resource_histogram{quantile=\"0.75\"}", "42.0"); - assertMetricExactValue("application:io_quarkus_example_metrics_metrics_resource_histogram{quantile=\"0.95\"}", "42.0"); - assertMetricExactValue("application:io_quarkus_example_metrics_metrics_resource_histogram{quantile=\"0.98\"}", "42.0"); - assertMetricExactValue("application:io_quarkus_example_metrics_metrics_resource_histogram{quantile=\"0.99\"}", "42.0"); - assertMetricExactValue("application:io_quarkus_example_metrics_metrics_resource_histogram{quantile=\"0.999\"}", "42.0"); + assertMetricExactValue(metricsPrefix + "metrics_resource_histogram_count", "1.0"); + assertMetricExactValue(metricsPrefix + "metrics_resource_histogram_min", "42.0"); + assertMetricExactValue(metricsPrefix + "metrics_resource_histogram_max", "42.0"); + assertMetricExactValue(metricsPrefix + "metrics_resource_histogram_mean", "42.0"); + assertMetricExactValue(metricsPrefix + "metrics_resource_histogram_stddev", "0.0"); + assertMetricExactValue(metricsPrefix + "metrics_resource_histogram{quantile=\"0.5\"}", "42.0"); + assertMetricExactValue(metricsPrefix + "metrics_resource_histogram{quantile=\"0.75\"}", "42.0"); + assertMetricExactValue(metricsPrefix + "metrics_resource_histogram{quantile=\"0.95\"}", "42.0"); + assertMetricExactValue(metricsPrefix + "metrics_resource_histogram{quantile=\"0.98\"}", "42.0"); + assertMetricExactValue(metricsPrefix + "metrics_resource_histogram{quantile=\"0.99\"}", "42.0"); + assertMetricExactValue(metricsPrefix + "metrics_resource_histogram{quantile=\"0.999\"}", "42.0"); } @Test @@ -100,7 +101,7 @@ public void testMetricWithAbsoluteName() { @Test public void testMetricWithCustomTags() { invokeCounterWithTags(); - assertMetricExactValue("application:io_quarkus_example_metrics_metrics_resource_counter_with_tags{foo=\"bar\"}", "1.0"); + assertMetricExactValue(metricsPrefix + "metrics_resource_counter_with_tags{foo=\"bar\"}", "1.0"); } @Test @@ -138,6 +139,15 @@ public void testVendorMetrics() { .body(containsString("vendor:memory_max_non_heap_bytes ")); } + /** + * A REST method with metrics is throwing a NotFoundException, so the client should receive 404. + */ + @Test + public void testEndpointWithMetricsThrowingException() { + RestAssured.when().get("/metricsresource/counter-throwing-not-found-exception").then() + .statusCode(404); + } + private void assertMetricExactValue(String name, String val) { RestAssured.when().get("/metrics").then() .body(containsString(name + " " + val)); diff --git a/integration-tests/main/src/test/java/io/quarkus/it/main/MicroProfileConfigITCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/MicroProfileConfigITCase.java new file mode 100644 index 0000000000000..d68b998638851 --- /dev/null +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/MicroProfileConfigITCase.java @@ -0,0 +1,24 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Licensed 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.it.main; + +import io.quarkus.test.junit.SubstrateTest; + +@SubstrateTest +public class MicroProfileConfigITCase extends MicroProfileConfigTestCase { + +} diff --git a/integration-tests/main/src/test/java/io/quarkus/example/test/LambdaTestCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/MicroProfileConfigTestCase.java similarity index 68% rename from integration-tests/main/src/test/java/io/quarkus/example/test/LambdaTestCase.java rename to integration-tests/main/src/test/java/io/quarkus/it/main/MicroProfileConfigTestCase.java index 612436e9457c1..294370748ca8c 100644 --- a/integration-tests/main/src/test/java/io/quarkus/example/test/LambdaTestCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/MicroProfileConfigTestCase.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 Red Hat, Inc. + * Copyright 2019 Red Hat, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.test; +package io.quarkus.it.main; import static org.hamcrest.Matchers.is; @@ -24,14 +24,13 @@ import io.restassured.RestAssured; @QuarkusTest -public class LambdaTestCase { +public class MicroProfileConfigTestCase { + + public static final String HEADER_NAME = "header-name"; @Test - public void testHelloLambda() { - RestAssured.with().body("{\"firstName\":\"Stuart\",\"lastName\":\"Douglas\"}") - .when().post("/HelloLambda") - .then() - .body(is("\"Hello Stuart Douglas.\"")); + public void testMicroprofileConfigGetPropertyNames() { + RestAssured.when().get("/microprofile-config/get-property-names").then() + .body(is("OK")); } - } diff --git a/integration-tests/main/src/test/java/io/quarkus/example/test/MockExternalService.java b/integration-tests/main/src/test/java/io/quarkus/it/main/MockExternalService.java similarity index 65% rename from integration-tests/main/src/test/java/io/quarkus/example/test/MockExternalService.java rename to integration-tests/main/src/test/java/io/quarkus/it/main/MockExternalService.java index bfc0ab511df8d..4ceee71db3009 100644 --- a/integration-tests/main/src/test/java/io/quarkus/example/test/MockExternalService.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/MockExternalService.java @@ -1,12 +1,10 @@ -package io.quarkus.example.test; +package io.quarkus.it.main; import javax.annotation.Priority; import javax.enterprise.context.ApplicationScoped; -import javax.enterprise.context.Dependent; import javax.enterprise.inject.Alternative; -import javax.interceptor.Interceptor; -import io.quarkus.example.rest.ExternalService; +import io.quarkus.it.rest.ExternalService; @Alternative() @Priority(1) diff --git a/integration-tests/main/src/test/java/io/quarkus/example/test/OpenApiITCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/OpenApiITCase.java similarity index 95% rename from integration-tests/main/src/test/java/io/quarkus/example/test/OpenApiITCase.java rename to integration-tests/main/src/test/java/io/quarkus/it/main/OpenApiITCase.java index c26f2e848a1d5..8b5af12c58b5a 100644 --- a/integration-tests/main/src/test/java/io/quarkus/example/test/OpenApiITCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/OpenApiITCase.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.test; +package io.quarkus.it.main; import io.quarkus.test.junit.SubstrateTest; diff --git a/integration-tests/main/src/test/java/io/quarkus/example/test/OpenApiTestCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/OpenApiTestCase.java similarity index 98% rename from integration-tests/main/src/test/java/io/quarkus/example/test/OpenApiTestCase.java rename to integration-tests/main/src/test/java/io/quarkus/it/main/OpenApiTestCase.java index cb012361a7344..22447bf2461f6 100644 --- a/integration-tests/main/src/test/java/io/quarkus/example/test/OpenApiTestCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/OpenApiTestCase.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.test; +package io.quarkus.it.main; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; diff --git a/integration-tests/main/src/test/java/io/quarkus/example/test/OpenTracingITCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/OpenTracingITCase.java similarity index 95% rename from integration-tests/main/src/test/java/io/quarkus/example/test/OpenTracingITCase.java rename to integration-tests/main/src/test/java/io/quarkus/it/main/OpenTracingITCase.java index 619afb35834a9..a67f8088e1973 100644 --- a/integration-tests/main/src/test/java/io/quarkus/example/test/OpenTracingITCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/OpenTracingITCase.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.test; +package io.quarkus.it.main; import io.quarkus.test.junit.SubstrateTest; diff --git a/integration-tests/main/src/test/java/io/quarkus/example/test/OpenTracingTestCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/OpenTracingTestCase.java similarity index 96% rename from integration-tests/main/src/test/java/io/quarkus/example/test/OpenTracingTestCase.java rename to integration-tests/main/src/test/java/io/quarkus/it/main/OpenTracingTestCase.java index a67a424831ecf..62b829c6a35de 100644 --- a/integration-tests/main/src/test/java/io/quarkus/example/test/OpenTracingTestCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/OpenTracingTestCase.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.test; +package io.quarkus.it.main; import static org.hamcrest.Matchers.is; diff --git a/integration-tests/main/src/test/java/io/quarkus/it/main/PropertyInjectionTestCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/PropertyInjectionTestCase.java new file mode 100644 index 0000000000000..ec9ef88ac6d8d --- /dev/null +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/PropertyInjectionTestCase.java @@ -0,0 +1,19 @@ +package io.quarkus.it.main; + +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +class PropertyInjectionTestCase { + + @ConfigProperty(name = "web-message") + String message; + + @Test + void testConfigPropertyInjectedIntoTest() { + Assertions.assertEquals("A message", message); + } +} diff --git a/integration-tests/main/src/test/java/io/quarkus/example/test/ReactiveStreamsOperatorsITCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/ReactiveStreamsOperatorsITCase.java similarity index 95% rename from integration-tests/main/src/test/java/io/quarkus/example/test/ReactiveStreamsOperatorsITCase.java rename to integration-tests/main/src/test/java/io/quarkus/it/main/ReactiveStreamsOperatorsITCase.java index 724683af0ba10..1b801e0346194 100644 --- a/integration-tests/main/src/test/java/io/quarkus/example/test/ReactiveStreamsOperatorsITCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/ReactiveStreamsOperatorsITCase.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.test; +package io.quarkus.it.main; import io.quarkus.test.junit.SubstrateTest; diff --git a/integration-tests/main/src/test/java/io/quarkus/example/test/ReactiveStreamsOperatorsTestCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/ReactiveStreamsOperatorsTestCase.java similarity index 97% rename from integration-tests/main/src/test/java/io/quarkus/example/test/ReactiveStreamsOperatorsTestCase.java rename to integration-tests/main/src/test/java/io/quarkus/it/main/ReactiveStreamsOperatorsTestCase.java index 254b866cc128a..65136f897dc40 100644 --- a/integration-tests/main/src/test/java/io/quarkus/example/test/ReactiveStreamsOperatorsTestCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/ReactiveStreamsOperatorsTestCase.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.test; +package io.quarkus.it.main; import static org.hamcrest.Matchers.is; diff --git a/integration-tests/main/src/test/java/io/quarkus/example/test/RequestScopeITCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/RequestScopeITCase.java similarity index 95% rename from integration-tests/main/src/test/java/io/quarkus/example/test/RequestScopeITCase.java rename to integration-tests/main/src/test/java/io/quarkus/it/main/RequestScopeITCase.java index 2f269cf8d0aa5..9edf11fc12a21 100644 --- a/integration-tests/main/src/test/java/io/quarkus/example/test/RequestScopeITCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/RequestScopeITCase.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.test; +package io.quarkus.it.main; import io.quarkus.test.junit.SubstrateTest; diff --git a/integration-tests/main/src/test/java/io/quarkus/example/test/RequestScopeTestCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/RequestScopeTestCase.java similarity index 96% rename from integration-tests/main/src/test/java/io/quarkus/example/test/RequestScopeTestCase.java rename to integration-tests/main/src/test/java/io/quarkus/it/main/RequestScopeTestCase.java index d4af8b119f23d..ad15df6045b72 100644 --- a/integration-tests/main/src/test/java/io/quarkus/example/test/RequestScopeTestCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/RequestScopeTestCase.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.test; +package io.quarkus.it.main; import static org.hamcrest.Matchers.is; diff --git a/integration-tests/main/src/test/java/io/quarkus/example/test/RestClientITCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/RestClientITCase.java similarity index 95% rename from integration-tests/main/src/test/java/io/quarkus/example/test/RestClientITCase.java rename to integration-tests/main/src/test/java/io/quarkus/it/main/RestClientITCase.java index f7417e5df6dea..874460edf0b00 100644 --- a/integration-tests/main/src/test/java/io/quarkus/example/test/RestClientITCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/RestClientITCase.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.test; +package io.quarkus.it.main; import io.quarkus.test.junit.SubstrateTest; diff --git a/integration-tests/main/src/test/java/io/quarkus/example/test/RestClientTestCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/RestClientTestCase.java similarity index 77% rename from integration-tests/main/src/test/java/io/quarkus/example/test/RestClientTestCase.java rename to integration-tests/main/src/test/java/io/quarkus/it/main/RestClientTestCase.java index 430938d1f8e14..a1dddf6a9d244 100644 --- a/integration-tests/main/src/test/java/io/quarkus/example/test/RestClientTestCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/RestClientTestCase.java @@ -14,10 +14,9 @@ * limitations under the License. */ -package io.quarkus.example.test; +package io.quarkus.it.main; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.*; import java.util.List; import java.util.Map; @@ -26,7 +25,6 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import io.quarkus.example.rest.ComponentType; import io.quarkus.test.junit.QuarkusTest; import io.restassured.RestAssured; import io.restassured.path.json.JsonPath; @@ -34,6 +32,8 @@ @QuarkusTest public class RestClientTestCase { + public static final String HEADER_NAME = "header-name"; + @Test public void testMicroprofileClient() { RestAssured.when().get("/client/manual").then() @@ -78,6 +78,26 @@ void testMicroprofileClientComplexCdi() { Assertions.assertEquals(map.get("value"), "component value"); } + @Test + void testMicroprofileCdiClientHeaderPassing() { + String headerValue = "some-not-at-all-random-header-value"; + RestAssured + .given().header(HEADER_NAME, headerValue) + .when().get("/client/manual/headers") + .then().header("Content-Type", "application/json") + .body(HEADER_NAME, equalTo(headerValue)); + } + + @Test + void testMicroprofileClientHeaderPassing() { + String headerValue = "some-not-at-all-random-header-value"; + RestAssured + .given().header(HEADER_NAME, headerValue) + .when().get("/client/cdi/headers") + .then().header("Content-Type", "application/json") + .body(HEADER_NAME, equalTo(headerValue)); + } + /** * Disabled by default as it establishes external connections. *

diff --git a/integration-tests/main/src/test/java/io/quarkus/example/test/ServletITCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/ServletITCase.java similarity index 95% rename from integration-tests/main/src/test/java/io/quarkus/example/test/ServletITCase.java rename to integration-tests/main/src/test/java/io/quarkus/it/main/ServletITCase.java index a0cca679263ab..8a8b5c818b4f2 100644 --- a/integration-tests/main/src/test/java/io/quarkus/example/test/ServletITCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/ServletITCase.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.test; +package io.quarkus.it.main; import io.quarkus.test.junit.SubstrateTest; diff --git a/integration-tests/main/src/test/java/io/quarkus/example/test/ServletTestCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/ServletTestCase.java similarity index 89% rename from integration-tests/main/src/test/java/io/quarkus/example/test/ServletTestCase.java rename to integration-tests/main/src/test/java/io/quarkus/it/main/ServletTestCase.java index 799c3d0fe3eb3..db946e5fbcef5 100644 --- a/integration-tests/main/src/test/java/io/quarkus/example/test/ServletTestCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/ServletTestCase.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.test; +package io.quarkus.it.main; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; @@ -65,4 +65,11 @@ public void testSecureAccessSuccess() { .when().get("/secure-test").then() .statusCode(200); } + + @Test + public void testWebjars() { + RestAssured + .when().get("webjars/bootstrap/3.1.0/css/bootstrap.min.css").then() + .statusCode(200); + } } diff --git a/integration-tests/main/src/test/java/io/quarkus/example/test/TestMockTestCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/TestMockTestCase.java similarity index 96% rename from integration-tests/main/src/test/java/io/quarkus/example/test/TestMockTestCase.java rename to integration-tests/main/src/test/java/io/quarkus/it/main/TestMockTestCase.java index d33f08faba2b4..4cfb749bbbc4e 100644 --- a/integration-tests/main/src/test/java/io/quarkus/example/test/TestMockTestCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/TestMockTestCase.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.test; +package io.quarkus.it.main; import static org.hamcrest.Matchers.is; diff --git a/integration-tests/hibernate-orm-panache/src/test/java/io/quarkus/example/test/TestResources.java b/integration-tests/main/src/test/java/io/quarkus/it/main/TestResources.java similarity index 95% rename from integration-tests/hibernate-orm-panache/src/test/java/io/quarkus/example/test/TestResources.java rename to integration-tests/main/src/test/java/io/quarkus/it/main/TestResources.java index 06653064904e9..3a2e96db38218 100644 --- a/integration-tests/hibernate-orm-panache/src/test/java/io/quarkus/example/test/TestResources.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/TestResources.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.test; +package io.quarkus.it.main; import io.quarkus.test.common.QuarkusTestResource; import io.quarkus.test.h2.H2DatabaseTestResource; diff --git a/integration-tests/main/src/test/java/io/quarkus/it/main/TestResteasyConstructorsTestCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/TestResteasyConstructorsTestCase.java new file mode 100644 index 0000000000000..caf90171e6798 --- /dev/null +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/TestResteasyConstructorsTestCase.java @@ -0,0 +1,22 @@ +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 TestResteasyConstructorsTestCase { + + @Test + public void testWithoutDefaultConstructor() { + RestAssured.when().get("/testCtor/service").then().body(is("some")); + } + + @Test + public void testWithDefaultConstructor() { + RestAssured.when().get("/testCtor2/service").then().body(is("some")); + } +} diff --git a/integration-tests/main/src/test/java/io/quarkus/example/test/TransactionITCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/TransactionITCase.java similarity index 95% rename from integration-tests/main/src/test/java/io/quarkus/example/test/TransactionITCase.java rename to integration-tests/main/src/test/java/io/quarkus/it/main/TransactionITCase.java index 345488f238cfe..d6d32c9e3594b 100644 --- a/integration-tests/main/src/test/java/io/quarkus/example/test/TransactionITCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/TransactionITCase.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.test; +package io.quarkus.it.main; import io.quarkus.test.junit.SubstrateTest; diff --git a/integration-tests/main/src/test/java/io/quarkus/example/test/TransactionTestCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/TransactionTestCase.java similarity index 96% rename from integration-tests/main/src/test/java/io/quarkus/example/test/TransactionTestCase.java rename to integration-tests/main/src/test/java/io/quarkus/it/main/TransactionTestCase.java index 9433c14c1e43b..be9e9a864912a 100644 --- a/integration-tests/main/src/test/java/io/quarkus/example/test/TransactionTestCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/TransactionTestCase.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.test; +package io.quarkus.it.main; import static org.hamcrest.Matchers.is; diff --git a/integration-tests/main/src/test/java/io/quarkus/example/test/ValidatorITCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/ValidatorITCase.java similarity index 95% rename from integration-tests/main/src/test/java/io/quarkus/example/test/ValidatorITCase.java rename to integration-tests/main/src/test/java/io/quarkus/it/main/ValidatorITCase.java index 730b6334d5b64..5b7ecece1ea5e 100644 --- a/integration-tests/main/src/test/java/io/quarkus/example/test/ValidatorITCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/ValidatorITCase.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.test; +package io.quarkus.it.main; import io.quarkus.test.junit.SubstrateTest; diff --git a/integration-tests/main/src/test/java/io/quarkus/example/test/ValidatorTestCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/ValidatorTestCase.java similarity index 98% rename from integration-tests/main/src/test/java/io/quarkus/example/test/ValidatorTestCase.java rename to integration-tests/main/src/test/java/io/quarkus/it/main/ValidatorTestCase.java index c5c0b9e756411..f629651155cae 100644 --- a/integration-tests/main/src/test/java/io/quarkus/example/test/ValidatorTestCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/ValidatorTestCase.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.test; +package io.quarkus.it.main; import java.io.ByteArrayOutputStream; import java.io.InputStream; diff --git a/integration-tests/main/src/test/java/io/quarkus/example/test/WebsocketITCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/WebsocketITCase.java similarity index 95% rename from integration-tests/main/src/test/java/io/quarkus/example/test/WebsocketITCase.java rename to integration-tests/main/src/test/java/io/quarkus/it/main/WebsocketITCase.java index 076491a149a2a..25cd5db6e03b4 100644 --- a/integration-tests/main/src/test/java/io/quarkus/example/test/WebsocketITCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/WebsocketITCase.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.test; +package io.quarkus.it.main; import io.quarkus.test.junit.SubstrateTest; diff --git a/integration-tests/main/src/test/java/io/quarkus/example/test/WebsocketTestCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/WebsocketTestCase.java similarity index 98% rename from integration-tests/main/src/test/java/io/quarkus/example/test/WebsocketTestCase.java rename to integration-tests/main/src/test/java/io/quarkus/it/main/WebsocketTestCase.java index fdb886cb20ae4..d8c9b4b6f0173 100644 --- a/integration-tests/main/src/test/java/io/quarkus/example/test/WebsocketTestCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/WebsocketTestCase.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.test; +package io.quarkus.it.main; import java.net.URI; import java.util.concurrent.LinkedBlockingDeque; diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 351d732ed58ef..730cf7d286369 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -48,12 +48,46 @@ jpa-h2 jpa-mssql hibernate-orm-panache + hibernate-search-elasticsearch vertx spring-di infinispan-cache-jpa - infinispan-cache-jpa-stress elytron-security camel-core camel-salesforce + camel-aws-s3 + flyway + keycloak + reactive-pg-client + test-extension + amazon-lambda + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + ${nexus-staging-maven-plugin.version} + + true + + + + + + + + + io.quarkus + quarkus-integration-test-class-transformer-deployment + ${project.version} + + + io.quarkus + quarkus-integration-test-class-transformer + ${project.version} + + + diff --git a/integration-tests/reactive-pg-client/pom.xml b/integration-tests/reactive-pg-client/pom.xml new file mode 100644 index 0000000000000..19ae05a1378ac --- /dev/null +++ b/integration-tests/reactive-pg-client/pom.xml @@ -0,0 +1,239 @@ + + + + + 4.0.0 + + + quarkus-integration-tests-parent + io.quarkus + 999-SNAPSHOT + ../ + + + quarkus-integration-test-reactive-pg-client + + Quarkus - Integration Tests - Reactive Pg Client + + + vertx-reactive:postgresql:///hibernate_orm_test + + + + + io.quarkus + quarkus-resteasy + + + io.quarkus + quarkus-reactive-pg-client + + + + io.quarkus + quarkus-junit5 + test + + + io.rest-assured + rest-assured + test + + + + + + + src/main/resources + true + + + + + maven-surefire-plugin + + true + + + + maven-failsafe-plugin + + true + + + + ${project.groupId} + quarkus-maven-plugin + ${project.version} + + + + build + + + + + + + + + + test-postgresql + + + test-postgresql + + + + + + 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 + + + + + + + + ${project.groupId} + quarkus-maven-plugin + ${project.version} + + + native-image + + native-image + + + true + true + ${graalvmHome} + + + + + + + + + + docker-postgresql + + + docker + + + + vertx-reactive:postgresql://:5431/hibernate_orm_test + + + + + io.fabric8 + docker-maven-plugin + + + + postgres:10.5 + postgresql + + + hibernate_orm_test + hibernate_orm_test + hibernate_orm_test + + + 5431:5432 + + + + mapped + + 5432 + + + + + + + + + true + + + + docker-start + compile + + stop + start + + + + docker-stop + post-integration-test + + stop + + + + + + + + + + diff --git a/integration-tests/reactive-pg-client/src/main/java/io/quarkus/it/reactive/pg/client/FruitResource.java b/integration-tests/reactive-pg-client/src/main/java/io/quarkus/it/reactive/pg/client/FruitResource.java new file mode 100644 index 0000000000000..4dc116adb980a --- /dev/null +++ b/integration-tests/reactive-pg-client/src/main/java/io/quarkus/it/reactive/pg/client/FruitResource.java @@ -0,0 +1,72 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Red Hat 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.it.reactive.pg.client; + +import java.util.concurrent.CompletionStage; + +import javax.annotation.PostConstruct; +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 io.reactiverse.axle.pgclient.PgIterator; +import io.reactiverse.axle.pgclient.PgPool; +import io.reactiverse.axle.pgclient.Row; +import io.vertx.core.json.JsonArray; +import io.vertx.core.json.JsonObject; + +@Path("/fruits") +public class FruitResource { + + @Inject + PgPool client; + + @PostConstruct + void setupDb() { + client.query("DROP TABLE IF EXISTS fruits") + .thenCompose(r -> client.query("CREATE TABLE fruits (id SERIAL PRIMARY KEY, name TEXT NOT NULL)")) + .thenCompose(r -> client.query("INSERT INTO fruits (name) VALUES ('Orange')")) + .thenCompose(r -> client.query("INSERT INTO fruits (name) VALUES ('Pear')")) + .thenCompose(r -> client.query("INSERT INTO fruits (name) VALUES ('Apple')")) + .toCompletableFuture() + .join(); + } + + @GET + @Produces(MediaType.APPLICATION_JSON) + public CompletionStage listFruits() { + return client.query("SELECT * FROM fruits") + .thenApply(pgRowSet -> { + JsonArray jsonArray = new JsonArray(); + PgIterator iterator = pgRowSet.iterator(); + while (iterator.hasNext()) { + Row row = iterator.next(); + jsonArray.add(toJson(row)); + } + return jsonArray; + }); + } + + private JsonObject toJson(Row row) { + return new JsonObject() + .put("id", row.getLong("id")) + .put("name", row.getString("name")); + } + +} diff --git a/integration-tests/reactive-pg-client/src/main/resources/application.properties b/integration-tests/reactive-pg-client/src/main/resources/application.properties new file mode 100644 index 0000000000000..d2a8018b61264 --- /dev/null +++ b/integration-tests/reactive-pg-client/src/main/resources/application.properties @@ -0,0 +1,3 @@ +quarkus.datasource.url=${postgres.url} +quarkus.datasource.username=hibernate_orm_test +quarkus.datasource.password=hibernate_orm_test diff --git a/integration-tests/reactive-pg-client/src/test/java/io/quarkus/it/reactive/pg/client/FruitsEndpointTest.java b/integration-tests/reactive-pg-client/src/test/java/io/quarkus/it/reactive/pg/client/FruitsEndpointTest.java new file mode 100644 index 0000000000000..740f5fd34ff55 --- /dev/null +++ b/integration-tests/reactive-pg-client/src/test/java/io/quarkus/it/reactive/pg/client/FruitsEndpointTest.java @@ -0,0 +1,41 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Red Hat 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.it.reactive.pg.client; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.CoreMatchers.containsString; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +public class FruitsEndpointTest { + + @Test + public void testListAllFruits() { + given() + .when().get("/fruits") + .then() + .statusCode(200) + .body( + containsString("Orange"), + containsString("Pear"), + containsString("Apple")); + } + +} diff --git a/integration-tests/reactive-pg-client/src/test/java/io/quarkus/it/reactive/pg/client/NativeFruitsEndpointIT.java b/integration-tests/reactive-pg-client/src/test/java/io/quarkus/it/reactive/pg/client/NativeFruitsEndpointIT.java new file mode 100644 index 0000000000000..6fdc94cc0c86d --- /dev/null +++ b/integration-tests/reactive-pg-client/src/test/java/io/quarkus/it/reactive/pg/client/NativeFruitsEndpointIT.java @@ -0,0 +1,26 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Red Hat 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.it.reactive.pg.client; + +import io.quarkus.test.junit.SubstrateTest; + +@SubstrateTest +public class NativeFruitsEndpointIT extends FruitsEndpointTest { + + // Runs the same tests as the parent class + +} diff --git a/integration-tests/shared-library/src/main/java/io/quarkus/example/shared/SharedResource.java b/integration-tests/shared-library/src/main/java/io/quarkus/it/shared/SharedResource.java similarity index 95% rename from integration-tests/shared-library/src/main/java/io/quarkus/example/shared/SharedResource.java rename to integration-tests/shared-library/src/main/java/io/quarkus/it/shared/SharedResource.java index 61e32c16b3b73..89d13d5517c7e 100644 --- a/integration-tests/shared-library/src/main/java/io/quarkus/example/shared/SharedResource.java +++ b/integration-tests/shared-library/src/main/java/io/quarkus/it/shared/SharedResource.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.example.shared; +package io.quarkus.it.shared; import javax.ws.rs.GET; import javax.ws.rs.Path; diff --git a/integration-tests/spring-di/pom.xml b/integration-tests/spring-di/pom.xml index a398f88ab4f38..ebb4f65ca3517 100644 --- a/integration-tests/spring-di/pom.xml +++ b/integration-tests/spring-di/pom.xml @@ -33,24 +33,21 @@ io.quarkus quarkus-resteasy - provided io.quarkus quarkus-arc - provided io.quarkus quarkus-spring-di - provided io.quarkus quarkus-undertow provided - + org.springframework spring-context diff --git a/integration-tests/spring-di/src/main/java/io/quarkus/spring/tests/AppConfiguration.java b/integration-tests/spring-di/src/main/java/io/quarkus/it/spring/AppConfiguration.java similarity index 96% rename from integration-tests/spring-di/src/main/java/io/quarkus/spring/tests/AppConfiguration.java rename to integration-tests/spring-di/src/main/java/io/quarkus/it/spring/AppConfiguration.java index 9edada147fd44..0fc7cba3423d6 100644 --- a/integration-tests/spring-di/src/main/java/io/quarkus/spring/tests/AppConfiguration.java +++ b/integration-tests/spring-di/src/main/java/io/quarkus/it/spring/AppConfiguration.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.spring.tests; +package io.quarkus.it.spring; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; diff --git a/integration-tests/spring-di/src/main/java/io/quarkus/spring/tests/Dummy.java b/integration-tests/spring-di/src/main/java/io/quarkus/it/spring/Dummy.java similarity index 95% rename from integration-tests/spring-di/src/main/java/io/quarkus/spring/tests/Dummy.java rename to integration-tests/spring-di/src/main/java/io/quarkus/it/spring/Dummy.java index e0a82120b651c..3efb538f3a5af 100644 --- a/integration-tests/spring-di/src/main/java/io/quarkus/spring/tests/Dummy.java +++ b/integration-tests/spring-di/src/main/java/io/quarkus/it/spring/Dummy.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.spring.tests; +package io.quarkus.it.spring; import org.springframework.stereotype.Repository; diff --git a/integration-tests/spring-di/src/main/java/io/quarkus/spring/tests/GreeterBean.java b/integration-tests/spring-di/src/main/java/io/quarkus/it/spring/GreeterBean.java similarity index 97% rename from integration-tests/spring-di/src/main/java/io/quarkus/spring/tests/GreeterBean.java rename to integration-tests/spring-di/src/main/java/io/quarkus/it/spring/GreeterBean.java index ab7480fc17e8c..6e5a7891da29b 100644 --- a/integration-tests/spring-di/src/main/java/io/quarkus/spring/tests/GreeterBean.java +++ b/integration-tests/spring-di/src/main/java/io/quarkus/it/spring/GreeterBean.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.spring.tests; +package io.quarkus.it.spring; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; diff --git a/integration-tests/spring-di/src/main/java/io/quarkus/spring/tests/InjectedSpringBeansResource.java b/integration-tests/spring-di/src/main/java/io/quarkus/it/spring/InjectedSpringBeansResource.java similarity index 97% rename from integration-tests/spring-di/src/main/java/io/quarkus/spring/tests/InjectedSpringBeansResource.java rename to integration-tests/spring-di/src/main/java/io/quarkus/it/spring/InjectedSpringBeansResource.java index 5e765ce8494e7..8e9a5c9d2a963 100644 --- a/integration-tests/spring-di/src/main/java/io/quarkus/spring/tests/InjectedSpringBeansResource.java +++ b/integration-tests/spring-di/src/main/java/io/quarkus/it/spring/InjectedSpringBeansResource.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.spring.tests; +package io.quarkus.it.spring; import javax.inject.Inject; import javax.servlet.http.HttpServletRequest; diff --git a/integration-tests/spring-di/src/main/java/io/quarkus/spring/tests/MessageProducer.java b/integration-tests/spring-di/src/main/java/io/quarkus/it/spring/MessageProducer.java similarity index 96% rename from integration-tests/spring-di/src/main/java/io/quarkus/spring/tests/MessageProducer.java rename to integration-tests/spring-di/src/main/java/io/quarkus/it/spring/MessageProducer.java index 150db7129234a..f0a3f9d8a347e 100644 --- a/integration-tests/spring-di/src/main/java/io/quarkus/spring/tests/MessageProducer.java +++ b/integration-tests/spring-di/src/main/java/io/quarkus/it/spring/MessageProducer.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.spring.tests; +package io.quarkus.it.spring; import org.springframework.beans.factory.annotation.Value; diff --git a/integration-tests/spring-di/src/main/java/io/quarkus/spring/tests/MultiplierStringFunction.java b/integration-tests/spring-di/src/main/java/io/quarkus/it/spring/MultiplierStringFunction.java similarity index 96% rename from integration-tests/spring-di/src/main/java/io/quarkus/spring/tests/MultiplierStringFunction.java rename to integration-tests/spring-di/src/main/java/io/quarkus/it/spring/MultiplierStringFunction.java index 08c9d35e72690..930c02250751e 100644 --- a/integration-tests/spring-di/src/main/java/io/quarkus/spring/tests/MultiplierStringFunction.java +++ b/integration-tests/spring-di/src/main/java/io/quarkus/it/spring/MultiplierStringFunction.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.spring.tests; +package io.quarkus.it.spring; import org.springframework.stereotype.Component; diff --git a/integration-tests/spring-di/src/main/java/io/quarkus/spring/tests/MyApplication.java b/integration-tests/spring-di/src/main/java/io/quarkus/it/spring/MyApplication.java similarity index 95% rename from integration-tests/spring-di/src/main/java/io/quarkus/spring/tests/MyApplication.java rename to integration-tests/spring-di/src/main/java/io/quarkus/it/spring/MyApplication.java index f97a559d964b4..1b682f65cfd5a 100644 --- a/integration-tests/spring-di/src/main/java/io/quarkus/spring/tests/MyApplication.java +++ b/integration-tests/spring-di/src/main/java/io/quarkus/it/spring/MyApplication.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.spring.tests; +package io.quarkus.it.spring; import javax.ws.rs.ApplicationPath; import javax.ws.rs.core.Application; diff --git a/integration-tests/spring-di/src/main/java/io/quarkus/spring/tests/MyService.java b/integration-tests/spring-di/src/main/java/io/quarkus/it/spring/MyService.java similarity index 96% rename from integration-tests/spring-di/src/main/java/io/quarkus/spring/tests/MyService.java rename to integration-tests/spring-di/src/main/java/io/quarkus/it/spring/MyService.java index 6a4fa61838d49..199fa1272eed0 100644 --- a/integration-tests/spring-di/src/main/java/io/quarkus/spring/tests/MyService.java +++ b/integration-tests/spring-di/src/main/java/io/quarkus/it/spring/MyService.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.spring.tests; +package io.quarkus.it.spring; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; diff --git a/integration-tests/spring-di/src/main/java/io/quarkus/spring/tests/NoOpSingleStringFunction.java b/integration-tests/spring-di/src/main/java/io/quarkus/it/spring/NoOpSingleStringFunction.java similarity index 96% rename from integration-tests/spring-di/src/main/java/io/quarkus/spring/tests/NoOpSingleStringFunction.java rename to integration-tests/spring-di/src/main/java/io/quarkus/it/spring/NoOpSingleStringFunction.java index 995cbdadfd1c1..834663b1135e4 100644 --- a/integration-tests/spring-di/src/main/java/io/quarkus/spring/tests/NoOpSingleStringFunction.java +++ b/integration-tests/spring-di/src/main/java/io/quarkus/it/spring/NoOpSingleStringFunction.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.spring.tests; +package io.quarkus.it.spring; import org.springframework.stereotype.Component; diff --git a/integration-tests/spring-di/src/main/java/io/quarkus/spring/tests/OtherDummy.java b/integration-tests/spring-di/src/main/java/io/quarkus/it/spring/OtherDummy.java similarity index 96% rename from integration-tests/spring-di/src/main/java/io/quarkus/spring/tests/OtherDummy.java rename to integration-tests/spring-di/src/main/java/io/quarkus/it/spring/OtherDummy.java index f9c3d282dfbdb..493153ba6ce24 100644 --- a/integration-tests/spring-di/src/main/java/io/quarkus/spring/tests/OtherDummy.java +++ b/integration-tests/spring-di/src/main/java/io/quarkus/it/spring/OtherDummy.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.spring.tests; +package io.quarkus.it.spring; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; diff --git a/integration-tests/spring-di/src/main/java/io/quarkus/spring/tests/PrototypeService.java b/integration-tests/spring-di/src/main/java/io/quarkus/it/spring/PrototypeService.java similarity index 96% rename from integration-tests/spring-di/src/main/java/io/quarkus/spring/tests/PrototypeService.java rename to integration-tests/spring-di/src/main/java/io/quarkus/it/spring/PrototypeService.java index bf834bedadb6f..d60d410e474e5 100644 --- a/integration-tests/spring-di/src/main/java/io/quarkus/spring/tests/PrototypeService.java +++ b/integration-tests/spring-di/src/main/java/io/quarkus/it/spring/PrototypeService.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.spring.tests; +package io.quarkus.it.spring; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; diff --git a/integration-tests/spring-di/src/main/java/io/quarkus/spring/tests/RequestBean.java b/integration-tests/spring-di/src/main/java/io/quarkus/it/spring/RequestBean.java similarity index 96% rename from integration-tests/spring-di/src/main/java/io/quarkus/spring/tests/RequestBean.java rename to integration-tests/spring-di/src/main/java/io/quarkus/it/spring/RequestBean.java index 0594015fbc334..fa828018adc3b 100644 --- a/integration-tests/spring-di/src/main/java/io/quarkus/spring/tests/RequestBean.java +++ b/integration-tests/spring-di/src/main/java/io/quarkus/it/spring/RequestBean.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.spring.tests; +package io.quarkus.it.spring; import java.util.concurrent.atomic.AtomicInteger; diff --git a/integration-tests/spring-di/src/main/java/io/quarkus/spring/tests/RequestService.java b/integration-tests/spring-di/src/main/java/io/quarkus/it/spring/RequestService.java similarity index 96% rename from integration-tests/spring-di/src/main/java/io/quarkus/spring/tests/RequestService.java rename to integration-tests/spring-di/src/main/java/io/quarkus/it/spring/RequestService.java index b17602fa187b0..15e34e8d046d8 100644 --- a/integration-tests/spring-di/src/main/java/io/quarkus/spring/tests/RequestService.java +++ b/integration-tests/spring-di/src/main/java/io/quarkus/it/spring/RequestService.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.spring.tests; +package io.quarkus.it.spring; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; diff --git a/integration-tests/spring-di/src/main/java/io/quarkus/spring/tests/SessionBean.java b/integration-tests/spring-di/src/main/java/io/quarkus/it/spring/SessionBean.java similarity index 96% rename from integration-tests/spring-di/src/main/java/io/quarkus/spring/tests/SessionBean.java rename to integration-tests/spring-di/src/main/java/io/quarkus/it/spring/SessionBean.java index 47f9e0eaa4485..4bf78c69d96d1 100644 --- a/integration-tests/spring-di/src/main/java/io/quarkus/spring/tests/SessionBean.java +++ b/integration-tests/spring-di/src/main/java/io/quarkus/it/spring/SessionBean.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.spring.tests; +package io.quarkus.it.spring; import java.util.concurrent.atomic.AtomicInteger; diff --git a/integration-tests/spring-di/src/main/java/io/quarkus/spring/tests/SessionService.java b/integration-tests/spring-di/src/main/java/io/quarkus/it/spring/SessionService.java similarity index 97% rename from integration-tests/spring-di/src/main/java/io/quarkus/spring/tests/SessionService.java rename to integration-tests/spring-di/src/main/java/io/quarkus/it/spring/SessionService.java index e988386f7d245..a4b9e87e0d024 100644 --- a/integration-tests/spring-di/src/main/java/io/quarkus/spring/tests/SessionService.java +++ b/integration-tests/spring-di/src/main/java/io/quarkus/it/spring/SessionService.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.spring.tests; +package io.quarkus.it.spring; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; diff --git a/integration-tests/spring-di/src/main/java/io/quarkus/spring/tests/StringFunction.java b/integration-tests/spring-di/src/main/java/io/quarkus/it/spring/StringFunction.java similarity index 95% rename from integration-tests/spring-di/src/main/java/io/quarkus/spring/tests/StringFunction.java rename to integration-tests/spring-di/src/main/java/io/quarkus/it/spring/StringFunction.java index 4a85a5d63aec7..3dbcee27006ae 100644 --- a/integration-tests/spring-di/src/main/java/io/quarkus/spring/tests/StringFunction.java +++ b/integration-tests/spring-di/src/main/java/io/quarkus/it/spring/StringFunction.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.spring.tests; +package io.quarkus.it.spring; import java.util.function.Function; diff --git a/integration-tests/spring-di/src/test/java/io/quarkus/spring/tests/InjectedSpringBeansResourceIT.java b/integration-tests/spring-di/src/test/java/io/quarkus/it/spring/InjectedSpringBeansResourceIT.java similarity index 95% rename from integration-tests/spring-di/src/test/java/io/quarkus/spring/tests/InjectedSpringBeansResourceIT.java rename to integration-tests/spring-di/src/test/java/io/quarkus/it/spring/InjectedSpringBeansResourceIT.java index 27f0397726e71..eb7ae198b38bc 100644 --- a/integration-tests/spring-di/src/test/java/io/quarkus/spring/tests/InjectedSpringBeansResourceIT.java +++ b/integration-tests/spring-di/src/test/java/io/quarkus/it/spring/InjectedSpringBeansResourceIT.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.spring.tests; +package io.quarkus.it.spring; import io.quarkus.test.junit.QuarkusTest; diff --git a/integration-tests/spring-di/src/test/java/io/quarkus/spring/tests/InjectedSpringBeansResourceTest.java b/integration-tests/spring-di/src/test/java/io/quarkus/it/spring/InjectedSpringBeansResourceTest.java similarity index 98% rename from integration-tests/spring-di/src/test/java/io/quarkus/spring/tests/InjectedSpringBeansResourceTest.java rename to integration-tests/spring-di/src/test/java/io/quarkus/it/spring/InjectedSpringBeansResourceTest.java index ae52ca63e762c..fc0f02399ee73 100644 --- a/integration-tests/spring-di/src/test/java/io/quarkus/spring/tests/InjectedSpringBeansResourceTest.java +++ b/integration-tests/spring-di/src/test/java/io/quarkus/it/spring/InjectedSpringBeansResourceTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.quarkus.spring.tests; +package io.quarkus.it.spring; import static io.restassured.RestAssured.when; import static org.hamcrest.Matchers.containsString; diff --git a/integration-tests/test-extension/pom.xml b/integration-tests/test-extension/pom.xml new file mode 100644 index 0000000000000..7050b795510b2 --- /dev/null +++ b/integration-tests/test-extension/pom.xml @@ -0,0 +1,135 @@ + + + + + + quarkus-integration-tests-parent + io.quarkus + 999-SNAPSHOT + ../ + + 4.0.0 + + quarkus-test-extension-nativetests + Quarkus - Integration Tests - Test Extension Native Tests + + + + io.quarkus + quarkus-undertow + + + io.quarkus + quarkus-test-extension + ${project.version} + + + + + io.quarkus + quarkus-junit5 + test + + + io.rest-assured + rest-assured + test + + + + + + + src/main/resources + true + + + + + ${project.groupId} + quarkus-maven-plugin + + + + build + + + true + + + + + + + + + + native-image + + + native + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + verify + + + + ${project.build.directory}/${project.build.finalName}-runner + + + + + + + ${project.groupId} + quarkus-maven-plugin + + + native-image + + native-image + + + true + true + + ${graalvmHome} + + + + + + + + + + + diff --git a/integration-tests/test-extension/src/main/java/io/quarkus/it/extension/FinalFieldReflectionTestEndpoint.java b/integration-tests/test-extension/src/main/java/io/quarkus/it/extension/FinalFieldReflectionTestEndpoint.java new file mode 100644 index 0000000000000..d47ee35231c32 --- /dev/null +++ b/integration-tests/test-extension/src/main/java/io/quarkus/it/extension/FinalFieldReflectionTestEndpoint.java @@ -0,0 +1,80 @@ +/* + * Copyright 2018 Red Hat, Inc. + * + * Licensed 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.it.extension; + +import static java.lang.String.format; + +import java.io.IOException; +import java.io.PrintWriter; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import io.quarkus.extest.runtime.FinalFieldReflectionObject; + +/** + * Final field reflection functionality test + */ +@WebServlet(name = "FinalFieldReflectionTestEndpoint", urlPatterns = "/core/reflection/final") +public class FinalFieldReflectionTestEndpoint extends HttpServlet { + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { + reflectiveSetterInvoke(resp); + resp.getWriter().write("OK"); + } + + private void reflectiveSetterInvoke(HttpServletResponse resp) throws IOException { + try { + FinalFieldReflectionObject nominalInstance = new FinalFieldReflectionObject(); + Field field = nominalInstance.getClass().getDeclaredField("value"); + field.setAccessible(true); + field.set(nominalInstance, "OK"); + + Method getValue = nominalInstance.getClass().getMethod("getValue"); + Object value = getValue.invoke(nominalInstance); + if (!"OK".equals(value)) { + final PrintWriter writer = resp.getWriter(); + writer.write(format("field incorrectly set, expecting 'OK', got '%s'", value)); + writer.append("\n\t"); + } + } catch (Exception e) { + reportException(e, resp); + } + } + + private void reportException(final Exception e, final HttpServletResponse resp) throws IOException { + reportException(null, e, resp); + } + + private void reportException(String errorMessage, final Exception e, final HttpServletResponse resp) throws IOException { + final PrintWriter writer = resp.getWriter(); + if (errorMessage != null) { + writer.write(errorMessage); + writer.write(" "); + } + writer.write(e.toString()); + writer.append("\n\t"); + e.printStackTrace(writer); + writer.append("\n\t"); + } + +} diff --git a/integration-tests/test-extension/src/main/java/io/quarkus/it/extension/NativeBean.java b/integration-tests/test-extension/src/main/java/io/quarkus/it/extension/NativeBean.java new file mode 100644 index 0000000000000..c37d09d20f0e5 --- /dev/null +++ b/integration-tests/test-extension/src/main/java/io/quarkus/it/extension/NativeBean.java @@ -0,0 +1,46 @@ +package io.quarkus.it.extension; + +import javax.enterprise.event.Observes; + +import io.quarkus.extest.runtime.IConfigConsumer; +import io.quarkus.extest.runtime.TestAnnotation; +import io.quarkus.extest.runtime.config.TestBuildAndRunTimeConfig; +import io.quarkus.extest.runtime.config.TestRunTimeConfig; +import io.quarkus.runtime.ShutdownEvent; +import io.quarkus.runtime.StartupEvent; + +@TestAnnotation +public class NativeBean implements IConfigConsumer { + TestRunTimeConfig runTimeConfig; + TestBuildAndRunTimeConfig buildTimeConfig; + + public NativeBean() { + System.out.printf("NativeBean.ctor, %s%n", super.toString()); + } + + /** + * Called by runtime with the runtime config object + * + * @param runTimeConfig + */ + @Override + public void loadConfig(TestBuildAndRunTimeConfig buildTimeConfig, TestRunTimeConfig runTimeConfig) { + System.out.printf("loadConfig, buildTimeConfig=%s, runTimeConfig=%s%n", buildTimeConfig, runTimeConfig); + this.buildTimeConfig = buildTimeConfig; + this.runTimeConfig = runTimeConfig; + } + + /** + * Called when the runtime has started + * + * @param event + */ + void onStart(@Observes StartupEvent event) { + System.out.printf("onStart, event=%s%n", event); + } + + void onStop(@Observes ShutdownEvent event) { + System.out.printf("onStop, event=%s%n", event); + } + +} diff --git a/integration-tests/test-extension/src/main/resources/DSAPublicKey.encoded b/integration-tests/test-extension/src/main/resources/DSAPublicKey.encoded new file mode 100644 index 0000000000000..f3d3e7a80c453 --- /dev/null +++ b/integration-tests/test-extension/src/main/resources/DSAPublicKey.encoded @@ -0,0 +1 @@ +MIIDQjCCAjUGByqGSM44BAEwggIoAoIBAQCPeTXZuarpv6vtiHrPSVG28y7FnjuvNxjo6sSWHz79NgbnQ1GpxBgzObgJ58KuHFObp0dbhdARrbi0eYd1SYRpXKwOjxSzNggooi/6JxEKPWKpk0U0CaD+aWxGWPhL3SCBnDcJoBBXsZWtzQAjPbpUhLYpH51kjviDRIZ3l5zsBLQ0pqwudemYXeI9sCkvwRGMn/qdgYHnM423krcw17njSVkvaAmYchU5Feo9a4tGU8YzRY+AOzKkwuDycpAlbk4/ijsIOKHEUOThjBopo33fXqFD3ktm/wSQPtXPFiPhWNSHxgjpfyEc2B3KI8tuOAdl+CLjQr5ITAV2OTlgHNZnAh0AuvaWpoV499/e5/pnyXfHhe8ysjO65YDAvNVpXQKCAQAWplxYIEhQcE51AqOXVwQNNNo6NHjBVNTkpcAtJC7gT5bmHkvQkEq9rI837rHgnzGC0jyQQ8tkL4gAQWDt+coJsyB2p5wypifyRz6Rh5uixOdEvSCBVEy1W4AsNo0fqD7UielOD6BojjJCilx4xHjGjQUntxyaOrsLC+EsRGiWOefTznTbEBplqiuH9kxoJts+xy9LVZmDS7TtsC98kOmkltOlXVNb6/xF1PYZ9j897buHOSXC8iTgdzEpbaiH7B5HSPh++1/et1SEMWsiMt7lU92vAhErDR8C2jCXMiT+J67ai51LKSLZuovjntnhA6Y8UoELxoi34u1DFuHvF9veA4IBBQACggEAK6IeZShhydDUM5XsOJ/VAYPOgrnLr30AfKWLR39+FJBunVMWNPpvO5D9dU7B6nmSiLATpwhBDNEhyJ0ltmBGuFDBAkKkqE4l6l2iVh+C1TyYliv1P2LCJFNgrAJxyr+5Q5zM9hUgfbT66xnwCf/4aiO7nBlj4wOL3l9ABVllYifMZyKVYFGluXmo+jyyeAcCtzHi5SABbTOQJN0WXTlGtzxLFQ0QErDGhP1/A6z5lw5VHJn2aWMeTCaH+rJZpQfM8b2VWr7UEljqFgpSIHbrImuXcf2nP6uZLKFiDdAjDUyj0h2jXwwcdhwWXuhOEv8XIilkc9nMcPLqbdcQ4M5agg== \ No newline at end of file diff --git a/integration-tests/test-extension/src/main/resources/application.properties b/integration-tests/test-extension/src/main/resources/application.properties new file mode 100644 index 0000000000000..0d01068c557d6 --- /dev/null +++ b/integration-tests/test-extension/src/main/resources/application.properties @@ -0,0 +1,8 @@ +# Log settings +quarkus.log.level=TRACE +quarkus.log.file.enable=true +quarkus.log.file.level=TRACE +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 diff --git a/integration-tests/test-extension/src/main/resources/config.xml b/integration-tests/test-extension/src/main/resources/config.xml new file mode 100644 index 0000000000000..6d0057ee81257 --- /dev/null +++ b/integration-tests/test-extension/src/main/resources/config.xml @@ -0,0 +1,17 @@ + + +

localhost
+ 12345 + + + name1 + model1 + 2019-03-21T02:40:38Z + + + name2 + model2 + 2019-03-23T11:44:00Z + + + \ No newline at end of file diff --git a/integration-tests/test-extension/src/test/java/io/quarkus/it/extension/ExtensionITCase.java b/integration-tests/test-extension/src/test/java/io/quarkus/it/extension/ExtensionITCase.java new file mode 100644 index 0000000000000..5a6aae5803505 --- /dev/null +++ b/integration-tests/test-extension/src/test/java/io/quarkus/it/extension/ExtensionITCase.java @@ -0,0 +1,11 @@ +package io.quarkus.it.extension; + +import io.quarkus.test.junit.SubstrateTest; + +/** + * Native image tests + */ +@SubstrateTest +public class ExtensionITCase extends ExtensionTestCase { + +} diff --git a/integration-tests/test-extension/src/test/java/io/quarkus/it/extension/ExtensionTestCase.java b/integration-tests/test-extension/src/test/java/io/quarkus/it/extension/ExtensionTestCase.java new file mode 100644 index 0000000000000..0b0ce3a18121c --- /dev/null +++ b/integration-tests/test-extension/src/test/java/io/quarkus/it/extension/ExtensionTestCase.java @@ -0,0 +1,43 @@ +package io.quarkus.it.extension; + +import static org.hamcrest.Matchers.is; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.Socket; +import java.nio.charset.StandardCharsets; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.RestAssured; + +@QuarkusTest +public class ExtensionTestCase { + /** + * Test the RuntimeXmlConfigService using old school sockets + */ + @Test + public void testRuntimeXmlConfigService() throws Exception { + // From config.xml + Socket socket = new Socket("localhost", 12345); + OutputStream os = socket.getOutputStream(); + os.write("testRuntimeXmlConfigService\n".getBytes("UTF-8")); + os.flush(); + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8))) { + String reply = reader.readLine(); + Assertions.assertEquals("testRuntimeXmlConfigService-ack", reply); + } + socket.close(); + } + + @Test + public void verifyCommandServlet() { + RestAssured.when().get("/commands/ping").then() + .body(is("/ping-ack")); + } + +} diff --git a/integration-tests/test-extension/src/test/java/io/quarkus/it/extension/FinalFieldReflectionInGraalITCase.java b/integration-tests/test-extension/src/test/java/io/quarkus/it/extension/FinalFieldReflectionInGraalITCase.java new file mode 100644 index 0000000000000..bf7019442bc44 --- /dev/null +++ b/integration-tests/test-extension/src/test/java/io/quarkus/it/extension/FinalFieldReflectionInGraalITCase.java @@ -0,0 +1,35 @@ +/* + * Copyright 2018 Red Hat, Inc. + * + * Licensed 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.it.extension; + +import static org.hamcrest.Matchers.is; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.SubstrateTest; +import io.restassured.RestAssured; + +@SubstrateTest +public class FinalFieldReflectionInGraalITCase { + + @Test + public void testFieldAndGetterReflectionOnEntityFromServlet() throws Exception { + RestAssured.when().get("/core/reflection/final").then() + .body(is("OK")); + } + +} diff --git a/integration-tests/vertx/pom.xml b/integration-tests/vertx/pom.xml index 59199bc891f60..328c09e2a155b 100644 --- a/integration-tests/vertx/pom.xml +++ b/integration-tests/vertx/pom.xml @@ -18,17 +18,14 @@ io.quarkus quarkus-resteasy - provided io.quarkus quarkus-arc - provided io.quarkus quarkus-vertx - provided diff --git a/integration-tests/vertx/src/main/java/io/quarkus/it/vertx/JsonTestResource.java b/integration-tests/vertx/src/main/java/io/quarkus/it/vertx/JsonTestResource.java new file mode 100644 index 0000000000000..e59e9957b5986 --- /dev/null +++ b/integration-tests/vertx/src/main/java/io/quarkus/it/vertx/JsonTestResource.java @@ -0,0 +1,80 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Red Hat 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.it.vertx; + +import static javax.ws.rs.core.MediaType.APPLICATION_JSON; +import static javax.ws.rs.core.MediaType.TEXT_PLAIN; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.stream.Collectors; + +import javax.ws.rs.*; + +import io.vertx.core.json.JsonArray; +import io.vertx.core.json.JsonObject; + +/** + * @author Thomas Ssegismont + */ +@Path("/json-bodies") +public class JsonTestResource { + + @GET + @Path("/json/sync") + @Produces(APPLICATION_JSON) + public JsonObject jsonSync() { + return new JsonObject().put("Hello", "World"); + } + + @POST + @Path("/json/sync") + @Consumes(APPLICATION_JSON) + @Produces(TEXT_PLAIN) + public String jsonSync(JsonObject jsonObject) { + return "Hello " + jsonObject.getString("Hello"); + } + + @GET + @Path("/array/sync") + @Produces(APPLICATION_JSON) + public JsonArray arraySync() { + return new JsonArray().add("Hello").add("World"); + } + + @POST + @Path("/array/sync") + @Consumes(APPLICATION_JSON) + @Produces(TEXT_PLAIN) + public String arraySync(JsonArray jsonArray) { + return jsonArray.stream().map(String.class::cast).collect(Collectors.joining(" ")); + } + + @GET + @Path("/json/async") + @Produces(APPLICATION_JSON) + public CompletionStage jsonAsync() { + return CompletableFuture.completedFuture(new JsonObject().put("Hello", "World")); + } + + @GET + @Path("/array/async") + @Produces(APPLICATION_JSON) + public CompletionStage arrayAsync() { + return CompletableFuture.completedFuture(new JsonArray().add("Hello").add("World")); + } +} diff --git a/integration-tests/vertx/src/main/java/io/quarkus/vertx/tests/MyApplication.java b/integration-tests/vertx/src/main/java/io/quarkus/it/vertx/MyApplication.java similarity index 83% rename from integration-tests/vertx/src/main/java/io/quarkus/vertx/tests/MyApplication.java rename to integration-tests/vertx/src/main/java/io/quarkus/it/vertx/MyApplication.java index 1d55fa5a29523..ef746ad49de2a 100644 --- a/integration-tests/vertx/src/main/java/io/quarkus/vertx/tests/MyApplication.java +++ b/integration-tests/vertx/src/main/java/io/quarkus/it/vertx/MyApplication.java @@ -1,4 +1,4 @@ -package io.quarkus.vertx.tests; +package io.quarkus.it.vertx; import javax.ws.rs.ApplicationPath; import javax.ws.rs.core.Application; diff --git a/integration-tests/vertx/src/main/java/io/quarkus/it/vertx/NettyEventLoopResource.java b/integration-tests/vertx/src/main/java/io/quarkus/it/vertx/NettyEventLoopResource.java new file mode 100644 index 0000000000000..c3c56be086ecb --- /dev/null +++ b/integration-tests/vertx/src/main/java/io/quarkus/it/vertx/NettyEventLoopResource.java @@ -0,0 +1,30 @@ +package io.quarkus.it.vertx; + +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; + +import io.netty.channel.EventLoopGroup; +import io.quarkus.netty.BossGroup; + +@Path("/eventloop") +public class NettyEventLoopResource { + + @Inject + EventLoopGroup worker; + + @Inject + @BossGroup + EventLoopGroup boss; + + @GET + public String test() { + if (boss == null) { + throw new RuntimeException("Boss group null"); + } + if (worker == null) { + throw new RuntimeException("worker group null"); + } + return "passed"; + } +} diff --git a/integration-tests/vertx/src/main/java/io/quarkus/vertx/tests/VertxProducerResource.java b/integration-tests/vertx/src/main/java/io/quarkus/it/vertx/VertxProducerResource.java similarity index 98% rename from integration-tests/vertx/src/main/java/io/quarkus/vertx/tests/VertxProducerResource.java rename to integration-tests/vertx/src/main/java/io/quarkus/it/vertx/VertxProducerResource.java index 1904b9f4c8417..c81fdfc8e018a 100644 --- a/integration-tests/vertx/src/main/java/io/quarkus/vertx/tests/VertxProducerResource.java +++ b/integration-tests/vertx/src/main/java/io/quarkus/it/vertx/VertxProducerResource.java @@ -1,4 +1,4 @@ -package io.quarkus.vertx.tests; +package io.quarkus.it.vertx; import java.util.HashMap; import java.util.Map; diff --git a/integration-tests/vertx/src/test/java/io/quarkus/it/vertx/JsonReaderIT.java b/integration-tests/vertx/src/test/java/io/quarkus/it/vertx/JsonReaderIT.java new file mode 100644 index 0000000000000..00fb77337ac4c --- /dev/null +++ b/integration-tests/vertx/src/test/java/io/quarkus/it/vertx/JsonReaderIT.java @@ -0,0 +1,24 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Red Hat 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.it.vertx; + +import io.quarkus.test.junit.SubstrateTest; + +@SubstrateTest +public class JsonReaderIT extends JsonReaderTest { + +} diff --git a/integration-tests/vertx/src/test/java/io/quarkus/it/vertx/JsonReaderTest.java b/integration-tests/vertx/src/test/java/io/quarkus/it/vertx/JsonReaderTest.java new file mode 100644 index 0000000000000..fb0ce1e800ffc --- /dev/null +++ b/integration-tests/vertx/src/test/java/io/quarkus/it/vertx/JsonReaderTest.java @@ -0,0 +1,61 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Red Hat 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.it.vertx; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.CoreMatchers.equalTo; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.http.ContentType; +import io.vertx.core.json.JsonArray; +import io.vertx.core.json.JsonObject; + +@QuarkusTest +public class JsonReaderTest { + + @Test + public void testJson() { + String body = new JsonObject().put("Hello", "World").toString(); + given().contentType(ContentType.JSON).body(body) + .post("/vertx-test/json-bodies/json/sync") + .then().statusCode(200).body(equalTo("Hello World")); + } + + @Test + public void testEmptyJson() { + given().contentType(ContentType.JSON).body("") + .post("/vertx-test/json-bodies/json/sync") + .then().statusCode(400); + } + + @Test + public void testArray() { + String body = new JsonArray().add("Hello").add("World").toString(); + given().contentType(ContentType.JSON).body(body) + .post("/vertx-test/json-bodies/array/sync") + .then().statusCode(200).body(equalTo("Hello World")); + } + + @Test + public void testEmptyArray() { + given().contentType(ContentType.JSON).body("") + .post("/vertx-test/json-bodies/array/sync") + .then().statusCode(400); + } +} diff --git a/integration-tests/vertx/src/test/java/io/quarkus/it/vertx/JsonWriterIT.java b/integration-tests/vertx/src/test/java/io/quarkus/it/vertx/JsonWriterIT.java new file mode 100644 index 0000000000000..730b2942bf67b --- /dev/null +++ b/integration-tests/vertx/src/test/java/io/quarkus/it/vertx/JsonWriterIT.java @@ -0,0 +1,27 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Red Hat 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.it.vertx; + +import io.quarkus.test.junit.SubstrateTest; + +/** + * @author Thomas Segismont + */ +@SubstrateTest +public class JsonWriterIT extends JsonWriterTest { + +} diff --git a/integration-tests/vertx/src/test/java/io/quarkus/it/vertx/JsonWriterTest.java b/integration-tests/vertx/src/test/java/io/quarkus/it/vertx/JsonWriterTest.java new file mode 100644 index 0000000000000..258bd4f3a8677 --- /dev/null +++ b/integration-tests/vertx/src/test/java/io/quarkus/it/vertx/JsonWriterTest.java @@ -0,0 +1,57 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Red Hat 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.it.vertx; + +import static org.hamcrest.CoreMatchers.equalTo; + +import java.util.Arrays; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.RestAssured; + +/** + * @author Thomas Segismont + */ +@QuarkusTest +public class JsonWriterTest { + + @Test + public void testJsonSync() { + RestAssured.when().get("/vertx-test/json-bodies/json/sync").then() + .statusCode(200).body("Hello", equalTo("World")); + } + + @Test + public void testArraySync() { + RestAssured.when().get("/vertx-test/json-bodies/array/sync").then() + .statusCode(200).body("", equalTo(Arrays.asList("Hello", "World"))); + } + + @Test + public void testJsonAsync() { + RestAssured.when().get("/vertx-test/json-bodies/json/async").then() + .statusCode(200).body("Hello", equalTo("World")); + } + + @Test + public void testArrayAsync() { + RestAssured.when().get("/vertx-test/json-bodies/array/async").then() + .statusCode(200).body("", equalTo(Arrays.asList("Hello", "World"))); + } +} diff --git a/integration-tests/vertx/src/test/java/io/quarkus/it/vertx/NettyEventLoopGroupResourceTest.java b/integration-tests/vertx/src/test/java/io/quarkus/it/vertx/NettyEventLoopGroupResourceTest.java new file mode 100644 index 0000000000000..1a53f14472df7 --- /dev/null +++ b/integration-tests/vertx/src/test/java/io/quarkus/it/vertx/NettyEventLoopGroupResourceTest.java @@ -0,0 +1,18 @@ +package io.quarkus.it.vertx; + +import static org.hamcrest.Matchers.containsString; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.RestAssured; + +@QuarkusTest +public class NettyEventLoopGroupResourceTest { + + @Test + public void testInjection() { + RestAssured.when().get("/vertx-test/eventloop").then() + .body(containsString("passed")); + } +} diff --git a/integration-tests/vertx/src/test/java/io/quarkus/vertx/runtime/tests/VertxProducerResourceIT.java b/integration-tests/vertx/src/test/java/io/quarkus/it/vertx/VertxProducerResourceIT.java similarity index 77% rename from integration-tests/vertx/src/test/java/io/quarkus/vertx/runtime/tests/VertxProducerResourceIT.java rename to integration-tests/vertx/src/test/java/io/quarkus/it/vertx/VertxProducerResourceIT.java index 6a3b6dfb0c24c..fb13de042bdc6 100644 --- a/integration-tests/vertx/src/test/java/io/quarkus/vertx/runtime/tests/VertxProducerResourceIT.java +++ b/integration-tests/vertx/src/test/java/io/quarkus/it/vertx/VertxProducerResourceIT.java @@ -1,4 +1,4 @@ -package io.quarkus.vertx.runtime.tests; +package io.quarkus.it.vertx; import io.quarkus.test.junit.SubstrateTest; diff --git a/integration-tests/vertx/src/test/java/io/quarkus/vertx/runtime/tests/VertxProducerResourceTest.java b/integration-tests/vertx/src/test/java/io/quarkus/it/vertx/VertxProducerResourceTest.java similarity index 93% rename from integration-tests/vertx/src/test/java/io/quarkus/vertx/runtime/tests/VertxProducerResourceTest.java rename to integration-tests/vertx/src/test/java/io/quarkus/it/vertx/VertxProducerResourceTest.java index 850651375c6d9..7182a2888a920 100644 --- a/integration-tests/vertx/src/test/java/io/quarkus/vertx/runtime/tests/VertxProducerResourceTest.java +++ b/integration-tests/vertx/src/test/java/io/quarkus/it/vertx/VertxProducerResourceTest.java @@ -1,4 +1,4 @@ -package io.quarkus.vertx.runtime.tests; +package io.quarkus.it.vertx; import static org.hamcrest.Matchers.containsString; diff --git a/pom.xml b/pom.xml index 7232b1d88d6a2..464300c76f1fb 100644 --- a/pom.xml +++ b/pom.xml @@ -22,7 +22,7 @@ org.jboss jboss-parent - 32 + 34 io.quarkus @@ -49,6 +49,8 @@ ${env.GRAALVM_HOME} jdbc:postgresql:hibernate_orm_test + + 1.6.8 @@ -59,14 +61,15 @@ core test-framework - - integration-tests - extensions + + integration-tests + independent-projects/arc + independent-projects/bootstrap devtools @@ -82,19 +85,9 @@ central Maven Repository Switchboard - http://repo.maven.apache.org/maven2 - - - jboss - http://repository.jboss.org/nexus/content/groups/public/ - - true - - - true - + https://repo.maven.apache.org/maven2 - + @@ -115,12 +108,13 @@ org.sonatype.plugins nexus-staging-maven-plugin - 1.6.3 + ${nexus-staging-maven-plugin.version} true https://oss.sonatype.org/ ossrh true + true diff --git a/test-framework/amazon-lambda/pom.xml b/test-framework/amazon-lambda/pom.xml new file mode 100644 index 0000000000000..e6c4be2d11363 --- /dev/null +++ b/test-framework/amazon-lambda/pom.xml @@ -0,0 +1,35 @@ + + + 4.0.0 + + + io.quarkus + quarkus-test-framework + 999-SNAPSHOT + ../pom.xml + + + quarkus-test-amazon-lambda + Quarkus - Amazon Lambda - Test Framework + + + + io.quarkus + quarkus-amazon-lambda + + + io.quarkus + quarkus-test-common + + + io.undertow + undertow-core + + + com.fasterxml.jackson.core + jackson-databind + + + diff --git a/test-framework/amazon-lambda/src/main/java/io/quarkus/amazon/lambda/test/LambdaClient.java b/test-framework/amazon-lambda/src/main/java/io/quarkus/amazon/lambda/test/LambdaClient.java new file mode 100644 index 0000000000000..1184a8b78f322 --- /dev/null +++ b/test-framework/amazon-lambda/src/main/java/io/quarkus/amazon/lambda/test/LambdaClient.java @@ -0,0 +1,60 @@ +package io.quarkus.amazon.lambda.test; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.atomic.AtomicInteger; + +import com.fasterxml.jackson.databind.ObjectMapper; + +public class LambdaClient { + + private static final AtomicInteger REQUEST_ID_GENERATOR = new AtomicInteger(); + static final ConcurrentHashMap> REQUESTS = new ConcurrentHashMap<>(); + static final LinkedBlockingDeque REQUEST_QUEUE = new LinkedBlockingDeque<>(); + static volatile LambdaException problem; + + public static T invoke(Class returnType, Object input) { + if (problem != null) { + throw new RuntimeException(problem); + } + try { + final ObjectMapper mapper = new ObjectMapper(); + String id = "aws-request-" + REQUEST_ID_GENERATOR.incrementAndGet(); + CompletableFuture result = new CompletableFuture<>(); + REQUESTS.put(id, result); + REQUEST_QUEUE.add(new Request(id, mapper.writeValueAsString(input))); + String output = result.get(); + return mapper.readerFor(returnType).readValue(output); + } catch (Exception e) { + if (e instanceof ExecutionException) { + Throwable ex = e.getCause(); + if (ex instanceof RuntimeException) { + throw (RuntimeException) ex; + } + throw new RuntimeException(ex); + } + throw new RuntimeException(e); + } + } + + public static class Request { + final String id; + final String json; + + Request(String id, String json) { + this.id = id; + this.json = json; + } + + public String getId() { + return id; + } + + public String getJson() { + return json; + } + } + +} diff --git a/test-framework/amazon-lambda/src/main/java/io/quarkus/amazon/lambda/test/LambdaException.java b/test-framework/amazon-lambda/src/main/java/io/quarkus/amazon/lambda/test/LambdaException.java new file mode 100644 index 0000000000000..e74b6a14162d2 --- /dev/null +++ b/test-framework/amazon-lambda/src/main/java/io/quarkus/amazon/lambda/test/LambdaException.java @@ -0,0 +1,16 @@ +package io.quarkus.amazon.lambda.test; + +@SuppressWarnings("serial") +public class LambdaException extends RuntimeException { + + final String type; + + public LambdaException(String type, String message) { + super(message); + this.type = type; + } + + public String getType() { + return type; + } +} diff --git a/test-framework/amazon-lambda/src/main/java/io/quarkus/amazon/lambda/test/LambdaResourceManager.java b/test-framework/amazon-lambda/src/main/java/io/quarkus/amazon/lambda/test/LambdaResourceManager.java new file mode 100644 index 0000000000000..16b32dfd2c468 --- /dev/null +++ b/test-framework/amazon-lambda/src/main/java/io/quarkus/amazon/lambda/test/LambdaResourceManager.java @@ -0,0 +1,116 @@ +package io.quarkus.amazon.lambda.test; + +import java.io.IOException; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import io.quarkus.amazon.lambda.runtime.AmazonLambdaApi; +import io.quarkus.amazon.lambda.runtime.FunctionError; +import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; +import io.undertow.Undertow; +import io.undertow.io.Receiver; +import io.undertow.server.HttpHandler; +import io.undertow.server.HttpServerExchange; +import io.undertow.server.RoutingHandler; +import io.undertow.server.handlers.BlockingHandler; +import io.undertow.util.HttpString; + +public class LambdaResourceManager implements QuarkusTestResourceLifecycleManager { + + private volatile Undertow undertow; + + public static final int PORT = Integer.getInteger("quarkus-internal.aws-lambda.test-port", 5387); + + @Override + public Map start() { + + RoutingHandler routingHandler = new RoutingHandler(true); + routingHandler.add("GET", AmazonLambdaApi.API_PATH_INVOCATION_NEXT, new HttpHandler() { + @Override + public void handleRequest(HttpServerExchange exchange) throws Exception { + LambdaStartedNotifier.started = true; + LambdaClient.Request req = null; + while (req == null) { + req = LambdaClient.REQUEST_QUEUE.poll(100, TimeUnit.MILLISECONDS); + if (undertow == null || undertow.getWorker().isShutdown()) { + return; + } + } + exchange.getResponseHeaders().put(new HttpString(AmazonLambdaApi.LAMBDA_RUNTIME_AWS_REQUEST_ID), req.id); + exchange.getResponseSender().send(req.json); + } + }); + routingHandler.add("POST", AmazonLambdaApi.API_PATH_INVOCATION + "{req}" + AmazonLambdaApi.API_PATH_RESPONSE, + new HttpHandler() { + @Override + public void handleRequest(HttpServerExchange exchange) throws Exception { + String id = exchange.getQueryParameters().get("req").getFirst(); + exchange.getRequestReceiver().receiveFullString(new Receiver.FullStringCallback() { + @Override + public void handle(HttpServerExchange exchange, String message) { + LambdaClient.REQUESTS.get(id).complete(message); + } + }); + } + }); + + routingHandler.add("POST", AmazonLambdaApi.API_PATH_INVOCATION + "{req}" + AmazonLambdaApi.API_PATH_ERROR, + new HttpHandler() { + @Override + public void handleRequest(HttpServerExchange exchange) throws Exception { + String id = exchange.getQueryParameters().get("req").getFirst(); + exchange.getRequestReceiver().receiveFullString(new Receiver.FullStringCallback() { + @Override + public void handle(HttpServerExchange exchange, String message) { + ObjectMapper mapper = new ObjectMapper(); + try { + FunctionError result = mapper.readerFor(FunctionError.class).readValue(message); + + LambdaClient.REQUESTS.get(id).completeExceptionally( + new LambdaException(result.getErrorType(), result.getErrorMessage())); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + }); + } + }); + routingHandler.add("POST", AmazonLambdaApi.API_PATH_INIT_ERROR, new HttpHandler() { + @Override + public void handleRequest(HttpServerExchange exchange) throws Exception { + exchange.getRequestReceiver().receiveFullString(new Receiver.FullStringCallback() { + @Override + public void handle(HttpServerExchange exchange, String message) { + ObjectMapper mapper = new ObjectMapper(); + try { + FunctionError result = mapper.readerFor(FunctionError.class).readValue(message); + LambdaClient.problem = new LambdaException(result.getErrorType(), result.getErrorMessage()); + LambdaStartedNotifier.started = true; + for (Map.Entry> e : LambdaClient.REQUESTS.entrySet()) { + e.getValue().completeExceptionally(LambdaClient.problem); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + }); + } + }); + undertow = Undertow.builder().addHttpListener(PORT, "localhost") + .setHandler(new BlockingHandler(routingHandler)) + .build(); + undertow.start(); + return Collections.singletonMap(AmazonLambdaApi.QUARKUS_INTERNAL_AWS_LAMBDA_TEST_API, "localhost:" + PORT); + } + + @Override + public void stop() { + undertow.stop(); + undertow = null; + + } +} diff --git a/test-framework/amazon-lambda/src/main/java/io/quarkus/amazon/lambda/test/LambdaStartedNotifier.java b/test-framework/amazon-lambda/src/main/java/io/quarkus/amazon/lambda/test/LambdaStartedNotifier.java new file mode 100644 index 0000000000000..d899e00a0152b --- /dev/null +++ b/test-framework/amazon-lambda/src/main/java/io/quarkus/amazon/lambda/test/LambdaStartedNotifier.java @@ -0,0 +1,13 @@ +package io.quarkus.amazon.lambda.test; + +import io.quarkus.test.common.NativeImageStartedNotifier; + +public class LambdaStartedNotifier implements NativeImageStartedNotifier { + + static volatile boolean started = false; + + @Override + public boolean isNativeImageStarted() { + return started; + } +} diff --git a/test-framework/amazon-lambda/src/main/resources/META-INF/services/io.quarkus.test.common.NativeImageStartedNotifier b/test-framework/amazon-lambda/src/main/resources/META-INF/services/io.quarkus.test.common.NativeImageStartedNotifier new file mode 100644 index 0000000000000..db629ff9845f0 --- /dev/null +++ b/test-framework/amazon-lambda/src/main/resources/META-INF/services/io.quarkus.test.common.NativeImageStartedNotifier @@ -0,0 +1 @@ +io.quarkus.amazon.lambda.test.LambdaStartedNotifier \ No newline at end of file diff --git a/test-framework/amazon-lambda/src/main/resources/META-INF/services/io.quarkus.test.common.QuarkusTestResourceLifecycleManager b/test-framework/amazon-lambda/src/main/resources/META-INF/services/io.quarkus.test.common.QuarkusTestResourceLifecycleManager new file mode 100644 index 0000000000000..b84300b170d34 --- /dev/null +++ b/test-framework/amazon-lambda/src/main/resources/META-INF/services/io.quarkus.test.common.QuarkusTestResourceLifecycleManager @@ -0,0 +1 @@ +io.quarkus.amazon.lambda.test.LambdaResourceManager \ No newline at end of file diff --git a/test-framework/common/pom.xml b/test-framework/common/pom.xml index e09bc3908e576..d649e4706fa3a 100644 --- a/test-framework/common/pom.xml +++ b/test-framework/common/pom.xml @@ -33,11 +33,11 @@ io.quarkus - quarkus-core + quarkus-core-deployment - org.glassfish - javax.json + io.quarkus + quarkus-jsonp-deployment org.jboss 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 8b04d1353778d..bc8d8af95fca4 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 @@ -26,7 +26,10 @@ import java.net.URLClassLoader; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; import org.eclipse.microprofile.config.ConfigProvider; @@ -39,6 +42,8 @@ public class NativeImageLauncher implements Closeable { private final Class testClass; private Process quarkusProcess; private final int port; + private final Map systemProps = new HashMap<>(); + private List startedNotifiers; public NativeImageLauncher(Class testClass) { this(testClass, ConfigProvider.getConfig().getOptionalValue("quarkus.http.test-port", Integer.class).orElse(8081)); @@ -47,9 +52,14 @@ public NativeImageLauncher(Class testClass) { public NativeImageLauncher(Class testClass, int port) { this.testClass = testClass; this.port = port; + List startedNotifiers = new ArrayList<>(); + for (NativeImageStartedNotifier i : ServiceLoader.load(NativeImageStartedNotifier.class)) { + startedNotifiers.add(i); + } + this.startedNotifiers = startedNotifiers; } - public void start() throws Exception { + public void start() throws IOException { String path = System.getProperty("native.image.path"); if (path == null) { @@ -59,7 +69,10 @@ public void start() throws Exception { args.add(path); args.add("-Dquarkus.http.port=" + port); args.add("-Dtest.url=" + TestHTTPResourceManager.getUri()); - args.add("-Dquarkus.log.file.path=target/quarkus.log"); + args.add("-Dquarkus.log.file.path=" + PropertyTestUtil.getLogFileLocation()); + for (Map.Entry e : systemProps.entrySet()) { + args.add("-D" + e.getKey() + "=" + e.getValue()); + } System.out.println("Executing " + args); @@ -80,7 +93,7 @@ private static String guessPath(Class testClass) { URL[] urls = ((URLClassLoader) cl).getURLs(); for (URL url : urls) { if (url.getProtocol().equals("file") && url.getPath().endsWith("test-classes/")) { - //we have the test classes dir + //we have the maven test classes dir File testClasses = new File(url.getPath()); for (File file : testClasses.getParentFile().listFiles()) { if (file.getName().endsWith("-runner")) { @@ -88,6 +101,15 @@ private static String guessPath(Class testClass) { return file.getAbsolutePath(); } } + } else if (url.getProtocol().equals("file") && url.getPath().endsWith("test/")) { + //we have the gradle test classes dir, build/classes/java/test + File testClasses = new File(url.getPath()); + for (File file : testClasses.getParentFile().getParentFile().getParentFile().listFiles()) { + if (file.getName().endsWith("-runner")) { + logGuessedPath(file.getAbsolutePath()); + return file.getAbsolutePath(); + } + } } } } @@ -114,6 +136,11 @@ private void waitForQuarkus() { while (System.currentTimeMillis() < bailout) { try { Thread.sleep(100); + for (NativeImageStartedNotifier i : startedNotifiers) { + if (i.isNativeImageStarted()) { + return; + } + } try (Socket s = new Socket()) { s.connect(new InetSocketAddress("localhost", port)); return; @@ -125,6 +152,10 @@ private void waitForQuarkus() { throw new RuntimeException("Unable to start native image in " + IMAGE_WAIT_TIME + "ms"); } + public void addSystemProperties(Map systemProps) { + this.systemProps.putAll(systemProps); + } + private static final class ProcessReader implements Runnable { private final InputStream inputStream; diff --git a/test-framework/common/src/main/java/io/quarkus/test/common/NativeImageStartedNotifier.java b/test-framework/common/src/main/java/io/quarkus/test/common/NativeImageStartedNotifier.java new file mode 100644 index 0000000000000..0a795c05156bd --- /dev/null +++ b/test-framework/common/src/main/java/io/quarkus/test/common/NativeImageStartedNotifier.java @@ -0,0 +1,10 @@ +package io.quarkus.test.common; + +/** + * Interface than can be implemented to notify the test infrastructure that the native image has started for + * non HTTP based tests. + */ +public interface NativeImageStartedNotifier { + + boolean isNativeImageStarted(); +} diff --git a/test-framework/common/src/main/java/io/quarkus/test/common/PathTestHelper.java b/test-framework/common/src/main/java/io/quarkus/test/common/PathTestHelper.java index b6a3379428e5f..769a536f87ace 100644 --- a/test-framework/common/src/main/java/io/quarkus/test/common/PathTestHelper.java +++ b/test-framework/common/src/main/java/io/quarkus/test/common/PathTestHelper.java @@ -21,11 +21,28 @@ import java.net.URL; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; +/** + * Maps between builder test and application class directories. + */ public final class PathTestHelper { - - private static final String TEST_CLASSES_FRAGMENT = File.separator + "test-classes"; - private static final String CLASSES_FRAGMENT = File.separator + "classes"; + private static final Map TEST_TO_MAIN_DIR_FRAGMENTS = new HashMap<>(); + static { + // eclipse + TEST_TO_MAIN_DIR_FRAGMENTS.put( + "bin" + File.separator + "test", + "bin" + File.separator + "main"); + // gradle + TEST_TO_MAIN_DIR_FRAGMENTS.put( + "classes" + File.separator + "java" + File.separator + "test", + "classes" + File.separator + "java" + File.separator + "main"); + // maven + TEST_TO_MAIN_DIR_FRAGMENTS.put( + File.separator + "test-classes", + File.separator + "classes"); + } private PathTestHelper() { } @@ -34,21 +51,44 @@ public static Path getTestClassesLocation(Class testClass) { String classFileName = testClass.getName().replace('.', File.separatorChar) + ".class"; URL resource = testClass.getClassLoader().getResource(classFileName); - try { - Path path = Paths.get(resource.toURI()); - if (!path.toString().contains(TEST_CLASSES_FRAGMENT)) { - throw new RuntimeException( - "The test class " + testClass + " is not located in the " + TEST_CLASSES_FRAGMENT + " directory."); - } - - return path.getRoot().resolve(path.subpath(0, path.getNameCount() - Paths.get(classFileName).getNameCount())); - } catch (URISyntaxException e) { - throw new RuntimeException(e); + if (!isInTestDir(resource)) { + throw new RuntimeException( + "The test class " + testClass + " is not located in any of the directories " + + TEST_TO_MAIN_DIR_FRAGMENTS.keySet()); } + Path path = toPath(resource); + return path.getRoot().resolve(path.subpath(0, path.getNameCount() - Paths.get(classFileName).getNameCount())); } public static Path getAppClassLocation(Class testClass) { - return Paths.get(getTestClassesLocation(testClass).toString().replace(TEST_CLASSES_FRAGMENT, CLASSES_FRAGMENT)); + String testClassPath = getTestClassesLocation(testClass).toString(); + return TEST_TO_MAIN_DIR_FRAGMENTS.entrySet().stream() + .filter(e -> testClassPath.contains(e.getKey())) + .map(e -> Paths.get(testClassPath.replace(e.getKey(), e.getValue()))) + .findFirst() + .orElseThrow(() -> new IllegalStateException("Unable to translate path for " + testClass.getName())); + } + + public static boolean isTestClass(String className, ClassLoader classLoader) { + String classFileName = className.replace('.', File.separatorChar) + ".class"; + URL resource = classLoader.getResource(classFileName); + return resource != null + && resource.getProtocol().startsWith("file") + && isInTestDir(resource); + } + + private static boolean isInTestDir(URL resource) { + String path = toPath(resource).toString(); + return TEST_TO_MAIN_DIR_FRAGMENTS.keySet().stream() + .anyMatch(path::contains); + } + + private static Path toPath(URL resource) { + try { + return Paths.get(resource.toURI()); + } catch (URISyntaxException e) { + throw new IllegalStateException("Failed to convert URL " + resource, e); + } } } diff --git a/test-framework/common/src/main/java/io/quarkus/test/common/PropertyTestUtil.java b/test-framework/common/src/main/java/io/quarkus/test/common/PropertyTestUtil.java new file mode 100644 index 0000000000000..47f361eaff968 --- /dev/null +++ b/test-framework/common/src/main/java/io/quarkus/test/common/PropertyTestUtil.java @@ -0,0 +1,34 @@ +/* + * Copyright 2019 Red Hat, Inc. + * + * Licensed 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.test.common; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Paths; + +public class PropertyTestUtil { + + public static void setLogFileProperty() { + System.setProperty("quarkus.log.file.path", getLogFileLocation()); + } + + public static String getLogFileLocation() { + if (Files.isDirectory(Paths.get("build"))) { + return "build" + File.separator + "quarkus.log"; + } + return "target" + File.separator + "quarkus.log"; + } +} diff --git a/test-framework/common/src/main/java/io/quarkus/test/common/QuarkusTestResourceLifecycleManager.java b/test-framework/common/src/main/java/io/quarkus/test/common/QuarkusTestResourceLifecycleManager.java index 6d16c0cd6f5c0..bcd5769640414 100644 --- a/test-framework/common/src/main/java/io/quarkus/test/common/QuarkusTestResourceLifecycleManager.java +++ b/test-framework/common/src/main/java/io/quarkus/test/common/QuarkusTestResourceLifecycleManager.java @@ -16,6 +16,8 @@ package io.quarkus.test.common; +import java.util.Map; + /** * Manage the lifecycle of a test resource, for instance a H2 test server. *

@@ -31,8 +33,10 @@ public interface QuarkusTestResourceLifecycleManager { /** * Start the test resource. + * + * @return A map of system properties that should be set for the running test */ - void start(); + Map start(); /** * Stop the test resource. 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 e0bb573a1c42d..9ef973fc78c85 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 @@ -27,7 +27,9 @@ import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.nio.file.attribute.BasicFileAttributes; +import java.util.HashMap; import java.util.LinkedHashSet; +import java.util.Map; import java.util.ServiceLoader; import java.util.Set; @@ -39,27 +41,44 @@ public class TestResourceManager { private final Set testResources; + private Map oldSystemProps; public TestResourceManager(Class testClass) { testResources = getTestResources(testClass); } - public void start() { + public Map start() { + Map ret = new HashMap<>(); for (QuarkusTestResourceLifecycleManager testResource : testResources) { try { - testResource.start(); + ret.putAll(testResource.start()); } catch (Exception e) { - throw new RuntimeException("Unable to start Quarkus test resource " + testResource); + throw new RuntimeException("Unable to start Quarkus test resource " + testResource, e); } } + oldSystemProps = new HashMap<>(); + for (Map.Entry i : ret.entrySet()) { + oldSystemProps.put(i.getKey(), System.getProperty(i.getKey())); + System.setProperty(i.getKey(), i.getValue()); + } + return ret; } public void stop() { + for (Map.Entry e : oldSystemProps.entrySet()) { + if (e.getValue() == null) { + System.clearProperty(e.getKey()); + } else { + System.setProperty(e.getKey(), e.getValue()); + } + + } + oldSystemProps = null; for (QuarkusTestResourceLifecycleManager testResource : testResources) { try { testResource.stop(); } catch (Exception e) { - throw new RuntimeException("Unable to start Quarkus test resource " + testResource); + throw new RuntimeException("Unable to start Quarkus test resource " + testResource, e); } } } diff --git a/test-framework/common/src/main/java/io/quarkus/test/common/TestScopeManager.java b/test-framework/common/src/main/java/io/quarkus/test/common/TestScopeManager.java new file mode 100644 index 0000000000000..57252f1fe9503 --- /dev/null +++ b/test-framework/common/src/main/java/io/quarkus/test/common/TestScopeManager.java @@ -0,0 +1,30 @@ +package io.quarkus.test.common; + +import java.util.ArrayList; +import java.util.List; +import java.util.ServiceLoader; + +import io.quarkus.deployment.test.TestScopeSetup; + +public class TestScopeManager { + + private static final List scopeManagers = new ArrayList<>(); + + static { + for (TestScopeSetup i : ServiceLoader.load(TestScopeSetup.class)) { + scopeManagers.add(i); + } + } + + public static void setup() { + for (TestScopeSetup i : scopeManagers) { + i.setup(); + } + } + + public static void tearDown() { + for (TestScopeSetup i : scopeManagers) { + i.setup(); + } + } +} diff --git a/test-framework/h2/src/main/java/io/quarkus/test/h2/H2DatabaseTestResource.java b/test-framework/h2/src/main/java/io/quarkus/test/h2/H2DatabaseTestResource.java index c52fc910995c9..6a64e164764f3 100644 --- a/test-framework/h2/src/main/java/io/quarkus/test/h2/H2DatabaseTestResource.java +++ b/test-framework/h2/src/main/java/io/quarkus/test/h2/H2DatabaseTestResource.java @@ -17,6 +17,8 @@ package io.quarkus.test.h2; import java.sql.SQLException; +import java.util.Collections; +import java.util.Map; import org.h2.tools.Server; @@ -27,7 +29,7 @@ public class H2DatabaseTestResource implements QuarkusTestResourceLifecycleManag private Server tcpServer; @Override - public void start() { + public Map start() { try { tcpServer = Server.createTcpServer(); @@ -36,6 +38,7 @@ public void start() { } catch (SQLException e) { throw new RuntimeException(e); } + return Collections.emptyMap(); } @Override diff --git a/test-framework/junit4/src/main/java/io/quarkus/test/junit4/QuarkusTest.java b/test-framework/junit4/src/main/java/io/quarkus/test/junit4/QuarkusTest.java index ebb23c61ea183..cbfb48a37d265 100644 --- a/test-framework/junit4/src/main/java/io/quarkus/test/junit4/QuarkusTest.java +++ b/test-framework/junit4/src/main/java/io/quarkus/test/junit4/QuarkusTest.java @@ -20,12 +20,19 @@ import static io.quarkus.test.common.PathTestHelper.getTestClassesLocation; import java.io.IOException; +import java.util.function.Consumer; +import org.junit.runner.RunWith; import org.junit.runner.notification.RunNotifier; import org.junit.runners.model.InitializationError; +import io.quarkus.builder.BuildChainBuilder; +import io.quarkus.builder.BuildContext; +import io.quarkus.builder.BuildStep; +import io.quarkus.deployment.builditem.TestAnnotationBuildItem; import io.quarkus.runner.RuntimeRunner; import io.quarkus.runtime.LaunchMode; +import io.quarkus.test.common.PropertyTestUtil; public class QuarkusTest extends AbstractQuarkusTestRunner { @@ -43,12 +50,24 @@ private static class QuarkusRunListener extends AbstractQuarkusRunListener { @Override protected void startQuarkus() { - System.setProperty("quarkus.log.file.path", "target/quarkus.log"); + PropertyTestUtil.setLogFileProperty(); runtimeRunner = RuntimeRunner.builder() .setLaunchMode(LaunchMode.TEST) .setClassLoader(getClass().getClassLoader()) .setTarget(getAppClassLocation(getTestClass())) .setFrameworkClassesPath(getTestClassesLocation(getTestClass())) + .addChainCustomizer(new Consumer() { + @Override + public void accept(BuildChainBuilder buildChainBuilder) { + buildChainBuilder.addBuildStep(new BuildStep() { + @Override + public void execute(BuildContext context) { + context.produce(new TestAnnotationBuildItem(RunWith.class.getName())); + } + }).produces(TestAnnotationBuildItem.class) + .build(); + } + }) .build(); runtimeRunner.run(); } 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 7578bb7e16d33..ccdd8b0442ca8 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 @@ -21,7 +21,6 @@ import java.io.File; import java.io.IOException; -import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.nio.file.FileVisitResult; @@ -38,11 +37,6 @@ import javax.enterprise.inject.Instance; import javax.enterprise.inject.spi.CDI; -import org.jboss.builder.BuildChainBuilder; -import org.jboss.builder.BuildContext; -import org.jboss.builder.BuildException; -import org.jboss.builder.BuildStep; -import org.jboss.builder.item.BuildItem; import org.jboss.invocation.proxy.ProxyConfiguration; import org.jboss.invocation.proxy.ProxyFactory; import org.jboss.shrinkwrap.api.exporter.ExplodedExporter; @@ -57,9 +51,15 @@ import org.junit.jupiter.api.extension.TestInstanceFactoryContext; import org.junit.jupiter.api.extension.TestInstantiationException; +import io.quarkus.builder.BuildChainBuilder; +import io.quarkus.builder.BuildContext; +import io.quarkus.builder.BuildException; +import io.quarkus.builder.BuildStep; +import io.quarkus.builder.item.BuildItem; import io.quarkus.runner.RuntimeRunner; import io.quarkus.runtime.LaunchMode; import io.quarkus.test.common.PathTestHelper; +import io.quarkus.test.common.PropertyTestUtil; import io.quarkus.test.common.RestAssuredURLManager; import io.quarkus.test.common.TestResourceManager; import io.quarkus.test.common.http.TestHTTPResourceManager; @@ -78,16 +78,19 @@ public class QuarkusUnitTest private RuntimeRunner runtimeRunner; private Path deploymentDir; - private Class expectedException; + private Consumer assertException; private Supplier archiveProducer; private Runnable afterUndeployListener; - public Class getExpectedException() { - return expectedException; + public QuarkusUnitTest setExpectedException(Class expectedException) { + return assertException(t -> { + assertEquals(expectedException, + t.getClass(), "Build failed with wrong exception"); + }); } - public QuarkusUnitTest setExpectedException(Class expectedException) { - this.expectedException = expectedException; + public QuarkusUnitTest assertException(Consumer assertException) { + this.assertException = assertException; return this; } @@ -100,6 +103,7 @@ public QuarkusUnitTest setArchiveProducer(Supplier archiveProducer) return this; } + @SuppressWarnings({ "rawtypes", "unchecked" }) public Object createTestInstance(TestInstanceFactoryContext factoryContext, ExtensionContext extensionContext) throws TestInstantiationException { try { @@ -116,7 +120,7 @@ public Object createTestInstance(TestInstanceFactoryContext factoryContext, Exte return factory.newInstance(new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - if (expectedException != null) { + if (assertException != null) { return null; } Method realMethod = actualTestInstance.getClass().getMethod(method.getName(), method.getParameterTypes()); @@ -160,7 +164,7 @@ public void beforeAll(ExtensionContext extensionContext) throws Exception { throw new RuntimeException("QuarkusUnitTest does not have archive producer set"); } - System.setProperty("quarkus.log.file.path", "target/quarkus.log"); + PropertyTestUtil.setLogFileProperty(); ExtensionContext.Store store = extensionContext.getRoot().getStore(ExtensionContext.Namespace.GLOBAL); if (store.get(TestResourceManager.class.getName()) == null) { TestResourceManager manager = new TestResourceManager(extensionContext.getRequiredTestClass()); @@ -194,9 +198,8 @@ public void accept(BuildChainBuilder buildChainBuilder) { @Override public void execute(BuildContext context) { try { - Constructor ctor = buildItem.getConstructor(boolean.class, - String[].class); - context.produce(ctor.newInstance(false, new String[] { testClass.getName() })); + Method factoryMethod = buildItem.getMethod("unremovableOf", Class.class); + context.produce((BuildItem) factoryMethod.invoke(null, testClass)); } catch (Exception e) { throw new RuntimeException(e); } @@ -219,8 +222,8 @@ public void execute(BuildContext context) { try { runtimeRunner.run(); - if (expectedException != null) { - fail("Build did not fail"); + if (assertException != null) { + fail("The build was expected to fail"); } started = true; Instance factory; @@ -235,15 +238,13 @@ public void execute(BuildContext context) { extensionContext.getStore(ExtensionContext.Namespace.GLOBAL).put(testClass.getName(), actualTest); } catch (Exception e) { started = false; - if (expectedException != null) { + if (assertException != null) { if (e instanceof RuntimeException) { Throwable cause = e.getCause(); if (cause != null && cause instanceof BuildException) { - assertEquals(expectedException, - cause.getCause().getClass(), "Build failed with wrong exception"); + assertException.accept(cause.getCause()); } else if (cause != null) { - assertEquals(expectedException, - cause.getClass(), "Build failed with wrong exception"); + assertException.accept(cause); } else { fail("Unable to unwrap build exception from: " + e); } diff --git a/test-framework/junit5/pom.xml b/test-framework/junit5/pom.xml index 5ec2347673494..1278d125049d3 100644 --- a/test-framework/junit5/pom.xml +++ b/test-framework/junit5/pom.xml @@ -31,6 +31,10 @@ Quarkus - Test framework - JUnit 5 + + io.quarkus + quarkus-bootstrap-core + io.quarkus quarkus-test-common diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/DisabledOnSubstrate.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/DisabledOnSubstrate.java new file mode 100644 index 0000000000000..f1d47bac56143 --- /dev/null +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/DisabledOnSubstrate.java @@ -0,0 +1,17 @@ +package io.quarkus.test.junit; + +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; + +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface DisabledOnSubstrate { + /** + * Reason for disabling this test + */ + public String value() default ""; +} diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/DisabledOnSubstrateCondition.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/DisabledOnSubstrateCondition.java new file mode 100644 index 0000000000000..8484d435e1cce --- /dev/null +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/DisabledOnSubstrateCondition.java @@ -0,0 +1,44 @@ +package io.quarkus.test.junit; + +import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation; + +import java.lang.reflect.AnnotatedElement; +import java.util.Optional; + +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext.Namespace; +import org.junit.jupiter.api.extension.ExtensionContext.Store; +import org.junit.platform.commons.util.StringUtils; + +import io.quarkus.test.junit.QuarkusTestExtension.ExtensionState; + +public class DisabledOnSubstrateCondition implements ExecutionCondition { + + private static final ConditionEvaluationResult ENABLED = ConditionEvaluationResult + .enabled("@DisabledOnSubstrate is not present"); + + /** + * Containers/tests are disabled if {@code @DisabledOnSubstrate} is present on the test + * class or method and we're running on Substrate. + */ + @Override + public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { + Optional element = context.getElement(); + Optional disabled = findAnnotation(element, DisabledOnSubstrate.class); + if (disabled.isPresent()) { + Store store = context.getStore(Namespace.GLOBAL); + ExtensionState state = (ExtensionState) store.get(ExtensionState.class.getName()); + if (state != null && state.isSubstrate()) { + String reason = disabled.map(DisabledOnSubstrate::value) + .filter(StringUtils::isNotBlank) + .orElseGet(() -> element.get() + " is @DisabledOnSubstrate"); + return ConditionEvaluationResult.disabled(reason); + } + } + + return ENABLED; + } + +} 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 9ee1b43605c01..cbeb39b4d1951 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 @@ -26,6 +26,7 @@ import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.net.URL; +import java.net.URLClassLoader; import java.nio.file.Files; import java.nio.file.Path; import java.util.Enumeration; @@ -33,6 +34,8 @@ import java.util.Map; import java.util.concurrent.LinkedBlockingDeque; import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Predicate; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.BeforeAllCallback; @@ -41,50 +44,41 @@ import org.junit.jupiter.api.extension.TestInstanceFactory; import org.junit.jupiter.api.extension.TestInstanceFactoryContext; import org.junit.jupiter.api.extension.TestInstantiationException; +import org.junit.platform.commons.JUnitException; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; +import org.opentest4j.TestAbortedException; +import io.quarkus.bootstrap.BootstrapClassLoaderFactory; +import io.quarkus.bootstrap.BootstrapException; +import io.quarkus.bootstrap.util.PropertyUtils; +import io.quarkus.builder.BuildChainBuilder; +import io.quarkus.builder.BuildContext; +import io.quarkus.builder.BuildStep; import io.quarkus.deployment.ClassOutput; import io.quarkus.deployment.QuarkusClassWriter; +import io.quarkus.deployment.builditem.TestAnnotationBuildItem; +import io.quarkus.deployment.builditem.TestClassPredicateBuildItem; import io.quarkus.deployment.util.IoUtil; import io.quarkus.runner.RuntimeRunner; import io.quarkus.runner.TransformerTarget; import io.quarkus.runtime.LaunchMode; import io.quarkus.test.common.NativeImageLauncher; +import io.quarkus.test.common.PathTestHelper; +import io.quarkus.test.common.PropertyTestUtil; import io.quarkus.test.common.RestAssuredURLManager; import io.quarkus.test.common.TestInjectionManager; import io.quarkus.test.common.TestResourceManager; +import io.quarkus.test.common.TestScopeManager; import io.quarkus.test.common.http.TestHTTPResourceManager; -public class QuarkusTestExtension implements BeforeAllCallback, BeforeEachCallback, AfterEachCallback, TestInstanceFactory { +public class QuarkusTestExtension + implements BeforeEachCallback, AfterEachCallback, TestInstanceFactory, BeforeAllCallback { - @Override - public void beforeAll(ExtensionContext context) throws Exception { - ExtensionContext root = context.getRoot(); - ExtensionContext.Store store = root.getStore(ExtensionContext.Namespace.GLOBAL); - ExtensionState state = (ExtensionState) store.get(ExtensionState.class.getName()); - System.setProperty("quarkus.log.file.path", "target/quarkus.log"); - boolean substrateTest = context.getRequiredTestClass().isAnnotationPresent(SubstrateTest.class); - if (state == null) { - TestResourceManager testResourceManager = new TestResourceManager(context.getRequiredTestClass()); - testResourceManager.start(); - - if (substrateTest) { - NativeImageLauncher launcher = new NativeImageLauncher(context.getRequiredTestClass()); - launcher.start(); - state = new ExtensionState(testResourceManager, launcher, true); - } else { - state = doJavaStart(context, testResourceManager); - } - store.put(ExtensionState.class.getName(), state); - } else { - if (substrateTest != state.isSubstrate()) { - throw new RuntimeException( - "Attempted to mix @SubstrateTest and JVM mode tests in the same test run. This is not allowed."); - } - } - } + private URLClassLoader appCl; + private ClassLoader originalCl; + private static boolean failedBoot; private ExtensionState doJavaStart(ExtensionContext context, TestResourceManager testResourceManager) { @@ -92,9 +86,26 @@ private ExtensionState doJavaStart(ExtensionContext context, TestResourceManager Path appClassLocation = getAppClassLocation(context.getRequiredTestClass()); Path testClassLocation = getTestClassesLocation(context.getRequiredTestClass()); + ClassLoader testClassLoader = context.getRequiredTestClass().getClassLoader(); + + try { + appCl = BootstrapClassLoaderFactory.newInstance() + .setAppClasses(appClassLocation) + .addToClassPath(testClassLocation) + .setParent(getClass().getClassLoader()) + .setOffline(PropertyUtils.getBooleanOrNull(BootstrapClassLoaderFactory.PROP_OFFLINE)) + .setLocalProjectsDiscovery( + PropertyUtils.getBoolean(BootstrapClassLoaderFactory.PROP_WS_DISCOVERY, true)) + .setEnableClasspathCache(PropertyUtils.getBoolean(BootstrapClassLoaderFactory.PROP_CP_CACHE, true)) + .newDeploymentClassLoader(); + } catch (BootstrapException e) { + throw new IllegalStateException("Failed to create the boostrap class loader", e); + } + originalCl = setCCL(appCl); + RuntimeRunner runtimeRunner = RuntimeRunner.builder() .setLaunchMode(LaunchMode.TEST) - .setClassLoader(getClass().getClassLoader()) + .setClassLoader(appCl) .setTarget(appClassLocation) .addAdditionalArchive(testClassLocation) .setClassOutput(new ClassOutput() { @@ -120,7 +131,8 @@ public void writeResource(String name, byte[] data) throws IOException { }) .setTransformerTarget(new TransformerTarget() { @Override - public void setTransformers(Map>> functions) { + public void setTransformers( + Map>> functions) { ClassLoader main = Thread.currentThread().getContextClassLoader(); //we need to use a temp class loader, or the old resource location will be cached @@ -148,7 +160,8 @@ public Enumeration getResources(String name) throws IOException { return main.getResources(name); } }; - for (Map.Entry>> e : functions.entrySet()) { + for (Map.Entry>> e : functions + .entrySet()) { String resourceName = e.getKey().replace('.', '/') + ".class"; try (InputStream stream = temp.getResourceAsStream(resourceName)) { if (stream == null) { @@ -190,6 +203,35 @@ protected ClassLoader getClassLoader() { } } }) + .addChainCustomizer(new Consumer() { + @Override + public void accept(BuildChainBuilder buildChainBuilder) { + buildChainBuilder.addBuildStep(new BuildStep() { + @Override + public void execute(BuildContext context) { + context.produce(new TestClassPredicateBuildItem(new Predicate() { + @Override + public boolean test(String className) { + return PathTestHelper.isTestClass(className, testClassLoader); + } + })); + } + }).produces(TestClassPredicateBuildItem.class) + .build(); + } + }) + .addChainCustomizer(new Consumer() { + @Override + public void accept(BuildChainBuilder buildChainBuilder) { + buildChainBuilder.addBuildStep(new BuildStep() { + @Override + public void execute(BuildContext context) { + context.produce(new TestAnnotationBuildItem(QuarkusTest.class.getName())); + } + }).produces(TestAnnotationBuildItem.class) + .build(); + } + }) .build(); runtimeRunner.run(); @@ -218,16 +260,61 @@ public void run() { @Override public void afterEach(ExtensionContext context) throws Exception { RestAssuredURLManager.clearURL(); + TestScopeManager.setup(); } @Override public void beforeEach(ExtensionContext context) throws Exception { RestAssuredURLManager.setURL(); + TestScopeManager.tearDown(); } @Override public Object createTestInstance(TestInstanceFactoryContext factoryContext, ExtensionContext extensionContext) throws TestInstantiationException { + if (failedBoot) { + try { + return extensionContext.getRequiredTestClass().newInstance(); + } catch (Exception e) { + throw new TestInstantiationException("Boot failed", e); + } + } + ExtensionContext root = extensionContext.getRoot(); + ExtensionContext.Store store = root.getStore(ExtensionContext.Namespace.GLOBAL); + ExtensionState state = store.get(ExtensionState.class.getName(), ExtensionState.class); + PropertyTestUtil.setLogFileProperty(); + boolean substrateTest = extensionContext.getRequiredTestClass().isAnnotationPresent(SubstrateTest.class); + if (state == null) { + TestResourceManager testResourceManager = new TestResourceManager(extensionContext.getRequiredTestClass()); + try { + Map systemProps = testResourceManager.start(); + + if (substrateTest) { + NativeImageLauncher launcher = new NativeImageLauncher(extensionContext.getRequiredTestClass()); + launcher.addSystemProperties(systemProps); + try { + launcher.start(); + } catch (IOException e) { + throw new JUnitException("Quarkus native image start failed, original cause: " + e); + } + state = new ExtensionState(testResourceManager, launcher, true); + } else { + state = doJavaStart(extensionContext, testResourceManager); + } + store.put(ExtensionState.class.getName(), state); + + } catch (RuntimeException e) { + testResourceManager.stop(); + failedBoot = true; + throw e; + } + } else { + if (substrateTest != state.isSubstrate()) { + throw new RuntimeException( + "Attempted to mix @SubstrateTest and JVM mode tests in the same test run. This is not allowed."); + } + } + try { Constructor ctor = factoryContext.getTestClass().getDeclaredConstructor(); ctor.setAccessible(true); @@ -240,7 +327,21 @@ public Object createTestInstance(TestInstanceFactoryContext factoryContext, Exte } } - static class ExtensionState implements ExtensionContext.Store.CloseableResource { + private static ClassLoader setCCL(ClassLoader cl) { + final Thread thread = Thread.currentThread(); + final ClassLoader original = thread.getContextClassLoader(); + thread.setContextClassLoader(cl); + return original; + } + + @Override + public void beforeAll(ExtensionContext context) throws Exception { + if (failedBoot) { + throw new TestAbortedException("Not running test as boot failed"); + } + } + + class ExtensionState implements ExtensionContext.Store.CloseableResource { private final TestResourceManager testResourceManager; private final Closeable resource; @@ -254,8 +355,17 @@ static class ExtensionState implements ExtensionContext.Store.CloseableResource @Override public void close() throws Throwable { - resource.close(); testResourceManager.stop(); + try { + resource.close(); + } finally { + if (QuarkusTestExtension.this.originalCl != null) { + setCCL(QuarkusTestExtension.this.originalCl); + } + } + if (appCl != null) { + appCl.close(); + } } public boolean isSubstrate() { diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/SubstrateTest.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/SubstrateTest.java index 27f42d0ef8a8a..b66233658fe95 100644 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/SubstrateTest.java +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/SubstrateTest.java @@ -39,7 +39,7 @@ * */ @Target(ElementType.TYPE) -@ExtendWith(QuarkusTestExtension.class) +@ExtendWith({ QuarkusTestExtension.class, DisabledOnSubstrateCondition.class }) @Retention(RetentionPolicy.RUNTIME) public @interface SubstrateTest { } diff --git a/test-framework/pom.xml b/test-framework/pom.xml index 6124eb410d693..26452e4a303a3 100644 --- a/test-framework/pom.xml +++ b/test-framework/pom.xml @@ -35,6 +35,7 @@ junit4 junit5-internal junit5 + amazon-lambda diff --git a/war-launcher/launcher/pom.xml b/war-launcher/launcher/pom.xml index f2cb55af3bac6..a5effe72fa6da 100644 --- a/war-launcher/launcher/pom.xml +++ b/war-launcher/launcher/pom.xml @@ -33,7 +33,7 @@ io.quarkus - quarkus-core + quarkus-core-deployment io.quarkus @@ -42,40 +42,40 @@ io.quarkus - quarkus-undertow + quarkus-undertow-deployment io.quarkus - quarkus-arc + quarkus-arc-deployment io.quarkus - quarkus-narayana-jta + quarkus-narayana-jta-deployment io.quarkus - quarkus-resteasy + quarkus-resteasy-deployment io.quarkus - quarkus-smallrye-rest-client + quarkus-smallrye-rest-client-deployment io.quarkus - quarkus-smallrye-health + quarkus-smallrye-health-deployment io.quarkus - quarkus-hibernate-orm + quarkus-hibernate-orm-deployment io.quarkus - quarkus-smallrye-metrics + quarkus-smallrye-metrics-deployment io.quarkus - quarkus-smallrye-openapi + quarkus-smallrye-openapi-deployment diff --git a/war-launcher/runner/pom.xml b/war-launcher/runner/pom.xml index 6d68b97de089c..0e312c89abc26 100644 --- a/war-launcher/runner/pom.xml +++ b/war-launcher/runner/pom.xml @@ -33,7 +33,7 @@ io.quarkus - quarkus-core + quarkus-core-deployment