diff --git a/.github/native-tests.json b/.github/native-tests.json
index 51c1808bf56ca9..08fb441174abfc 100644
--- a/.github/native-tests.json
+++ b/.github/native-tests.json
@@ -21,7 +21,7 @@
{
"category": "Data3",
"timeout": 70,
- "test-modules": "flyway, hibernate-orm-panache, hibernate-orm-panache-kotlin, hibernate-orm-envers, liquibase",
+ "test-modules": "flyway, hibernate-orm-panache, hibernate-orm-panache-kotlin, hibernate-orm-envers, liquibase, liquibase-mongodb",
"os-name": "ubuntu-latest"
},
{
diff --git a/.github/workflows/ci-actions-incremental.yml b/.github/workflows/ci-actions-incremental.yml
index 375315315fe8bd..c5011fc09bcec6 100644
--- a/.github/workflows/ci-actions-incremental.yml
+++ b/.github/workflows/ci-actions-incremental.yml
@@ -178,9 +178,10 @@ jobs:
uses: actions/upload-artifact@v2
if: ${{ failure() || cancelled() }}
with:
- name: "build-reports-JVM Tests - JDK ${{matrix.java.name}}"
+ name: "build-reports-Initial JDK 11 Build"
path: |
target/build-report.json
+ LICENSE.txt
retention-days: 2
calculate-test-jobs:
@@ -322,6 +323,7 @@ jobs:
path: |
**/target/*-reports/TEST-*.xml
target/build-report.json
+ LICENSE.txt
retention-days: 2
- name: Upload gc.log
uses: actions/upload-artifact@v2
@@ -388,6 +390,7 @@ jobs:
path: |
**/target/*-reports/TEST-*.xml
target/build-report.json
+ LICENSE.txt
retention-days: 2
gradle-tests:
@@ -447,6 +450,7 @@ jobs:
**/build/test-results/test/TEST-*.xml
**/target/*-reports/TEST-*.xml
target/build-report.json
+ LICENSE.txt
retention-days: 2
devtools-tests:
@@ -506,6 +510,7 @@ jobs:
path: |
**/target/*-reports/TEST-*.xml
target/build-report.json
+ LICENSE.txt
retention-days: 2
tcks-test:
@@ -564,6 +569,7 @@ jobs:
path: |
**/target/*-reports/TEST-*.xml
target/build-report.json
+ LICENSE.txt
retention-days: 2
native-tests:
@@ -643,4 +649,5 @@ jobs:
**/target/*-reports/TEST-*.xml
**/build/test-results/test/TEST-*.xml
target/build-report.json
+ LICENSE.txt
retention-days: 2
diff --git a/bom/application/pom.xml b/bom/application/pom.xml
index f1c39ced2103be..7acd0a5f7ff49e 100644
--- a/bom/application/pom.xml
+++ b/bom/application/pom.xml
@@ -17,7 +17,7 @@
1.691.0.2.11.0.12.2
- 2.3.1.Final
+ 2.4.0.Final4.7.0.Final0.33.01.0.0
@@ -45,15 +45,15 @@
2.4.43.1.13.0.1
- 2.1.10
- 1.3.1
+ 2.1.12
+ 1.3.22.0.15.2.13.2.11.2.01.0.132.6.0
- 2.12.0
+ 2.13.03.9.11.2.11.3.5
@@ -109,7 +109,7 @@
1.16.1.Final1.8.7.Final3.4.2.Final
- 4.1.2
+ 4.1.34.5.134.4.144.1.4
@@ -120,7 +120,7 @@
2.7.48.0.267.2.2.jre8
- 21.1.0.0
+ 21.3.0.010.14.2.011.5.6.01.2.6
@@ -131,7 +131,7 @@
12.1.7.Final4.4.1.Final2.9.2
- 4.1.65.Final
+ 4.1.67.Final1.0.33.4.2.Final1.0.0
@@ -145,7 +145,7 @@
3.10.01.62.9.1
- 2.17.29
+ 2.17.352.40.01.4.21.5.30
@@ -156,27 +156,28 @@
4.1.01.0.95.12.0.202106070339-r
- 7.14.0
+ 7.15.01.0.94.4.3
+ 4.4.31.296.0.04.3.4
- 4.3.1
+ 4.3.21.2.12.17.0
- 0.33.8
+ 0.33.93.14.95.1.20.1.0
- 5.7.0
+ 5.7.22.2.05.2.SP42.1.SP25.2.Final2.1.SP13.12.4
- 5.3.1
+ 5.8.04.9.21.1.4.Final14.0.0
@@ -352,6 +353,15 @@
import
+
+
+ com.oracle.database.jdbc
+ ojdbc-bom
+ ${oracle-jdbc.version}
+ pom
+ import
+
+
io.smallrye.reactive
@@ -818,6 +828,16 @@
quarkus-liquibase-deployment${project.version}
+
+ io.quarkus
+ quarkus-liquibase-mongodb
+ ${project.version}
+
+
+ io.quarkus
+ quarkus-liquibase-mongodb-deployment
+ ${project.version}
+ io.quarkusquarkus-hibernate-orm
@@ -1540,13 +1560,6 @@
quarkus-vertx-http-deployment${project.version}
-
- io.quarkus
- quarkus-vertx-http-deployment
- ${project.version}
- test-jar
- test
- io.quarkusquarkus-vertx-web
@@ -1607,6 +1620,11 @@
quarkus-mailer${project.version}
+
+ io.quarkus
+ quarkus-mailer-deployment
+ ${project.version}
+ io.quarkusquarkus-mongodb-client
@@ -1924,6 +1942,21 @@
quarkus-amazon-common${project.version}
+
+ io.quarkus
+ quarkus-amazon-lambda-event-server
+ ${project.version}
+
+
+ io.quarkus
+ quarkus-amazon-lambda-http-event-server
+ ${project.version}
+
+
+ io.quarkus
+ quarkus-amazon-lambda-rest-event-server
+ ${project.version}
+ io.quarkusquarkus-amazon-common-deployment
@@ -4488,11 +4521,6 @@
mssql-jdbc${mssql-jdbc.version}
-
- com.oracle.database.jdbc
- ojdbc11
- ${oracle-jdbc.version}
- org.elasticsearch.clientelasticsearch-rest-client
@@ -5061,6 +5089,11 @@
+
+ org.liquibase.ext
+ liquibase-mongodb
+ ${liquibase-mongodb.version}
+ org.yamlsnakeyaml
diff --git a/build-parent/pom.xml b/build-parent/pom.xml
index e7c6e7f70fe40c..605cba756dbd33 100644
--- a/build-parent/pom.xml
+++ b/build-parent/pom.xml
@@ -34,7 +34,7 @@
${version.surefire.plugin}
- 1.1.1
+ 1.2.0
+
+ io.quarkus
+ quarkus-apache-httpclient
+ test
+
+
+
+
+
+
+
+
diff --git a/extensions/amazon-lambda-http/http-event-server/src/main/java/io/quarkus/amazon/lambda/runtime/MockHttpEventServer.java b/extensions/amazon-lambda-http/http-event-server/src/main/java/io/quarkus/amazon/lambda/runtime/MockHttpEventServer.java
new file mode 100644
index 00000000000000..c5f255c3ccebf4
--- /dev/null
+++ b/extensions/amazon-lambda-http/http-event-server/src/main/java/io/quarkus/amazon/lambda/runtime/MockHttpEventServer.java
@@ -0,0 +1,149 @@
+package io.quarkus.amazon.lambda.runtime;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.UUID;
+
+import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent;
+import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPResponse;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.MapperFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectReader;
+import com.fasterxml.jackson.databind.ObjectWriter;
+
+import io.vertx.core.buffer.Buffer;
+import io.vertx.core.http.HttpServerResponse;
+import io.vertx.ext.web.RoutingContext;
+
+public class MockHttpEventServer extends MockEventServer {
+
+ private final ObjectMapper objectMapper;
+ private final ObjectWriter eventWriter;
+ private final ObjectReader responseReader;
+
+ public MockHttpEventServer() {
+ objectMapper = new ObjectMapper();
+ objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
+ .configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
+ eventWriter = objectMapper.writerFor(APIGatewayV2HTTPEvent.class);
+ responseReader = objectMapper.readerFor(APIGatewayV2HTTPResponse.class);
+ }
+
+ @Override
+ protected void defaultHanderSetup() {
+ router.route().handler(this::handleHttpRequests);
+ }
+
+ public void handleHttpRequests(RoutingContext ctx) {
+ String requestId = ctx.request().getHeader(AmazonLambdaApi.LAMBDA_RUNTIME_AWS_REQUEST_ID);
+ if (requestId == null) {
+ requestId = UUID.randomUUID().toString();
+ }
+ ctx.put(AmazonLambdaApi.LAMBDA_RUNTIME_AWS_REQUEST_ID, requestId);
+ String traceId = ctx.request().getHeader(AmazonLambdaApi.LAMBDA_RUNTIME_AWS_REQUEST_ID);
+ if (traceId == null) {
+ traceId = UUID.randomUUID().toString();
+ }
+ ctx.put(AmazonLambdaApi.LAMBDA_TRACE_HEADER_KEY, traceId);
+ Buffer body = ctx.getBody();
+
+ APIGatewayV2HTTPEvent event = new APIGatewayV2HTTPEvent();
+ event.setRequestContext(new APIGatewayV2HTTPEvent.RequestContext());
+ event.getRequestContext().setHttp(new APIGatewayV2HTTPEvent.RequestContext.Http());
+ event.getRequestContext().getHttp().setMethod(ctx.request().method().name());
+ event.setRawPath(ctx.request().path());
+ event.setRawQueryString(ctx.request().query());
+ for (String header : ctx.request().headers().names()) {
+ if (event.getHeaders() == null)
+ event.setHeaders(new HashMap<>());
+ List values = ctx.request().headers().getAll(header);
+ String value = String.join(",", values);
+ event.getHeaders().put(header, value);
+ }
+ if (body != null) {
+ String ct = ctx.request().getHeader("content-type");
+ if (ct == null || isBinary(ct)) {
+ String encoded = Base64.getMimeEncoder().encodeToString(body.getBytes());
+ event.setBody(encoded);
+ event.setIsBase64Encoded(true);
+ } else {
+ event.setBody(new String(body.getBytes(), StandardCharsets.UTF_8));
+ }
+ }
+
+ try {
+ byte[] mEvent = eventWriter.writeValueAsBytes(event);
+ ctx.put(APIGatewayV2HTTPEvent.class.getName(), mEvent);
+ queue.put(ctx);
+ } catch (Exception e) {
+ log.error("Publish failure", e);
+ ctx.fail(500);
+ }
+ }
+
+ @Override
+ protected String getEventContentType(RoutingContext request) {
+ if (request.get(APIGatewayV2HTTPEvent.class.getName()) != null)
+ return "application/json";
+ return super.getEventContentType(request);
+ }
+
+ @Override
+ protected Buffer processEventBody(RoutingContext request) {
+ byte[] buf = request.get(APIGatewayV2HTTPEvent.class.getName());
+ if (buf != null) {
+ return Buffer.buffer(buf);
+ }
+ return super.processEventBody(request);
+ }
+
+ @Override
+ public void processResponse(RoutingContext ctx, RoutingContext pending, Buffer buffer) {
+ if (pending.get(APIGatewayV2HTTPEvent.class.getName()) != null) {
+ try {
+ APIGatewayV2HTTPResponse res = responseReader.readValue(buffer.getBytes());
+ HttpServerResponse response = pending.response();
+ if (res.getHeaders() != null) {
+ for (Map.Entry header : res.getHeaders().entrySet()) {
+ for (String val : header.getValue().split(",")) {
+ response.headers().add(header.getKey(), val);
+ }
+ }
+ }
+ response.setStatusCode(res.getStatusCode());
+ String body = res.getBody();
+ if (body != null) {
+ if (res.getIsBase64Encoded()) {
+ byte[] bytes = Base64.getDecoder().decode(body);
+ response.end(Buffer.buffer(bytes));
+ } else {
+ response.end(body);
+ }
+ } else {
+ response.end();
+ }
+
+ } catch (IOException e) {
+ log.error("Publish failure", e);
+ pending.fail(500);
+ }
+ } else {
+ super.processResponse(ctx, pending, buffer);
+ }
+ }
+
+ private boolean isBinary(String contentType) {
+ if (contentType != null) {
+ String ct = contentType.toLowerCase(Locale.ROOT);
+ return !(ct.startsWith("text") || ct.contains("json") || ct.contains("xml") || ct.contains("yaml"));
+ }
+ return false;
+ }
+
+}
diff --git a/extensions/amazon-lambda-http/http-event-server/src/test/java/io/quarkus/amazon/lambda/runtime/EventServerTest.java b/extensions/amazon-lambda-http/http-event-server/src/test/java/io/quarkus/amazon/lambda/runtime/EventServerTest.java
new file mode 100644
index 00000000000000..831049128008b5
--- /dev/null
+++ b/extensions/amazon-lambda-http/http-event-server/src/test/java/io/quarkus/amazon/lambda/runtime/EventServerTest.java
@@ -0,0 +1,82 @@
+package io.quarkus.amazon.lambda.runtime;
+
+import java.util.HashMap;
+import java.util.concurrent.Future;
+
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Response;
+
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent;
+import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPResponse;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.MapperFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectReader;
+import com.fasterxml.jackson.databind.ObjectWriter;
+
+public class EventServerTest {
+
+ static MockEventServer server;
+ static ObjectMapper mapper;
+ static ObjectReader eventReader;
+ static ObjectWriter resWriter;
+
+ @BeforeAll
+ public static void start() {
+ server = new MockHttpEventServer();
+ server.start();
+ mapper = new ObjectMapper();
+ mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
+ .configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
+ eventReader = mapper.readerFor(APIGatewayV2HTTPEvent.class);
+ resWriter = mapper.writerFor(APIGatewayV2HTTPResponse.class);
+ }
+
+ @AfterAll
+ public static void end() throws Exception {
+ server.close();
+ }
+
+ @Test
+ public void testServer() throws Exception {
+ Client client = ClientBuilder.newBuilder().build();
+ WebTarget base = client.target("http://localhost:" + MockEventServer.DEFAULT_PORT);
+ Future lambdaInvoke = base.request().async()
+ .post(Entity.text("Hello World"));
+
+ Response next = base.path(MockEventServer.NEXT_INVOCATION).request().get();
+ Assertions.assertEquals(200, next.getStatus());
+ String requestId = next.getHeaderString(AmazonLambdaApi.LAMBDA_RUNTIME_AWS_REQUEST_ID);
+ String traceId = next.getHeaderString(AmazonLambdaApi.LAMBDA_TRACE_HEADER_KEY);
+ Assertions.assertNotNull(requestId);
+ Assertions.assertNotNull(traceId);
+ String json = next.readEntity(String.class);
+ APIGatewayV2HTTPEvent event = eventReader.readValue(json);
+ Assertions.assertEquals("text/plain", event.getHeaders().get("Content-Type"));
+ Assertions.assertEquals("Hello World", event.getBody());
+ next.close();
+
+ APIGatewayV2HTTPResponse res = new APIGatewayV2HTTPResponse();
+ res.setStatusCode(201);
+ res.setHeaders(new HashMap());
+ res.getHeaders().put("Content-Type", "text/plain");
+ res.setBody("Hi");
+ Response sendResponse = base.path(MockEventServer.INVOCATION).path(requestId).path("response")
+ .request().post(Entity.json(resWriter.writeValueAsString(res)));
+ Assertions.assertEquals(204, sendResponse.getStatus());
+ sendResponse.close();
+
+ Response lambdaResponse = lambdaInvoke.get();
+ Assertions.assertEquals(201, lambdaResponse.getStatus());
+ Assertions.assertEquals("Hi", lambdaResponse.readEntity(String.class));
+ lambdaResponse.close();
+ }
+}
diff --git a/extensions/amazon-lambda-http/pom.xml b/extensions/amazon-lambda-http/pom.xml
index 61c49706a6b979..78c20a2fb26b2d 100644
--- a/extensions/amazon-lambda-http/pom.xml
+++ b/extensions/amazon-lambda-http/pom.xml
@@ -17,7 +17,9 @@
runtime
+ http-event-serverdeployment
+
maven-archetype
diff --git a/extensions/amazon-lambda-http/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/amazon-lambda-http/runtime/src/main/resources/META-INF/quarkus-extension.yaml
index 3ee6b0b861cd66..295b2b80f286c1 100644
--- a/extensions/amazon-lambda-http/runtime/src/main/resources/META-INF/quarkus-extension.yaml
+++ b/extensions/amazon-lambda-http/runtime/src/main/resources/META-INF/quarkus-extension.yaml
@@ -11,4 +11,4 @@ metadata:
categories:
- "cloud"
guide: "https://quarkus.io/guides/amazon-lambda-http"
- status: "preview"
+ status: "stable"
diff --git a/extensions/amazon-lambda-rest/deployment/pom.xml b/extensions/amazon-lambda-rest/deployment/pom.xml
index 32362c7132335c..97aa14362fcb06 100644
--- a/extensions/amazon-lambda-rest/deployment/pom.xml
+++ b/extensions/amazon-lambda-rest/deployment/pom.xml
@@ -39,6 +39,10 @@
io.quarkusquarkus-amazon-lambda-rest
+
+ io.quarkus
+ quarkus-amazon-lambda-rest-event-server
+
diff --git a/extensions/amazon-lambda-rest/deployment/src/main/java/io/quarkus/amazon/lambda/http/deployment/AmazonLambdaHttpProcessor.java b/extensions/amazon-lambda-rest/deployment/src/main/java/io/quarkus/amazon/lambda/http/deployment/AmazonLambdaHttpProcessor.java
index 68242ca8e9f806..822459c06fe63c 100644
--- a/extensions/amazon-lambda-rest/deployment/src/main/java/io/quarkus/amazon/lambda/http/deployment/AmazonLambdaHttpProcessor.java
+++ b/extensions/amazon-lambda-rest/deployment/src/main/java/io/quarkus/amazon/lambda/http/deployment/AmazonLambdaHttpProcessor.java
@@ -20,12 +20,10 @@
import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
-import io.quarkus.deployment.builditem.LaunchModeBuildItem;
import io.quarkus.deployment.builditem.SystemPropertyBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
import io.quarkus.deployment.pkg.builditem.ArtifactResultBuildItem;
import io.quarkus.deployment.pkg.builditem.OutputTargetBuildItem;
-import io.quarkus.runtime.LaunchMode;
import io.quarkus.vertx.http.deployment.RequireVirtualHttpBuildItem;
import io.vertx.core.file.impl.FileResolver;
@@ -46,8 +44,8 @@ public void setupSecurity(BuildProducer additionalBeans
}
@BuildStep
- public RequireVirtualHttpBuildItem requestVirtualHttp(LaunchModeBuildItem launchMode) {
- return launchMode.getLaunchMode() == LaunchMode.NORMAL ? RequireVirtualHttpBuildItem.MARKER : null;
+ public RequireVirtualHttpBuildItem requestVirtualHttp() {
+ return RequireVirtualHttpBuildItem.ALWAYS_VIRTUAL;
}
@BuildStep
diff --git a/extensions/amazon-lambda-rest/deployment/src/main/java/io/quarkus/amazon/lambda/http/deployment/DevServicesRestLambdaProcessor.java b/extensions/amazon-lambda-rest/deployment/src/main/java/io/quarkus/amazon/lambda/http/deployment/DevServicesRestLambdaProcessor.java
new file mode 100644
index 00000000000000..5e9f952f55badf
--- /dev/null
+++ b/extensions/amazon-lambda-rest/deployment/src/main/java/io/quarkus/amazon/lambda/http/deployment/DevServicesRestLambdaProcessor.java
@@ -0,0 +1,14 @@
+package io.quarkus.amazon.lambda.http.deployment;
+
+import io.quarkus.amazon.lambda.deployment.EventServerOverrideBuildItem;
+import io.quarkus.amazon.lambda.runtime.MockRestEventServer;
+import io.quarkus.deployment.annotations.BuildStep;
+
+public class DevServicesRestLambdaProcessor {
+
+ @BuildStep
+ public EventServerOverrideBuildItem overrideEventServer() {
+ return new EventServerOverrideBuildItem(
+ () -> new MockRestEventServer());
+ }
+}
diff --git a/extensions/amazon-lambda-rest/pom.xml b/extensions/amazon-lambda-rest/pom.xml
index 34cb1cf262363d..3e194bce78c241 100644
--- a/extensions/amazon-lambda-rest/pom.xml
+++ b/extensions/amazon-lambda-rest/pom.xml
@@ -17,6 +17,7 @@
runtime
+ rest-event-serverdeploymentmaven-archetype
diff --git a/extensions/amazon-lambda-rest/rest-event-server/pom.xml b/extensions/amazon-lambda-rest/rest-event-server/pom.xml
new file mode 100644
index 00000000000000..81b5fd7b97743b
--- /dev/null
+++ b/extensions/amazon-lambda-rest/rest-event-server/pom.xml
@@ -0,0 +1,59 @@
+
+
+ 4.0.0
+
+
+ io.quarkus
+ quarkus-amazon-lambda-rest-parent
+ 999-SNAPSHOT
+ ../pom.xml
+
+
+ quarkus-amazon-lambda-rest-event-server
+ Quarkus - Amazon Lambda REST Event Server
+ AWS Lambda REST Mock Lambda event server for testing and dev mode
+
+
+
+ io.quarkus
+ quarkus-amazon-lambda-event-server
+
+
+ io.quarkus
+ quarkus-amazon-lambda-rest
+
+
+ org.junit.jupiter
+ junit-jupiter
+ test
+
+
+ org.jboss.resteasy
+ resteasy-client
+ test
+
+
+ commons-logging
+ commons-logging
+
+
+ jakarta.activation
+ jakarta.activation-api
+
+
+
+
+
+ io.quarkus
+ quarkus-apache-httpclient
+ test
+
+
+
+
+
+
+
+
diff --git a/extensions/amazon-lambda-rest/rest-event-server/src/main/java/io/quarkus/amazon/lambda/runtime/MockRestEventServer.java b/extensions/amazon-lambda-rest/rest-event-server/src/main/java/io/quarkus/amazon/lambda/runtime/MockRestEventServer.java
new file mode 100644
index 00000000000000..4a3665eaf21889
--- /dev/null
+++ b/extensions/amazon-lambda-rest/rest-event-server/src/main/java/io/quarkus/amazon/lambda/runtime/MockRestEventServer.java
@@ -0,0 +1,173 @@
+package io.quarkus.amazon.lambda.runtime;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.UUID;
+
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.MapperFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectReader;
+import com.fasterxml.jackson.databind.ObjectWriter;
+
+import io.quarkus.amazon.lambda.http.model.AwsProxyRequest;
+import io.quarkus.amazon.lambda.http.model.AwsProxyRequestContext;
+import io.quarkus.amazon.lambda.http.model.AwsProxyResponse;
+import io.quarkus.amazon.lambda.http.model.Headers;
+import io.quarkus.amazon.lambda.http.model.MultiValuedTreeMap;
+import io.vertx.core.buffer.Buffer;
+import io.vertx.core.http.HttpServerResponse;
+import io.vertx.ext.web.RoutingContext;
+
+public class MockRestEventServer extends MockEventServer {
+
+ private final ObjectMapper objectMapper;
+ private final ObjectWriter eventWriter;
+ private final ObjectReader responseReader;
+
+ public MockRestEventServer() {
+ objectMapper = new ObjectMapper();
+ objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
+ .configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
+ eventWriter = objectMapper.writerFor(AwsProxyRequest.class);
+ responseReader = objectMapper.readerFor(AwsProxyResponse.class);
+ }
+
+ @Override
+ protected void defaultHanderSetup() {
+ router.route().handler(this::handleHttpRequests);
+ }
+
+ public void handleHttpRequests(RoutingContext ctx) {
+ String requestId = ctx.request().getHeader(AmazonLambdaApi.LAMBDA_RUNTIME_AWS_REQUEST_ID);
+ if (requestId == null) {
+ requestId = UUID.randomUUID().toString();
+ }
+ ctx.put(AmazonLambdaApi.LAMBDA_RUNTIME_AWS_REQUEST_ID, requestId);
+ String traceId = ctx.request().getHeader(AmazonLambdaApi.LAMBDA_RUNTIME_AWS_REQUEST_ID);
+ if (traceId == null) {
+ traceId = UUID.randomUUID().toString();
+ }
+ ctx.put(AmazonLambdaApi.LAMBDA_TRACE_HEADER_KEY, traceId);
+ Buffer body = ctx.getBody();
+
+ AwsProxyRequest event = new AwsProxyRequest();
+ event.setRequestContext(new AwsProxyRequestContext());
+ event.getRequestContext().setRequestId(requestId);
+ event.getRequestContext().setHttpMethod(ctx.request().method().name());
+ event.setHttpMethod(ctx.request().method().name());
+ event.setPath(ctx.request().path());
+ if (ctx.request().query() != null) {
+ event.setMultiValueQueryStringParameters(new MultiValuedTreeMap<>());
+ String[] params = ctx.request().query().split("&");
+ for (String param : params) {
+ if (param.contains("=")) {
+ String[] keyval = param.split("=");
+ try {
+ event.getMultiValueQueryStringParameters().add(
+ URLDecoder.decode(keyval[0], StandardCharsets.UTF_8.name()),
+ URLDecoder.decode(keyval[1], StandardCharsets.UTF_8.name()));
+ } catch (UnsupportedEncodingException e) {
+ log.error("Failed to parse query string", e);
+ ctx.response().setStatusCode(400).end();
+ return;
+ }
+ }
+ }
+
+ }
+ if (ctx.request().headers() != null) {
+ event.setMultiValueHeaders(new Headers());
+ for (String header : ctx.request().headers().names()) {
+ List values = ctx.request().headers().getAll(header);
+ for (String val : values)
+ event.getMultiValueHeaders().add(header, val);
+ }
+ }
+ if (body != null) {
+ String ct = ctx.request().getHeader("content-type");
+ if (ct == null || isBinary(ct)) {
+ String encoded = Base64.getMimeEncoder().encodeToString(body.getBytes());
+ event.setBody(encoded);
+ event.setIsBase64Encoded(true);
+ } else {
+ event.setBody(new String(body.getBytes(), StandardCharsets.UTF_8));
+ }
+ }
+
+ try {
+ byte[] mEvent = eventWriter.writeValueAsBytes(event);
+ ctx.put(AwsProxyRequest.class.getName(), mEvent);
+ queue.put(ctx);
+ } catch (Exception e) {
+ log.error("Publish failure", e);
+ ctx.fail(500);
+ }
+ }
+
+ @Override
+ protected String getEventContentType(RoutingContext request) {
+ if (request.get(AwsProxyRequest.class.getName()) != null)
+ return "application/json";
+ return super.getEventContentType(request);
+ }
+
+ @Override
+ protected Buffer processEventBody(RoutingContext request) {
+ byte[] buf = request.get(AwsProxyRequest.class.getName());
+ if (buf != null) {
+ return Buffer.buffer(buf);
+ }
+ return super.processEventBody(request);
+ }
+
+ @Override
+ public void processResponse(RoutingContext ctx, RoutingContext pending, Buffer buffer) {
+ if (pending.get(AwsProxyRequest.class.getName()) != null) {
+ try {
+ AwsProxyResponse res = responseReader.readValue(buffer.getBytes());
+ HttpServerResponse response = pending.response();
+ if (res.getMultiValueHeaders() != null) {
+ for (Map.Entry> header : res.getMultiValueHeaders().entrySet()) {
+ for (String val : header.getValue()) {
+ response.headers().add(header.getKey(), val);
+ }
+ }
+ }
+ response.setStatusCode(res.getStatusCode());
+ String body = res.getBody();
+ if (body != null) {
+ if (res.isBase64Encoded()) {
+ byte[] bytes = Base64.getDecoder().decode(body);
+ response.end(Buffer.buffer(bytes));
+ } else {
+ response.end(body);
+ }
+ } else {
+ response.end();
+ }
+
+ } catch (IOException e) {
+ log.error("Publish failure", e);
+ pending.fail(500);
+ }
+ } else {
+ super.processResponse(ctx, pending, buffer);
+ }
+ }
+
+ private boolean isBinary(String contentType) {
+ if (contentType != null) {
+ String ct = contentType.toLowerCase(Locale.ROOT);
+ return !(ct.startsWith("text") || ct.contains("json") || ct.contains("xml") || ct.contains("yaml"));
+ }
+ return false;
+ }
+
+}
diff --git a/extensions/amazon-lambda-rest/rest-event-server/src/test/java/io/quarkus/amazon/lambda/runtime/EventServerTest.java b/extensions/amazon-lambda-rest/rest-event-server/src/test/java/io/quarkus/amazon/lambda/runtime/EventServerTest.java
new file mode 100644
index 00000000000000..508ea7e4bbabb6
--- /dev/null
+++ b/extensions/amazon-lambda-rest/rest-event-server/src/test/java/io/quarkus/amazon/lambda/runtime/EventServerTest.java
@@ -0,0 +1,83 @@
+package io.quarkus.amazon.lambda.runtime;
+
+import java.util.HashMap;
+import java.util.concurrent.Future;
+
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Response;
+
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.MapperFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectReader;
+import com.fasterxml.jackson.databind.ObjectWriter;
+
+import io.quarkus.amazon.lambda.http.model.AwsProxyRequest;
+import io.quarkus.amazon.lambda.http.model.AwsProxyResponse;
+
+public class EventServerTest {
+
+ static MockEventServer server;
+ static ObjectMapper mapper;
+ static ObjectReader eventReader;
+ static ObjectWriter resWriter;
+
+ @BeforeAll
+ public static void start() {
+ server = new MockRestEventServer();
+ server.start();
+ mapper = new ObjectMapper();
+ mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
+ .configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
+ eventReader = mapper.readerFor(AwsProxyRequest.class);
+ resWriter = mapper.writerFor(AwsProxyResponse.class);
+ }
+
+ @AfterAll
+ public static void end() throws Exception {
+ server.close();
+ }
+
+ @Test
+ public void testServer() throws Exception {
+ Client client = ClientBuilder.newBuilder().build();
+ WebTarget base = client.target("http://localhost:" + MockEventServer.DEFAULT_PORT);
+ Future lambdaInvoke = base.request().async()
+ .post(Entity.text("Hello World"));
+
+ Response next = base.path(MockEventServer.NEXT_INVOCATION).request().get();
+ Assertions.assertEquals(200, next.getStatus());
+ String requestId = next.getHeaderString(AmazonLambdaApi.LAMBDA_RUNTIME_AWS_REQUEST_ID);
+ String traceId = next.getHeaderString(AmazonLambdaApi.LAMBDA_TRACE_HEADER_KEY);
+ Assertions.assertNotNull(requestId);
+ Assertions.assertNotNull(traceId);
+ String json = next.readEntity(String.class);
+ AwsProxyRequest event = eventReader.readValue(json);
+ Assertions.assertEquals("text/plain", event.getMultiValueHeaders().getFirst("Content-Type"));
+ Assertions.assertEquals("Hello World", event.getBody());
+ next.close();
+
+ AwsProxyResponse res = new AwsProxyResponse();
+ res.setStatusCode(201);
+ res.setHeaders(new HashMap());
+ res.getHeaders().put("Content-Type", "text/plain");
+ res.setBody("Hi");
+ Response sendResponse = base.path(MockEventServer.INVOCATION).path(requestId).path("response")
+ .request().post(Entity.json(resWriter.writeValueAsString(res)));
+ Assertions.assertEquals(204, sendResponse.getStatus());
+ sendResponse.close();
+
+ Response lambdaResponse = lambdaInvoke.get();
+ Assertions.assertEquals(201, lambdaResponse.getStatus());
+ Assertions.assertEquals("Hi", lambdaResponse.readEntity(String.class));
+ lambdaResponse.close();
+ }
+}
diff --git a/extensions/amazon-lambda-rest/runtime/src/main/java/io/quarkus/amazon/lambda/http/graal/LambdaContainerHandlerSubstitution.java b/extensions/amazon-lambda-rest/runtime/src/main/java/io/quarkus/amazon/lambda/http/graal/LambdaContainerHandlerSubstitution.java
index e2f2ede57953ab..d1c9ffebdd1960 100644
--- a/extensions/amazon-lambda-rest/runtime/src/main/java/io/quarkus/amazon/lambda/http/graal/LambdaContainerHandlerSubstitution.java
+++ b/extensions/amazon-lambda-rest/runtime/src/main/java/io/quarkus/amazon/lambda/http/graal/LambdaContainerHandlerSubstitution.java
@@ -5,7 +5,7 @@
import com.oracle.svm.core.annotate.TargetClass;
@TargetClass(LambdaContainerHandler.class)
-public class LambdaContainerHandlerSubstitution {
+public final class LambdaContainerHandlerSubstitution {
// afterburner does not work in native mode, so let's ensure it's never registered
@Substitute
diff --git a/extensions/amazon-lambda-rest/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/amazon-lambda-rest/runtime/src/main/resources/META-INF/quarkus-extension.yaml
index 8636a6f8832a44..992fa650deacb1 100644
--- a/extensions/amazon-lambda-rest/runtime/src/main/resources/META-INF/quarkus-extension.yaml
+++ b/extensions/amazon-lambda-rest/runtime/src/main/resources/META-INF/quarkus-extension.yaml
@@ -11,6 +11,6 @@ metadata:
categories:
- "cloud"
guide: "https://quarkus.io/guides/amazon-lambda-http"
- status: "preview"
+ status: "stable"
config:
- "quarkus.lambda-http."
diff --git a/extensions/amazon-lambda-xray/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/amazon-lambda-xray/runtime/src/main/resources/META-INF/quarkus-extension.yaml
index b68cb8deb0e6ad..64721449014f47 100644
--- a/extensions/amazon-lambda-xray/runtime/src/main/resources/META-INF/quarkus-extension.yaml
+++ b/extensions/amazon-lambda-xray/runtime/src/main/resources/META-INF/quarkus-extension.yaml
@@ -10,4 +10,4 @@ metadata:
guide: "https://quarkus.io/guides/amazon-lambda#tracing-with-aws-xray-and-graalvm"
categories:
- "cloud"
- status: "preview"
\ No newline at end of file
+ status: "stable"
\ No newline at end of file
diff --git a/extensions/amazon-lambda/common-deployment/pom.xml b/extensions/amazon-lambda/common-deployment/pom.xml
index f2686d8d098c60..9e34d9282e0ce9 100644
--- a/extensions/amazon-lambda/common-deployment/pom.xml
+++ b/extensions/amazon-lambda/common-deployment/pom.xml
@@ -35,6 +35,10 @@
io.quarkusquarkus-amazon-lambda-common
+
+ io.quarkus
+ quarkus-amazon-lambda-event-server
+ io.quarkusquarkus-junit5-internal
diff --git a/extensions/amazon-lambda/common-deployment/src/main/java/io/quarkus/amazon/lambda/deployment/AmazonLambdaCommonProcessor.java b/extensions/amazon-lambda/common-deployment/src/main/java/io/quarkus/amazon/lambda/deployment/AmazonLambdaCommonProcessor.java
index df692697ed573a..eafa77114b062e 100644
--- a/extensions/amazon-lambda/common-deployment/src/main/java/io/quarkus/amazon/lambda/deployment/AmazonLambdaCommonProcessor.java
+++ b/extensions/amazon-lambda/common-deployment/src/main/java/io/quarkus/amazon/lambda/deployment/AmazonLambdaCommonProcessor.java
@@ -5,7 +5,6 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import io.quarkus.amazon.lambda.runtime.AmazonLambdaMapperRecorder;
-import io.quarkus.amazon.lambda.runtime.LambdaBuildTimeConfig;
import io.quarkus.arc.deployment.BeanContainerBuildItem;
import io.quarkus.arc.deployment.UnremovableBeanBuildItem;
import io.quarkus.deployment.annotations.BuildProducer;
@@ -75,12 +74,11 @@ public void initContextReaders(AmazonLambdaMapperRecorder recorder,
@BuildStep
@Record(value = ExecutionTime.STATIC_INIT)
- void initContextReaders(LambdaBuildTimeConfig config,
- AmazonLambdaMapperRecorder recorder,
+ void initContextReaders(AmazonLambdaMapperRecorder recorder,
LambdaObjectMapperInitializedBuildItem dependency,
LaunchModeBuildItem launchModeBuildItem) {
LaunchMode mode = launchModeBuildItem.getLaunchMode();
- if (config.enablePollingJvmMode && mode.isDevOrTest()) {
+ if (mode.isDevOrTest()) {
// only need context readers in native or dev or test mode
recorder.initContextReaders();
}
diff --git a/extensions/amazon-lambda/common-deployment/src/main/java/io/quarkus/amazon/lambda/deployment/DevServicesLambdaProcessor.java b/extensions/amazon-lambda/common-deployment/src/main/java/io/quarkus/amazon/lambda/deployment/DevServicesLambdaProcessor.java
new file mode 100644
index 00000000000000..6b41e5100b0c96
--- /dev/null
+++ b/extensions/amazon-lambda/common-deployment/src/main/java/io/quarkus/amazon/lambda/deployment/DevServicesLambdaProcessor.java
@@ -0,0 +1,95 @@
+package io.quarkus.amazon.lambda.deployment;
+
+import static io.quarkus.deployment.annotations.ExecutionTime.STATIC_INIT;
+
+import java.util.Optional;
+import java.util.function.Supplier;
+
+import org.jboss.logging.Logger;
+
+import io.quarkus.amazon.lambda.runtime.AmazonLambdaApi;
+import io.quarkus.amazon.lambda.runtime.LambdaHotReplacementRecorder;
+import io.quarkus.amazon.lambda.runtime.MockEventServer;
+import io.quarkus.bootstrap.classloading.QuarkusClassLoader;
+import io.quarkus.deployment.IsNormal;
+import io.quarkus.deployment.annotations.BuildProducer;
+import io.quarkus.deployment.annotations.BuildStep;
+import io.quarkus.deployment.annotations.Record;
+import io.quarkus.deployment.builditem.DevServicesNativeConfigResultBuildItem;
+import io.quarkus.deployment.builditem.LaunchModeBuildItem;
+import io.quarkus.deployment.builditem.ServiceStartBuildItem;
+import io.quarkus.runtime.LaunchMode;
+
+public class DevServicesLambdaProcessor {
+ private static final Logger log = Logger.getLogger(DevServicesLambdaProcessor.class);
+
+ static MockEventServer server;
+ static LaunchMode startMode;
+
+ @BuildStep(onlyIfNot = IsNormal.class)
+ @Record(STATIC_INIT)
+ public void enableHotReplacementChecker(LaunchModeBuildItem launchMode,
+ LambdaHotReplacementRecorder recorder,
+ LambdaObjectMapperInitializedBuildItem dependency) {
+ if (launchMode.getLaunchMode().isDevOrTest()) {
+ if (!legacyTestingEnabled()) {
+ recorder.enable();
+ }
+ }
+ }
+
+ private boolean legacyTestingEnabled() {
+ try {
+ Class.forName("io.quarkus.amazon.lambda.test.LambdaClient");
+ return true;
+ } catch (ClassNotFoundException e) {
+ return false;
+ }
+ }
+
+ @BuildStep(onlyIfNot = IsNormal.class)
+ public void startEventServer(LaunchModeBuildItem launchMode,
+ LambdaConfig config,
+ Optional override,
+ BuildProducer devServicePropertiesProducer,
+ BuildProducer serviceStartBuildItemBuildProducer) throws Exception {
+ if (!launchMode.getLaunchMode().isDevOrTest())
+ return;
+ if (legacyTestingEnabled())
+ return;
+ if (server != null) {
+ return;
+ }
+ Supplier supplier = null;
+ if (override.isPresent()) {
+ supplier = override.get().getServer();
+ } else {
+ supplier = () -> new MockEventServer();
+ }
+
+ server = supplier.get();
+ int port = launchMode.getLaunchMode() == LaunchMode.TEST ? config.mockEventServer.testPort
+ : config.mockEventServer.devPort;
+ startMode = launchMode.getLaunchMode();
+ server.start(port);
+ String baseUrl = "localhost:" + port + MockEventServer.BASE_PATH;
+ System.setProperty(AmazonLambdaApi.QUARKUS_INTERNAL_AWS_LAMBDA_TEST_API, baseUrl);
+ devServicePropertiesProducer.produce(
+ new DevServicesNativeConfigResultBuildItem(AmazonLambdaApi.QUARKUS_INTERNAL_AWS_LAMBDA_TEST_API, baseUrl));
+ Runnable closeTask = () -> {
+ if (server != null) {
+ try {
+ server.close();
+ } catch (Throwable e) {
+ log.error("Failed to stop the Lambda Mock Event Server", e);
+ } finally {
+ server = null;
+ }
+ }
+ startMode = null;
+ server = null;
+ };
+ QuarkusClassLoader cl = (QuarkusClassLoader) Thread.currentThread().getContextClassLoader();
+ ((QuarkusClassLoader) cl.parent()).addCloseTask(closeTask);
+ }
+}
diff --git a/extensions/amazon-lambda/common-deployment/src/main/java/io/quarkus/amazon/lambda/deployment/EventServerOverrideBuildItem.java b/extensions/amazon-lambda/common-deployment/src/main/java/io/quarkus/amazon/lambda/deployment/EventServerOverrideBuildItem.java
new file mode 100644
index 00000000000000..29644cf78b8b02
--- /dev/null
+++ b/extensions/amazon-lambda/common-deployment/src/main/java/io/quarkus/amazon/lambda/deployment/EventServerOverrideBuildItem.java
@@ -0,0 +1,18 @@
+package io.quarkus.amazon.lambda.deployment;
+
+import java.util.function.Supplier;
+
+import io.quarkus.amazon.lambda.runtime.MockEventServer;
+import io.quarkus.builder.item.SimpleBuildItem;
+
+public final class EventServerOverrideBuildItem extends SimpleBuildItem {
+ private Supplier server;
+
+ public EventServerOverrideBuildItem(Supplier server) {
+ this.server = server;
+ }
+
+ public Supplier getServer() {
+ return server;
+ }
+}
diff --git a/extensions/amazon-lambda/common-deployment/src/main/java/io/quarkus/amazon/lambda/deployment/LambdaConfig.java b/extensions/amazon-lambda/common-deployment/src/main/java/io/quarkus/amazon/lambda/deployment/LambdaConfig.java
new file mode 100644
index 00000000000000..e9dcc8e4c3e61f
--- /dev/null
+++ b/extensions/amazon-lambda/common-deployment/src/main/java/io/quarkus/amazon/lambda/deployment/LambdaConfig.java
@@ -0,0 +1,14 @@
+package io.quarkus.amazon.lambda.deployment;
+
+import io.quarkus.runtime.annotations.ConfigPhase;
+import io.quarkus.runtime.annotations.ConfigRoot;
+
+@ConfigRoot(phase = ConfigPhase.BUILD_TIME)
+public class LambdaConfig {
+
+ /**
+ * Configuration for the mock event server that is run
+ * in dev mode and test mode
+ */
+ MockEventServerConfig mockEventServer;
+}
diff --git a/extensions/amazon-lambda/common-deployment/src/main/java/io/quarkus/amazon/lambda/deployment/MockEventServerConfig.java b/extensions/amazon-lambda/common-deployment/src/main/java/io/quarkus/amazon/lambda/deployment/MockEventServerConfig.java
new file mode 100644
index 00000000000000..8722ba40f47f62
--- /dev/null
+++ b/extensions/amazon-lambda/common-deployment/src/main/java/io/quarkus/amazon/lambda/deployment/MockEventServerConfig.java
@@ -0,0 +1,23 @@
+package io.quarkus.amazon.lambda.deployment;
+
+import io.quarkus.runtime.annotations.ConfigGroup;
+import io.quarkus.runtime.annotations.ConfigItem;
+
+/**
+ * Configuration for the mock event server that is run
+ * in dev mode and test mode
+ */
+@ConfigGroup
+public class MockEventServerConfig {
+ /**
+ * Port to access mock event server in dev mode
+ */
+ @ConfigItem(defaultValue = "8080")
+ public int devPort;
+
+ /**
+ * Port to access mock event server in dev mode
+ */
+ @ConfigItem(defaultValue = "8081")
+ public int testPort;
+}
diff --git a/extensions/amazon-lambda/common-runtime/src/main/java/io/quarkus/amazon/lambda/runtime/AbstractLambdaPollLoop.java b/extensions/amazon-lambda/common-runtime/src/main/java/io/quarkus/amazon/lambda/runtime/AbstractLambdaPollLoop.java
index db662c1139144b..1d7052ccc766df 100644
--- a/extensions/amazon-lambda/common-runtime/src/main/java/io/quarkus/amazon/lambda/runtime/AbstractLambdaPollLoop.java
+++ b/extensions/amazon-lambda/common-runtime/src/main/java/io/quarkus/amazon/lambda/runtime/AbstractLambdaPollLoop.java
@@ -33,23 +33,42 @@ public AbstractLambdaPollLoop(ObjectMapper objectMapper, ObjectReader cognitoIdR
protected abstract boolean isStream();
- public void startPollLoop(ShutdownContext context) {
+ protected HttpURLConnection requestConnection = null;
+ public void startPollLoop(ShutdownContext context) {
final AtomicBoolean running = new AtomicBoolean(true);
+ // flag to check whether to interrupt.
+ final AtomicBoolean shouldInterrupt = new AtomicBoolean(true);
+ String baseUrl = AmazonLambdaApi.baseUrl();
final Thread pollingThread = new Thread(new Runnable() {
@SuppressWarnings("unchecked")
@Override
public void run() {
-
try {
- checkQuarkusBootstrapped();
- URL requestUrl = AmazonLambdaApi.invocationNext();
+ if (!LambdaHotReplacementRecorder.enabled) {
+ // when running with continuous testing, this method fails
+ // because currentApplication is not set when running as an
+ // auxiliary application. So, just skip it if hot replacement enabled.
+ // this is only needed in lambda JVM mode anyways to make sure
+ // quarkus has started.
+ checkQuarkusBootstrapped();
+ }
+ URL requestUrl = AmazonLambdaApi.invocationNext(baseUrl);
+ if (AmazonLambdaApi.isTestMode()) {
+ // FYI: This log is required as native test runner
+ // looks for "Listening on" in log to ensure native executable booted
+ log.info("Listening on: " + requestUrl.toString());
+ }
while (running.get()) {
- HttpURLConnection requestConnection = null;
try {
requestConnection = (HttpURLConnection) requestUrl.openConnection();
} catch (IOException e) {
+ if (!running.get()) {
+ // just return gracefully as we were probably shut down by
+ // shutdown task
+ return;
+ }
if (abortGracefully(e)) {
return;
}
@@ -57,10 +76,31 @@ public void run() {
}
try {
String requestId = requestConnection.getHeaderField(AmazonLambdaApi.LAMBDA_RUNTIME_AWS_REQUEST_ID);
+ if (requestConnection.getResponseCode() != 200) {
+ // connection should be closed by finally clause
+ continue;
+ }
try {
+ if (LambdaHotReplacementRecorder.enabled) {
+ try {
+ // do not interrupt during a hot replacement
+ // as shutdown will abort and do nasty things.
+ shouldInterrupt.set(false);
+ if (LambdaHotReplacementRecorder.checkHotReplacement()) {
+ // hot replacement happened in dev mode
+ // so we requeue the request as quarkus will restart
+ // and the message will not be processed
+ // FYI: this requeue endpoint is something only the mock event server implements
+ requeue(baseUrl, requestId);
+ return;
+ }
+ } finally {
+ shouldInterrupt.set(true);
+ }
+ }
String traceId = requestConnection.getHeaderField(AmazonLambdaApi.LAMBDA_TRACE_HEADER_KEY);
TraceId.setTraceId(traceId);
- URL url = AmazonLambdaApi.invocationResponse(requestId);
+ URL url = AmazonLambdaApi.invocationResponse(baseUrl, requestId);
if (isStream()) {
HttpURLConnection responseConnection = responseStream(url);
if (running.get()) {
@@ -87,7 +127,7 @@ public void run() {
}
log.error("Failed to run lambda", e);
- postError(AmazonLambdaApi.invocationError(requestId),
+ postError(AmazonLambdaApi.invocationError(baseUrl, requestId),
new FunctionError(e.getClass().getName(), e.getMessage()));
continue;
}
@@ -111,25 +151,34 @@ public void run() {
} catch (Exception e) {
try {
log.error("Lambda init error", e);
- postError(AmazonLambdaApi.initError(), new FunctionError(e.getClass().getName(), e.getMessage()));
+ postError(AmazonLambdaApi.initError(baseUrl),
+ new FunctionError(e.getClass().getName(), e.getMessage()));
} 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) {
+ log.error("Shutting down Quarkus application because of error");
app.stop();
}
}
+ } finally {
+ log.info("Lambda polling thread complete");
}
}
}, "Lambda Thread");
pollingThread.setDaemon(true);
context.addShutdownTask(() -> {
running.set(false);
- //note that this does not seem to be 100% reliable in unblocking the thread
- //which is why it is a daemon.
- pollingThread.interrupt();
+ try {
+ //note that interrupting does not seem to be 100% reliable in unblocking the thread
+ requestConnection.disconnect();
+ } catch (Exception ignore) {
+ }
+ if (shouldInterrupt.get()) {
+ pollingThread.interrupt();
+ }
});
pollingThread.start();
@@ -167,10 +216,24 @@ private void checkQuarkusBootstrapped() {
protected void postResponse(URL url, Object response) throws IOException {
HttpURLConnection responseConnection = (HttpURLConnection) url.openConnection();
+ if (response != null) {
+ getOutputWriter().writeHeaders(responseConnection);
+ }
responseConnection.setDoOutput(true);
responseConnection.setRequestMethod("POST");
- if (response != null)
+ if (response != null) {
getOutputWriter().writeValue(responseConnection.getOutputStream(), response);
+ }
+ while (responseConnection.getInputStream().read() != -1) {
+ // Read data
+ }
+ }
+
+ protected void requeue(String baseUrl, String requestId) throws IOException {
+ URL url = AmazonLambdaApi.requeue(baseUrl, requestId);
+ HttpURLConnection responseConnection = (HttpURLConnection) url.openConnection();
+ responseConnection.setDoOutput(true);
+ responseConnection.setRequestMethod("POST");
while (responseConnection.getInputStream().read() != -1) {
// Read data
}
@@ -178,6 +241,7 @@ protected void postResponse(URL url, Object response) throws IOException {
protected void postError(URL url, Object response) throws IOException {
HttpURLConnection responseConnection = (HttpURLConnection) url.openConnection();
+ responseConnection.setRequestProperty("Content-Type", "application/json");
responseConnection.setDoOutput(true);
responseConnection.setRequestMethod("POST");
objectMapper.writeValue(responseConnection.getOutputStream(), response);
@@ -202,7 +266,7 @@ boolean abortGracefully(Exception ex) {
|| (ex instanceof UnknownHostException && !lambdaEnv);
if (graceful)
- log.warn("Aborting lambda poll loop: " + (!lambdaEnv ? "no lambda container found" : "test mode"));
+ log.warn("Aborting lambda poll loop: " + (lambdaEnv ? "no lambda container found" : "ending dev/test mode"));
return graceful;
}
diff --git a/extensions/amazon-lambda/common-runtime/src/main/java/io/quarkus/amazon/lambda/runtime/AmazonLambdaApi.java b/extensions/amazon-lambda/common-runtime/src/main/java/io/quarkus/amazon/lambda/runtime/AmazonLambdaApi.java
index a768c158bc3899..ec1e528ab38753 100644
--- a/extensions/amazon-lambda/common-runtime/src/main/java/io/quarkus/amazon/lambda/runtime/AmazonLambdaApi.java
+++ b/extensions/amazon-lambda/common-runtime/src/main/java/io/quarkus/amazon/lambda/runtime/AmazonLambdaApi.java
@@ -28,20 +28,35 @@ public class AmazonLambdaApi {
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);
+ // Only available in dev/test mode and points to path for mock even tserver
+ public static final String API_BASE_PATH_TEST = "/_lambda_";
+ public static final String POST_EVENT = API_BASE_PATH_TEST;
+
+ // this is quarkus specific endpoint for dev mode
+ public static final String API_PATH_REQUEUE = "/requeue";
+
+ static String baseUrl() {
+ return API_PROTOCOL + runtimeApi();
+ }
+
+ static URL invocationNext(String baseUrl) throws MalformedURLException {
+ return new URL(baseUrl + 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 invocationError(String baseUrl, String requestId) throws MalformedURLException {
+ return new URL(baseUrl + 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 invocationResponse(String baseUrl, String requestId) throws MalformedURLException {
+ return new URL(baseUrl + API_PATH_INVOCATION + requestId + API_PATH_RESPONSE);
}
- static URL initError() throws MalformedURLException {
- return new URL(API_PROTOCOL + runtimeApi() + API_PATH_INIT_ERROR);
+ static URL requeue(String baseUrl, String requestId) throws MalformedURLException {
+ return new URL(baseUrl + API_PATH_INVOCATION + requestId + API_PATH_REQUEUE);
+ }
+
+ static URL initError(String baseUrl) throws MalformedURLException {
+ return new URL(baseUrl + API_PATH_INIT_ERROR);
}
static String logGroupName() {
@@ -64,6 +79,10 @@ static String functionVersion() {
return System.getenv("AWS_LAMBDA_FUNCTION_VERSION");
}
+ public static boolean isTestMode() {
+ return System.getProperty(AmazonLambdaApi.QUARKUS_INTERNAL_AWS_LAMBDA_TEST_API) != null;
+ }
+
private static String runtimeApi() {
String testApi = System.getProperty(QUARKUS_INTERNAL_AWS_LAMBDA_TEST_API);
if (testApi != null) {
diff --git a/extensions/amazon-lambda/common-runtime/src/main/java/io/quarkus/amazon/lambda/runtime/JacksonOutputWriter.java b/extensions/amazon-lambda/common-runtime/src/main/java/io/quarkus/amazon/lambda/runtime/JacksonOutputWriter.java
index d950a57f741cf5..137e8c75461059 100644
--- a/extensions/amazon-lambda/common-runtime/src/main/java/io/quarkus/amazon/lambda/runtime/JacksonOutputWriter.java
+++ b/extensions/amazon-lambda/common-runtime/src/main/java/io/quarkus/amazon/lambda/runtime/JacksonOutputWriter.java
@@ -2,6 +2,7 @@
import java.io.IOException;
import java.io.OutputStream;
+import java.net.HttpURLConnection;
import com.fasterxml.jackson.databind.ObjectWriter;
@@ -16,4 +17,10 @@ public JacksonOutputWriter(ObjectWriter writer) {
public void writeValue(OutputStream os, Object obj) throws IOException {
writer.writeValue(os, obj);
}
+
+ @Override
+ public void writeHeaders(HttpURLConnection conn) {
+ conn.setRequestProperty("Content-Type", "application/json");
+ }
+
}
diff --git a/extensions/amazon-lambda/common-runtime/src/main/java/io/quarkus/amazon/lambda/runtime/LambdaBuildTimeConfig.java b/extensions/amazon-lambda/common-runtime/src/main/java/io/quarkus/amazon/lambda/runtime/LambdaBuildTimeConfig.java
deleted file mode 100644
index 3af310748aa107..00000000000000
--- a/extensions/amazon-lambda/common-runtime/src/main/java/io/quarkus/amazon/lambda/runtime/LambdaBuildTimeConfig.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package io.quarkus.amazon.lambda.runtime;
-
-import io.quarkus.runtime.annotations.ConfigItem;
-import io.quarkus.runtime.annotations.ConfigPhase;
-import io.quarkus.runtime.annotations.ConfigRoot;
-
-/**
- *
- */
-@ConfigRoot(phase = ConfigPhase.BUILD_TIME)
-public class LambdaBuildTimeConfig {
-
- /**
- * If true, this will enable the aws event poll loop within a Quarkus test run. This loop normally only runs in native
- * image. This option is strictly for testing purposes.
- *
- */
- @ConfigItem
- public boolean enablePollingJvmMode;
-}
diff --git a/extensions/amazon-lambda/common-runtime/src/main/java/io/quarkus/amazon/lambda/runtime/LambdaHotReplacementRecorder.java b/extensions/amazon-lambda/common-runtime/src/main/java/io/quarkus/amazon/lambda/runtime/LambdaHotReplacementRecorder.java
new file mode 100644
index 00000000000000..8e187d637adad4
--- /dev/null
+++ b/extensions/amazon-lambda/common-runtime/src/main/java/io/quarkus/amazon/lambda/runtime/LambdaHotReplacementRecorder.java
@@ -0,0 +1,49 @@
+package io.quarkus.amazon.lambda.runtime;
+
+import org.jboss.logging.Logger;
+
+import io.quarkus.dev.console.DevConsoleManager;
+import io.quarkus.dev.spi.HotReplacementContext;
+import io.quarkus.runtime.annotations.Recorder;
+
+@Recorder
+public class LambdaHotReplacementRecorder {
+ private static final Logger log = Logger.getLogger(LambdaHotReplacementRecorder.class);
+
+ static volatile long nextUpdate;
+ public static volatile boolean enabled;
+ private static final long HOT_REPLACEMENT_INTERVAL = 2000;
+
+ static Object syncLock = new Object();
+
+ public static boolean checkHotReplacement() throws Exception {
+ if (!enabled)
+ return false;
+ HotReplacementContext hotReplacementContext = DevConsoleManager.getHotReplacementContext();
+ if (hotReplacementContext == null)
+ return false;
+
+ if ((nextUpdate > System.currentTimeMillis() && !hotReplacementContext.isTest())) {
+ if (hotReplacementContext.getDeploymentProblem() != null) {
+ throw new Exception("Hot Replacement Deployment issue", hotReplacementContext.getDeploymentProblem());
+ }
+ return false;
+ }
+ synchronized (syncLock) {
+ if (nextUpdate < System.currentTimeMillis() || hotReplacementContext.isTest()) {
+ nextUpdate = System.currentTimeMillis() + HOT_REPLACEMENT_INTERVAL;
+ try {
+ boolean restart = hotReplacementContext.doScan(true);
+ return restart;
+ } catch (Exception e) {
+ throw new IllegalStateException("Unable to perform live reload scanning", e);
+ }
+ }
+ }
+ return false;
+ }
+
+ public void enable() {
+ enabled = true;
+ }
+}
diff --git a/extensions/amazon-lambda/common-runtime/src/main/java/io/quarkus/amazon/lambda/runtime/LambdaOutputWriter.java b/extensions/amazon-lambda/common-runtime/src/main/java/io/quarkus/amazon/lambda/runtime/LambdaOutputWriter.java
index d9de5be4e9ff08..a0895ab5302277 100644
--- a/extensions/amazon-lambda/common-runtime/src/main/java/io/quarkus/amazon/lambda/runtime/LambdaOutputWriter.java
+++ b/extensions/amazon-lambda/common-runtime/src/main/java/io/quarkus/amazon/lambda/runtime/LambdaOutputWriter.java
@@ -2,7 +2,11 @@
import java.io.IOException;
import java.io.OutputStream;
+import java.net.HttpURLConnection;
public interface LambdaOutputWriter {
void writeValue(OutputStream os, Object obj) throws IOException;
+
+ default void writeHeaders(HttpURLConnection conn) {
+ }
}
diff --git a/extensions/amazon-lambda/deployment/pom.xml b/extensions/amazon-lambda/deployment/pom.xml
index 6748ee9112fa6e..8e5df28f1b6d4b 100644
--- a/extensions/amazon-lambda/deployment/pom.xml
+++ b/extensions/amazon-lambda/deployment/pom.xml
@@ -35,6 +35,11 @@
quarkus-junit5-internaltest
+
+ io.quarkus
+ quarkus-junit5
+ test
+
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 128109ea225234..b755f0aae7f7b2 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
@@ -24,7 +24,6 @@
import io.quarkus.amazon.lambda.runtime.AmazonLambdaRecorder;
import io.quarkus.amazon.lambda.runtime.FunctionError;
-import io.quarkus.amazon.lambda.runtime.LambdaBuildTimeConfig;
import io.quarkus.amazon.lambda.runtime.LambdaConfig;
import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.arc.deployment.BeanContainerBuildItem;
@@ -248,13 +247,12 @@ void startPoolLoop(AmazonLambdaRecorder recorder,
@BuildStep
@Record(value = ExecutionTime.RUNTIME_INIT)
- void enableNativeEventLoop(LambdaBuildTimeConfig config,
- AmazonLambdaRecorder recorder,
+ void startPoolLoopDevOrTest(AmazonLambdaRecorder recorder,
List orderServicesFirst, // force some ordering of recorders
ShutdownContextBuildItem shutdownContextBuildItem,
LaunchModeBuildItem launchModeBuildItem) {
LaunchMode mode = launchModeBuildItem.getLaunchMode();
- if (config.enablePollingJvmMode && mode.isDevOrTest()) {
+ if (mode.isDevOrTest()) {
recorder.startPollLoop(shutdownContextBuildItem);
}
}
diff --git a/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/GreetingLambda.java b/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/GreetingLambda.java
new file mode 100644
index 00000000000000..f62a7ef7eef955
--- /dev/null
+++ b/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/GreetingLambda.java
@@ -0,0 +1,12 @@
+package io.quarkus.amazon.lambda.deployment.testing;
+
+import com.amazonaws.services.lambda.runtime.Context;
+import com.amazonaws.services.lambda.runtime.RequestHandler;
+
+public class GreetingLambda implements RequestHandler {
+
+ @Override
+ public String handleRequest(Person input, Context context) {
+ return "Hey " + input.getName();
+ }
+}
diff --git a/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/LambdaDevServicesContinuousTestingTestCase.java b/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/LambdaDevServicesContinuousTestingTestCase.java
new file mode 100644
index 00000000000000..1565016006b699
--- /dev/null
+++ b/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/LambdaDevServicesContinuousTestingTestCase.java
@@ -0,0 +1,49 @@
+package io.quarkus.amazon.lambda.deployment.testing;
+
+import java.util.function.Supplier;
+
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.asset.StringAsset;
+import org.jboss.shrinkwrap.api.spec.JavaArchive;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.test.ContinuousTestingTestUtils;
+import io.quarkus.test.QuarkusDevModeTest;
+
+public class LambdaDevServicesContinuousTestingTestCase {
+ @RegisterExtension
+ public static QuarkusDevModeTest test = new QuarkusDevModeTest()
+ .setArchiveProducer(new Supplier() {
+ @Override
+ public JavaArchive get() {
+ return ShrinkWrap.create(JavaArchive.class)
+ .addClasses(GreetingLambda.class, Person.class)
+ .addAsResource(new StringAsset(ContinuousTestingTestUtils.appProperties("")),
+ "application.properties");
+ }
+ }).setTestArchiveProducer(new Supplier() {
+ @Override
+ public JavaArchive get() {
+ return ShrinkWrap.create(JavaArchive.class).addClass(LambdaHandlerET.class);
+ }
+ });
+
+ @Test
+ public void testLambda() throws Exception {
+ ContinuousTestingTestUtils utils = new ContinuousTestingTestUtils();
+ var result = utils.waitForNextCompletion();
+ Assertions.assertEquals(1, result.getTotalTestsPassed());
+ Assertions.assertEquals(0, result.getTotalTestsFailed());
+ test.modifySourceFile(GreetingLambda.class, s -> s.replace("Hey", "Yo"));
+ result = utils.waitForNextCompletion();
+ Assertions.assertEquals(0, result.getTotalTestsPassed());
+ Assertions.assertEquals(1, result.getTotalTestsFailed());
+ test.modifyTestSourceFile(LambdaHandlerET.class, s -> s.replace("Hey", "Yo"));
+ result = utils.waitForNextCompletion();
+ Assertions.assertEquals(1, result.getTotalTestsPassed());
+ Assertions.assertEquals(0, result.getTotalTestsFailed());
+
+ }
+}
diff --git a/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/LambdaHandlerET.java b/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/LambdaHandlerET.java
new file mode 100644
index 00000000000000..e37e209b3c221f
--- /dev/null
+++ b/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/LambdaHandlerET.java
@@ -0,0 +1,31 @@
+package io.quarkus.amazon.lambda.deployment.testing;
+
+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
+class LambdaHandlerET {
+
+ @Test
+ public void testSimpleLambdaSuccess() throws Exception {
+ // you test your lambas by invoking on http://localhost:8081
+ // this works in dev mode too
+
+ Person in = new Person();
+ in.setName("Stu");
+ given()
+ .contentType("application/json")
+ .accept("application/json")
+ .body(in)
+ .when()
+ .post()
+ .then()
+ .statusCode(200)
+ .body(containsString("Hey Stu"));
+ }
+
+}
diff --git a/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/Person.java b/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/Person.java
new file mode 100644
index 00000000000000..d2a4066a77dc29
--- /dev/null
+++ b/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/testing/Person.java
@@ -0,0 +1,15 @@
+package io.quarkus.amazon.lambda.deployment.testing;
+
+public class Person {
+
+ private String name;
+
+ public String getName() {
+ return name;
+ }
+
+ public Person setName(String name) {
+ this.name = name;
+ return this;
+ }
+}
diff --git a/extensions/amazon-lambda/event-server/pom.xml b/extensions/amazon-lambda/event-server/pom.xml
new file mode 100644
index 00000000000000..9c2b5daf911e1b
--- /dev/null
+++ b/extensions/amazon-lambda/event-server/pom.xml
@@ -0,0 +1,70 @@
+
+
+ 4.0.0
+
+
+ io.quarkus
+ quarkus-amazon-lambda-parent
+ 999-SNAPSHOT
+ ../pom.xml
+
+
+ quarkus-amazon-lambda-event-server
+ Quarkus - Amazon Lambda Event Server
+ Mock Lambda event server for testing and dev mode
+
+
+
+ io.vertx
+ vertx-web
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
+
+
+ io.quarkus
+ quarkus-amazon-lambda-common
+
+
+ org.junit.jupiter
+ junit-jupiter
+ test
+
+
+ org.jboss.resteasy
+ resteasy-client
+ test
+
+
+ commons-logging
+ commons-logging
+
+
+ jakarta.activation
+ jakarta.activation-api
+
+
+
+
+
+ io.quarkus
+ quarkus-apache-httpclient
+ test
+
+
+ io.rest-assured
+ rest-assured
+ test
+
+
+
+
+
+
+
+
diff --git a/extensions/amazon-lambda/event-server/src/main/java/io/quarkus/amazon/lambda/runtime/MockEventServer.java b/extensions/amazon-lambda/event-server/src/main/java/io/quarkus/amazon/lambda/runtime/MockEventServer.java
new file mode 100644
index 00000000000000..cea6dfd37e2315
--- /dev/null
+++ b/extensions/amazon-lambda/event-server/src/main/java/io/quarkus/amazon/lambda/runtime/MockEventServer.java
@@ -0,0 +1,240 @@
+package io.quarkus.amazon.lambda.runtime;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.UUID;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.jboss.logging.Logger;
+
+import io.vertx.core.Vertx;
+import io.vertx.core.buffer.Buffer;
+import io.vertx.core.http.HttpServer;
+import io.vertx.ext.web.Router;
+import io.vertx.ext.web.RoutingContext;
+import io.vertx.ext.web.handler.BodyHandler;
+
+public class MockEventServer implements Closeable {
+ protected static final Logger log = Logger.getLogger(MockEventServer.class);
+ public static final int DEFAULT_PORT = 8081;
+
+ public void start() {
+ int port = DEFAULT_PORT;
+ start(port);
+ }
+
+ public void start(int port) {
+ this.port = port;
+ vertx = Vertx.vertx();
+ httpServer = vertx.createHttpServer();
+ router = Router.router(vertx);
+ setupRoutes();
+ httpServer.requestHandler(router).listen(port).result();
+ log.info("Mock Lambda Event Server Started");
+ }
+
+ private Vertx vertx;
+ private int port;
+ protected HttpServer httpServer;
+ protected Router router;
+ protected BlockingQueue queue;
+ protected ConcurrentHashMap responsePending = new ConcurrentHashMap<>();
+ protected ExecutorService blockingPool = Executors.newCachedThreadPool();
+ public static final String BASE_PATH = AmazonLambdaApi.API_BASE_PATH_TEST;
+ public static final String INVOCATION = BASE_PATH + AmazonLambdaApi.API_PATH_INVOCATION;
+ public static final String NEXT_INVOCATION = BASE_PATH + AmazonLambdaApi.API_PATH_INVOCATION_NEXT;
+ public static final String POST_EVENT = BASE_PATH;
+
+ public MockEventServer() {
+ queue = new LinkedBlockingQueue<>();
+ }
+
+ public HttpServer getHttpServer() {
+ return httpServer;
+ }
+
+ public void setupRoutes() {
+ router.route().handler(BodyHandler.create());
+ router.post(POST_EVENT).handler(this::postEvent);
+ router.route(NEXT_INVOCATION).blockingHandler(this::nextEvent);
+ router.route(INVOCATION + ":requestId" + AmazonLambdaApi.API_PATH_REQUEUE).handler(this::handleRequeue);
+ router.route(INVOCATION + ":requestId" + AmazonLambdaApi.API_PATH_RESPONSE).handler(this::handleResponse);
+ router.route(INVOCATION + ":requestId" + AmazonLambdaApi.API_PATH_ERROR).handler(this::handleError);
+ defaultHanderSetup();
+ }
+
+ protected void defaultHanderSetup() {
+ router.post().handler(this::postEvent);
+ }
+
+ public void postEvent(RoutingContext ctx) {
+ String requestId = ctx.request().getHeader(AmazonLambdaApi.LAMBDA_RUNTIME_AWS_REQUEST_ID);
+ if (requestId == null) {
+ requestId = UUID.randomUUID().toString();
+ }
+ ctx.put(AmazonLambdaApi.LAMBDA_RUNTIME_AWS_REQUEST_ID, requestId);
+ String traceId = ctx.request().getHeader(AmazonLambdaApi.LAMBDA_RUNTIME_AWS_REQUEST_ID);
+ if (traceId == null) {
+ traceId = UUID.randomUUID().toString();
+ }
+ ctx.put(AmazonLambdaApi.LAMBDA_TRACE_HEADER_KEY, traceId);
+ try {
+ queue.put(ctx);
+ } catch (InterruptedException e) {
+ log.error("Publish interrupted");
+ ctx.fail(500);
+ }
+ }
+
+ private RoutingContext pollNextEvent() throws InterruptedException {
+ for (;;) {
+ RoutingContext request = queue.poll(10, TimeUnit.MILLISECONDS);
+ if (request != null)
+ return request;
+
+ }
+ }
+
+ public void nextEvent(RoutingContext ctx) {
+ // Vert.x barfs if you block too long so we have our own executor
+ blockingPool.execute(() -> {
+ final AtomicBoolean closed = new AtomicBoolean(false);
+ ctx.response().closeHandler((v) -> closed.set(true));
+ RoutingContext request = null;
+ try {
+ for (;;) {
+ request = queue.poll(10, TimeUnit.MILLISECONDS);
+ if (request != null) {
+ if (closed.get()) {
+ queue.put(request);
+ return;
+ } else {
+ break;
+ }
+ } else if (closed.get()) {
+ return;
+ }
+ }
+ } catch (InterruptedException e) {
+ log.error("nextEvent interrupted");
+ ctx.fail(500);
+ }
+
+ String contentType = getEventContentType(request);
+ if (contentType != null) {
+ ctx.response().putHeader("content-type", contentType);
+ }
+ String traceId = request.get(AmazonLambdaApi.LAMBDA_TRACE_HEADER_KEY);
+ if (traceId != null) {
+ ctx.response().putHeader(AmazonLambdaApi.LAMBDA_TRACE_HEADER_KEY, traceId);
+ }
+ String requestId = request.get(AmazonLambdaApi.LAMBDA_RUNTIME_AWS_REQUEST_ID);
+ responsePending.put(requestId, request);
+ ctx.response().putHeader(AmazonLambdaApi.LAMBDA_RUNTIME_AWS_REQUEST_ID, requestId);
+ Buffer body = processEventBody(request);
+ if (body != null) {
+ ctx.response().setStatusCode(200).end(body);
+ } else {
+ ctx.response().setStatusCode(200).end();
+ }
+ });
+ }
+
+ protected String getEventContentType(RoutingContext request) {
+ return request.request().getHeader("content-type");
+ }
+
+ protected Buffer processEventBody(RoutingContext request) {
+ return request.getBody();
+ }
+
+ public void handleResponse(RoutingContext ctx) {
+ String requestId = ctx.pathParam("requestId");
+ RoutingContext pending = responsePending.remove(requestId);
+ if (pending == null) {
+ log.error("Unknown lambda request: " + requestId);
+ ctx.fail(404);
+ return;
+ }
+ Buffer buffer = ctx.getBody();
+ processResponse(ctx, pending, buffer);
+ ctx.response().setStatusCode(204);
+ ctx.end();
+ }
+
+ public void handleRequeue(RoutingContext ctx) {
+ String requestId = ctx.pathParam("requestId");
+ RoutingContext pending = responsePending.remove(requestId);
+ if (pending == null) {
+ log.error("Unknown lambda request: " + requestId);
+ ctx.fail(404);
+ return;
+ }
+ try {
+ queue.put(pending);
+ } catch (InterruptedException e) {
+ log.error("Publish interrupted");
+ ctx.fail(500);
+ }
+ ctx.response().setStatusCode(204);
+ ctx.end();
+ }
+
+ public void processResponse(RoutingContext ctx, RoutingContext pending, Buffer buffer) {
+ if (buffer != null) {
+ if (ctx.request().getHeader("Content-Type") != null) {
+ pending.response().putHeader("Content-Type", ctx.request().getHeader("Content-Type"));
+ }
+ pending.response()
+ .setStatusCode(200)
+ .end(buffer);
+ } else {
+ pending.response()
+ .setStatusCode(204)
+ .end();
+ }
+ }
+
+ public void handleError(RoutingContext ctx) {
+ String requestId = ctx.pathParam("requestId");
+ RoutingContext pending = responsePending.remove(requestId);
+ if (pending == null) {
+ log.error("Unknown lambda request: " + requestId);
+ ctx.fail(404);
+ return;
+ }
+ Buffer buffer = ctx.getBody();
+ processError(ctx, pending, buffer);
+ ctx.response().setStatusCode(204);
+ ctx.end();
+ }
+
+ public void processError(RoutingContext ctx, RoutingContext pending, Buffer buffer) {
+ if (buffer != null) {
+ if (ctx.request().getHeader("Content-Type") != null) {
+ pending.response().putHeader("Content-Type", ctx.request().getHeader("Content-Type"));
+ }
+ pending.response()
+ .setStatusCode(500)
+ .end(buffer);
+ } else {
+ pending.response()
+ .setStatusCode(500)
+ .end();
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ log.info("Stopping Mock Lambda Event Server");
+ httpServer.close().result();
+ vertx.close().result();
+ blockingPool.shutdown();
+ }
+}
diff --git a/extensions/amazon-lambda/event-server/src/test/java/io/quarkus/amazon/lambda/runtime/EventServerTest.java b/extensions/amazon-lambda/event-server/src/test/java/io/quarkus/amazon/lambda/runtime/EventServerTest.java
new file mode 100644
index 00000000000000..d6f98929b281d9
--- /dev/null
+++ b/extensions/amazon-lambda/event-server/src/test/java/io/quarkus/amazon/lambda/runtime/EventServerTest.java
@@ -0,0 +1,66 @@
+package io.quarkus.amazon.lambda.runtime;
+
+import java.util.concurrent.Future;
+
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Response;
+
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+public class EventServerTest {
+
+ static MockEventServer server;
+
+ @BeforeAll
+ public static void start() {
+ server = new MockEventServer();
+ server.start();
+ }
+
+ @AfterAll
+ public static void end() throws Exception {
+ server.close();
+ }
+
+ @Test
+ public void testServer() throws Exception {
+ Client client = ClientBuilder.newBuilder().build();
+ WebTarget base = client.target("http://localhost:" + MockEventServer.DEFAULT_PORT);
+ WebTarget postEvent = base.path(MockEventServer.POST_EVENT);
+ // test posting event works at '/_lambda'
+ invokeTest(base, postEvent);
+ // make sure posting event works on '/'
+ invokeTest(base, base);
+ }
+
+ private void invokeTest(WebTarget base, WebTarget postEvent)
+ throws InterruptedException, java.util.concurrent.ExecutionException {
+ Future lambdaInvoke = postEvent.request().async().post(Entity.json("\"hello\""));
+
+ Response next = base.path(MockEventServer.NEXT_INVOCATION).request().get();
+ Assertions.assertEquals(200, next.getStatus());
+ String requestId = next.getHeaderString(AmazonLambdaApi.LAMBDA_RUNTIME_AWS_REQUEST_ID);
+ String traceId = next.getHeaderString(AmazonLambdaApi.LAMBDA_TRACE_HEADER_KEY);
+ Assertions.assertNotNull(requestId);
+ Assertions.assertNotNull(traceId);
+ String json = next.readEntity(String.class);
+ Assertions.assertEquals("\"hello\"", json);
+ next.close();
+
+ Response sendResponse = base.path(MockEventServer.INVOCATION).path(requestId).path("response")
+ .request().post(Entity.json("\"good day\""));
+ Assertions.assertEquals(204, sendResponse.getStatus());
+ sendResponse.close();
+
+ Response lambdaResponse = lambdaInvoke.get();
+ Assertions.assertEquals(200, lambdaResponse.getStatus());
+ Assertions.assertEquals("\"good day\"", lambdaResponse.readEntity(String.class));
+ lambdaResponse.close();
+ }
+}
diff --git a/extensions/amazon-lambda/maven-archetype/src/main/resources/archetype-resources/build.gradle b/extensions/amazon-lambda/maven-archetype/src/main/resources/archetype-resources/build.gradle
index a003f1174cfd5a..0609366f7346e9 100644
--- a/extensions/amazon-lambda/maven-archetype/src/main/resources/archetype-resources/build.gradle
+++ b/extensions/amazon-lambda/maven-archetype/src/main/resources/archetype-resources/build.gradle
@@ -10,10 +10,8 @@ repositories {
dependencies {
implementation enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}")
- implementation 'io.quarkus:quarkus-resteasy'
implementation 'io.quarkus:quarkus-amazon-lambda'
- testImplementation "io.quarkus:quarkus-test-amazon-lambda"
testImplementation 'io.quarkus:quarkus-junit5'
testImplementation 'io.rest-assured:rest-assured'
}
diff --git a/extensions/amazon-lambda/maven-archetype/src/main/resources/archetype-resources/pom.xml b/extensions/amazon-lambda/maven-archetype/src/main/resources/archetype-resources/pom.xml
index b617576bb157ab..732ec60ca31e72 100644
--- a/extensions/amazon-lambda/maven-archetype/src/main/resources/archetype-resources/pom.xml
+++ b/extensions/amazon-lambda/maven-archetype/src/main/resources/archetype-resources/pom.xml
@@ -36,8 +36,8 @@
quarkus-amazon-lambda
- io.quarkus
- quarkus-test-amazon-lambda
+ io.rest-assured
+ rest-assuredtest
diff --git a/extensions/amazon-lambda/maven-archetype/src/main/resources/archetype-resources/src/test/java/LambdaHandlerTest.java b/extensions/amazon-lambda/maven-archetype/src/main/resources/archetype-resources/src/test/java/LambdaHandlerTest.java
index 4f89d941f2b53b..69dc8f5452ba7c 100644
--- a/extensions/amazon-lambda/maven-archetype/src/main/resources/archetype-resources/src/test/java/LambdaHandlerTest.java
+++ b/extensions/amazon-lambda/maven-archetype/src/main/resources/archetype-resources/src/test/java/LambdaHandlerTest.java
@@ -1,22 +1,31 @@
package ${package};
-import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
-import io.quarkus.amazon.lambda.test.LambdaClient;
import io.quarkus.test.junit.QuarkusTest;
+import static io.restassured.RestAssured.given;
+import static org.hamcrest.CoreMatchers.containsString;
+
@QuarkusTest
public class LambdaHandlerTest {
@Test
public void testSimpleLambdaSuccess() throws Exception {
- InputObject in = new InputObject();
- in.setGreeting("Hello");
+ // you test your lambas by invoking on http://localhost:8081
+ // this works in dev mode too
+
+ Person in = new Person();
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-'");
+ given()
+ .contentType("application/json")
+ .accept("application/json")
+ .body(in)
+ .when()
+ .post()
+ .then()
+ .statusCode(200)
+ .body(containsString("Hello Stu"));
}
}
diff --git a/extensions/amazon-lambda/maven-archetype/src/main/resources/archetype-resources/src/test/resources/application.properties b/extensions/amazon-lambda/maven-archetype/src/main/resources/archetype-resources/src/test/resources/application.properties
index 7c37ee7b060a83..6be7ba734f7956 100644
--- a/extensions/amazon-lambda/maven-archetype/src/main/resources/archetype-resources/src/test/resources/application.properties
+++ b/extensions/amazon-lambda/maven-archetype/src/main/resources/archetype-resources/src/test/resources/application.properties
@@ -1,5 +1,2 @@
quarkus.lambda.handler=test
-quarkus.lambda.enable-polling-jvm-mode=true
-
-
diff --git a/extensions/amazon-lambda/pom.xml b/extensions/amazon-lambda/pom.xml
index 71ccac1880aee3..ee3640b9fbf348 100644
--- a/extensions/amazon-lambda/pom.xml
+++ b/extensions/amazon-lambda/pom.xml
@@ -17,6 +17,7 @@
common-runtime
+ event-servercommon-deploymentruntimedeployment
diff --git a/extensions/amazon-lambda/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/amazon-lambda/runtime/src/main/resources/META-INF/quarkus-extension.yaml
index c19be1cae40cb5..495288d1d6b539 100644
--- a/extensions/amazon-lambda/runtime/src/main/resources/META-INF/quarkus-extension.yaml
+++ b/extensions/amazon-lambda/runtime/src/main/resources/META-INF/quarkus-extension.yaml
@@ -8,7 +8,7 @@ metadata:
categories:
- "cloud"
guide: "https://quarkus.io/guides/amazon-lambda"
- status: "preview"
+ status: "stable"
codestart:
name: "amazon-lambda"
kind: "example"
diff --git a/extensions/amazon-services/common/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/amazon-services/common/runtime/src/main/resources/META-INF/quarkus-extension.yaml
index 4ea5a1d78f630b..86c58a06796c58 100644
--- a/extensions/amazon-services/common/runtime/src/main/resources/META-INF/quarkus-extension.yaml
+++ b/extensions/amazon-services/common/runtime/src/main/resources/META-INF/quarkus-extension.yaml
@@ -8,5 +8,5 @@ metadata:
- "amazon"
categories:
- "data"
- status: "preview"
+ status: "stable"
unlisted: true
\ No newline at end of file
diff --git a/extensions/amazon-services/dynamodb/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/amazon-services/dynamodb/runtime/src/main/resources/META-INF/quarkus-extension.yaml
index ace051cb576d94..597bec9e4fb7bc 100644
--- a/extensions/amazon-services/dynamodb/runtime/src/main/resources/META-INF/quarkus-extension.yaml
+++ b/extensions/amazon-services/dynamodb/runtime/src/main/resources/META-INF/quarkus-extension.yaml
@@ -10,6 +10,6 @@ metadata:
guide: "https://quarkus.io/guides/amazon-dynamodb"
categories:
- "data"
- status: "preview"
+ status: "stable"
config:
- "quarkus.dynamodb."
diff --git a/extensions/amazon-services/iam/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/amazon-services/iam/runtime/src/main/resources/META-INF/quarkus-extension.yaml
index 5eb16f520dd059..0046f605a490ea 100644
--- a/extensions/amazon-services/iam/runtime/src/main/resources/META-INF/quarkus-extension.yaml
+++ b/extensions/amazon-services/iam/runtime/src/main/resources/META-INF/quarkus-extension.yaml
@@ -9,6 +9,6 @@ metadata:
guide: "https://quarkus.io/guides/amazon-iam"
categories:
- "data"
- status: "preview"
+ status: "stable"
config:
- "quarkus.iam."
diff --git a/extensions/amazon-services/kms/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/amazon-services/kms/runtime/src/main/resources/META-INF/quarkus-extension.yaml
index c7cb65aa89ff8a..e6bc35fc57f554 100644
--- a/extensions/amazon-services/kms/runtime/src/main/resources/META-INF/quarkus-extension.yaml
+++ b/extensions/amazon-services/kms/runtime/src/main/resources/META-INF/quarkus-extension.yaml
@@ -9,6 +9,6 @@ metadata:
guide: "https://quarkus.io/guides/amazon-kms"
categories:
- "data"
- status: "preview"
+ status: "stable"
config:
- "quarkus.kms."
diff --git a/extensions/amazon-services/s3/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/amazon-services/s3/runtime/src/main/resources/META-INF/quarkus-extension.yaml
index ed8bc6d64adc54..cfff54043b1190 100644
--- a/extensions/amazon-services/s3/runtime/src/main/resources/META-INF/quarkus-extension.yaml
+++ b/extensions/amazon-services/s3/runtime/src/main/resources/META-INF/quarkus-extension.yaml
@@ -9,6 +9,6 @@ metadata:
guide: "https://quarkus.io/guides/amazon-s3"
categories:
- "data"
- status: "preview"
+ status: "stable"
config:
- "quarkus.s3."
diff --git a/extensions/amazon-services/ses/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/amazon-services/ses/runtime/src/main/resources/META-INF/quarkus-extension.yaml
index 7133851af1215a..7b8c825793fe74 100644
--- a/extensions/amazon-services/ses/runtime/src/main/resources/META-INF/quarkus-extension.yaml
+++ b/extensions/amazon-services/ses/runtime/src/main/resources/META-INF/quarkus-extension.yaml
@@ -9,6 +9,6 @@ metadata:
guide: "https://quarkus.io/guides/amazon-ses"
categories:
- "data"
- status: "preview"
+ status: "stable"
config:
- "quarkus.ses."
diff --git a/extensions/amazon-services/sns/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/amazon-services/sns/runtime/src/main/resources/META-INF/quarkus-extension.yaml
index 87cab1d1233cfe..5df4b079013f87 100644
--- a/extensions/amazon-services/sns/runtime/src/main/resources/META-INF/quarkus-extension.yaml
+++ b/extensions/amazon-services/sns/runtime/src/main/resources/META-INF/quarkus-extension.yaml
@@ -9,6 +9,6 @@ metadata:
guide: "https://quarkus.io/guides/amazon-sns"
categories:
- "data"
- status: "preview"
+ status: "stable"
config:
- "quarkus.sns."
diff --git a/extensions/amazon-services/sqs/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/amazon-services/sqs/runtime/src/main/resources/META-INF/quarkus-extension.yaml
index 58366e56f9dc92..35623c807f85bb 100644
--- a/extensions/amazon-services/sqs/runtime/src/main/resources/META-INF/quarkus-extension.yaml
+++ b/extensions/amazon-services/sqs/runtime/src/main/resources/META-INF/quarkus-extension.yaml
@@ -9,6 +9,6 @@ metadata:
guide: "https://quarkus.io/guides/amazon-sqs"
categories:
- "data"
- status: "preview"
+ status: "stable"
config:
- "quarkus.sqs."
diff --git a/extensions/amazon-services/ssm/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/amazon-services/ssm/runtime/src/main/resources/META-INF/quarkus-extension.yaml
index b3024a2dfb0878..ec7c7fbba307c1 100644
--- a/extensions/amazon-services/ssm/runtime/src/main/resources/META-INF/quarkus-extension.yaml
+++ b/extensions/amazon-services/ssm/runtime/src/main/resources/META-INF/quarkus-extension.yaml
@@ -9,6 +9,6 @@ metadata:
guide: "https://quarkus.io/guides/amazon-ssm"
categories:
- "data"
- status: "preview"
+ status: "stable"
config:
- "quarkus.ssm."
diff --git a/extensions/apicurio-registry-avro/deployment/src/main/java/io/quarkus/apicurio/registry/avro/DevServicesApicurioRegistryProcessor.java b/extensions/apicurio-registry-avro/deployment/src/main/java/io/quarkus/apicurio/registry/avro/DevServicesApicurioRegistryProcessor.java
index 1147572ecc9bf9..b644e4a860a0a7 100644
--- a/extensions/apicurio-registry-avro/deployment/src/main/java/io/quarkus/apicurio/registry/avro/DevServicesApicurioRegistryProcessor.java
+++ b/extensions/apicurio-registry-avro/deployment/src/main/java/io/quarkus/apicurio/registry/avro/DevServicesApicurioRegistryProcessor.java
@@ -20,7 +20,10 @@
import io.quarkus.deployment.builditem.DevServicesConfigResultBuildItem;
import io.quarkus.deployment.builditem.DevServicesSharedNetworkBuildItem;
import io.quarkus.deployment.builditem.LaunchModeBuildItem;
+import io.quarkus.deployment.console.ConsoleInstalledBuildItem;
+import io.quarkus.deployment.console.StartupLogCompressor;
import io.quarkus.deployment.dev.devservices.GlobalDevServicesConfig;
+import io.quarkus.deployment.logging.LoggingSetupBuildItem;
import io.quarkus.devservices.common.ContainerLocator;
import io.quarkus.runtime.LaunchMode;
import io.quarkus.runtime.configuration.ConfigUtils;
@@ -57,7 +60,9 @@ public class DevServicesApicurioRegistryProcessor {
public void startApicurioRegistryDevService(LaunchModeBuildItem launchMode,
ApicurioRegistryDevServicesBuildTimeConfig apicurioRegistryDevServices,
Optional devServicesSharedNetworkBuildItem,
- BuildProducer devServicesConfiguration) {
+ BuildProducer devServicesConfiguration,
+ Optional consoleInstalledBuildItem,
+ LoggingSetupBuildItem loggingSetupBuildItem) {
ApicurioRegistryDevServiceCfg configuration = getConfiguration(apicurioRegistryDevServices);
@@ -69,11 +74,20 @@ public void startApicurioRegistryDevService(LaunchModeBuildItem launchMode,
shutdownApicurioRegistry();
cfg = null;
}
-
- ApicurioRegistry apicurioRegistry = startApicurioRegistry(configuration, launchMode,
- devServicesSharedNetworkBuildItem.isPresent());
- if (apicurioRegistry == null) {
- return;
+ ApicurioRegistry apicurioRegistry;
+ StartupLogCompressor compressor = new StartupLogCompressor(
+ (launchMode.isTest() ? "(test) " : "") + "Apicurio Registry Dev Services Starting:",
+ consoleInstalledBuildItem, loggingSetupBuildItem);
+ try {
+ apicurioRegistry = startApicurioRegistry(configuration, launchMode,
+ devServicesSharedNetworkBuildItem.isPresent());
+ if (apicurioRegistry == null) {
+ return;
+ }
+ compressor.close();
+ } catch (Throwable t) {
+ compressor.closeAndDumpCaptured();
+ throw new RuntimeException(t);
}
cfg = configuration;
diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanArchivePredicateBuildItem.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanArchivePredicateBuildItem.java
new file mode 100644
index 00000000000000..26aabbf1797463
--- /dev/null
+++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanArchivePredicateBuildItem.java
@@ -0,0 +1,25 @@
+package io.quarkus.arc.deployment;
+
+import java.util.function.Predicate;
+
+import io.quarkus.builder.item.MultiBuildItem;
+import io.quarkus.deployment.ApplicationArchive;
+
+/**
+ *
+ * By default, only explict/implicit bean archives (as defined by the spec) are considered during the bean discovery. However,
+ * extensions can register a logic to identify additional bean archives.
+ */
+public final class BeanArchivePredicateBuildItem extends MultiBuildItem {
+
+ private final Predicate predicate;
+
+ public BeanArchivePredicateBuildItem(Predicate predicate) {
+ this.predicate = predicate;
+ }
+
+ public Predicate getPredicate() {
+ return predicate;
+ }
+
+}
diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanArchiveProcessor.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanArchiveProcessor.java
index 2bb662fd5a7b04..8133de96db552a 100644
--- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanArchiveProcessor.java
+++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanArchiveProcessor.java
@@ -1,6 +1,11 @@
package io.quarkus.arc.deployment;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
import java.util.stream.Collectors;
import org.jboss.jandex.AnnotationInstance;
@@ -35,12 +40,13 @@ public BeanArchiveIndexBuildItem build(ArcConfig config, ApplicationArchivesBuil
List additionalBeanDefiningAnnotations,
List additionalBeans, List generatedBeans,
LiveReloadBuildItem liveReloadBuildItem, BuildProducer generatedClass,
- CustomScopeAnnotationsBuildItem customScopes, List excludeDependencyBuildItems)
+ CustomScopeAnnotationsBuildItem customScopes, List excludeDependencyBuildItems,
+ List beanArchivePredicates)
throws Exception {
// First build an index from application archives
IndexView applicationIndex = buildApplicationIndex(config, applicationArchivesBuildItem,
- additionalBeanDefiningAnnotations, customScopes, excludeDependencyBuildItems);
+ additionalBeanDefiningAnnotations, customScopes, excludeDependencyBuildItems, beanArchivePredicates);
// Then build additional index for beans added by extensions
Indexer additionalBeanIndexer = new Indexer();
@@ -81,7 +87,8 @@ public BeanArchiveIndexBuildItem build(ArcConfig config, ApplicationArchivesBuil
private IndexView buildApplicationIndex(ArcConfig config, ApplicationArchivesBuildItem applicationArchivesBuildItem,
List additionalBeanDefiningAnnotations,
- CustomScopeAnnotationsBuildItem customScopes, List excludeDependencyBuildItems) {
+ CustomScopeAnnotationsBuildItem customScopes, List excludeDependencyBuildItems,
+ List beanArchivePredicates) {
Set archives = applicationArchivesBuildItem.getAllApplicationArchives();
@@ -116,10 +123,8 @@ private IndexView buildApplicationIndex(ArcConfig config, ApplicationArchivesBui
continue;
}
IndexView index = archive.getIndex();
- // NOTE: Implicit bean archive without beans.xml contains one or more bean classes with a bean defining annotation and no extension
- if (archive.getChildPath("META-INF/beans.xml") != null || archive.getChildPath("WEB-INF/beans.xml") != null
- || (index.getAllKnownImplementors(DotNames.EXTENSION).isEmpty()
- && containsBeanDefiningAnnotation(index, beanDefiningAnnotations))) {
+ if (isExplicitBeanArchive(archive) || isImplicitBeanArchive(index, beanDefiningAnnotations)
+ || isAdditionalBeanArchive(archive, beanArchivePredicates)) {
indexes.add(index);
}
}
@@ -127,6 +132,26 @@ && containsBeanDefiningAnnotation(index, beanDefiningAnnotations))) {
return CompositeIndex.create(indexes);
}
+ private boolean isExplicitBeanArchive(ApplicationArchive archive) {
+ return archive.getChildPath("META-INF/beans.xml") != null || archive.getChildPath("WEB-INF/beans.xml") != null;
+ }
+
+ private boolean isImplicitBeanArchive(IndexView index, Set beanDefiningAnnotations) {
+ // NOTE: Implicit bean archive without beans.xml contains one or more bean classes with a bean defining annotation and no extension
+ return index.getAllKnownImplementors(DotNames.EXTENSION).isEmpty()
+ && containsBeanDefiningAnnotation(index, beanDefiningAnnotations);
+ }
+
+ private boolean isAdditionalBeanArchive(ApplicationArchive archive,
+ List beanArchivePredicates) {
+ for (BeanArchivePredicateBuildItem p : beanArchivePredicates) {
+ if (p.getPredicate().test(archive)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private boolean isApplicationArchiveExcluded(ArcConfig config, List excludeDependencyBuildItems,
ApplicationArchive archive) {
if (archive.getArtifactKey() != null) {
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 ee60809ab577f7..a0d855f1ce38ff 100644
--- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigBuildStep.java
+++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigBuildStep.java
@@ -1,12 +1,17 @@
package io.quarkus.arc.deployment;
+import static io.quarkus.arc.deployment.ConfigClassBuildItem.Type.MAPPING;
+import static io.quarkus.arc.deployment.ConfigClassBuildItem.Type.PROPERTIES;
import static io.quarkus.deployment.annotations.ExecutionTime.RUNTIME_INIT;
-import static io.quarkus.deployment.configuration.ConfigMappingUtils.CONFIG_MAPPING_NAME;
import static io.smallrye.config.ConfigMappings.ConfigClassWithPrefix.configClassWithPrefix;
+import static java.util.Collections.emptySet;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
+import static org.eclipse.microprofile.config.inject.ConfigProperties.UNCONFIGURED_PREFIX;
import static org.jboss.jandex.AnnotationInstance.create;
import static org.jboss.jandex.AnnotationTarget.Kind.CLASS;
+import static org.jboss.jandex.AnnotationTarget.Kind.FIELD;
+import static org.jboss.jandex.AnnotationTarget.Kind.METHOD_PARAMETER;
import static org.jboss.jandex.AnnotationValue.createStringValue;
import java.util.ArrayList;
@@ -14,6 +19,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Stream;
@@ -25,8 +31,10 @@
import org.eclipse.microprofile.config.inject.ConfigProperties;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.jboss.jandex.AnnotationInstance;
+import org.jboss.jandex.AnnotationTarget;
import org.jboss.jandex.AnnotationValue;
import org.jboss.jandex.ClassInfo;
+import org.jboss.jandex.ClassType;
import org.jboss.jandex.DotName;
import org.jboss.jandex.FieldInfo;
import org.jboss.jandex.IndexView;
@@ -46,15 +54,16 @@
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.Record;
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
-import io.quarkus.deployment.builditem.ConfigClassBuildItem;
import io.quarkus.deployment.builditem.ConfigurationBuildItem;
import io.quarkus.deployment.builditem.GeneratedClassBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
-import io.quarkus.deployment.configuration.ConfigMappingUtils;
import io.quarkus.deployment.configuration.definition.RootDefinition;
import io.quarkus.deployment.recording.RecorderContext;
import io.quarkus.gizmo.ResultHandle;
import io.quarkus.runtime.annotations.ConfigPhase;
+import io.smallrye.config.ConfigMapping;
+import io.smallrye.config.ConfigMappingLoader;
+import io.smallrye.config.ConfigMappingMetadata;
import io.smallrye.config.ConfigMappings.ConfigClassWithPrefix;
import io.smallrye.config.inject.ConfigProducer;
@@ -66,6 +75,7 @@ public class ConfigBuildStep {
private static final DotName MP_CONFIG_PROPERTIES_NAME = DotName.createSimple(ConfigProperties.class.getName());
private static final DotName MP_CONFIG_VALUE_NAME = DotName.createSimple(ConfigValue.class.getName());
+ private static final DotName CONFIG_MAPPING_NAME = DotName.createSimple(ConfigMapping.class.getName());
private static final DotName MAP_NAME = DotName.createSimple(Map.class.getName());
private static final DotName SET_NAME = DotName.createSimple(Set.class.getName());
private static final DotName LIST_NAME = DotName.createSimple(List.class.getName());
@@ -242,11 +252,79 @@ void generateConfigClasses(
BuildProducer reflectiveClasses,
BuildProducer configClasses) {
- // TODO - Generation of Mapping interface classes can be done in core because they don't require CDI
- ConfigMappingUtils.generateConfigClasses(combinedIndex, generatedClasses, reflectiveClasses, configClasses,
- CONFIG_MAPPING_NAME);
- ConfigMappingUtils.generateConfigClasses(combinedIndex, generatedClasses, reflectiveClasses, configClasses,
- MP_CONFIG_PROPERTIES_NAME);
+ List mappingAnnotations = new ArrayList<>();
+ mappingAnnotations.addAll(combinedIndex.getIndex().getAnnotations(CONFIG_MAPPING_NAME));
+ mappingAnnotations.addAll(combinedIndex.getIndex().getAnnotations(MP_CONFIG_PROPERTIES_NAME));
+
+ for (AnnotationInstance instance : mappingAnnotations) {
+ AnnotationTarget target = instance.target();
+ AnnotationValue annotationPrefix = instance.value("prefix");
+
+ if (target.kind().equals(FIELD)) {
+ if (annotationPrefix != null && !annotationPrefix.asString().equals(UNCONFIGURED_PREFIX)) {
+ configClasses.produce(
+ toConfigClassBuildItem(instance, toClass(target.asField().type().name()),
+ annotationPrefix.asString()));
+ continue;
+ }
+ }
+
+ if (target.kind().equals(METHOD_PARAMETER)) {
+ if (annotationPrefix != null && !annotationPrefix.asString().equals(UNCONFIGURED_PREFIX)) {
+ ClassType classType = target.asMethodParameter().method().parameters()
+ .get(target.asMethodParameter().position()).asClassType();
+ configClasses
+ .produce(toConfigClassBuildItem(instance, toClass(classType.name()), annotationPrefix.asString()));
+ continue;
+ }
+ }
+
+ if (!target.kind().equals(CLASS)) {
+ continue;
+ }
+
+ Class> configClass = toClass(target.asClass().name());
+ String prefix = Optional.ofNullable(annotationPrefix).map(AnnotationValue::asString).orElse("");
+
+ List configMappingsMetadata = ConfigMappingLoader.getConfigMappingsMetadata(configClass);
+ Set generatedClassesNames = new HashSet<>();
+ Set mappingsInfo = new HashSet<>();
+ configMappingsMetadata.forEach(mappingMetadata -> {
+ generatedClasses.produce(
+ new GeneratedClassBuildItem(true, mappingMetadata.getClassName(), mappingMetadata.getClassBytes()));
+ reflectiveClasses
+ .produce(ReflectiveClassBuildItem.builder(mappingMetadata.getInterfaceType()).methods(true).build());
+ reflectiveClasses
+ .produce(ReflectiveClassBuildItem.builder(mappingMetadata.getClassName()).constructors(true).build());
+
+ for (Class> parent : getHierarchy(mappingMetadata.getInterfaceType())) {
+ reflectiveClasses.produce(ReflectiveClassBuildItem.builder(parent).methods(true).build());
+ }
+
+ generatedClassesNames.add(mappingMetadata.getClassName());
+
+ ClassInfo mappingInfo = combinedIndex.getIndex()
+ .getClassByName(DotName.createSimple(mappingMetadata.getInterfaceType().getName()));
+ if (mappingInfo != null) {
+ mappingsInfo.add(mappingInfo);
+ }
+ });
+
+ // Search and register possible classes for implicit Converter methods
+ for (ClassInfo classInfo : mappingsInfo) {
+ for (MethodInfo method : classInfo.methods()) {
+ if (!isHandledByProducers(method.returnType()) &&
+ mappingsInfo.stream()
+ .map(ClassInfo::name)
+ .noneMatch(name -> name.equals(method.returnType().name()))) {
+ reflectiveClasses
+ .produce(new ReflectiveClassBuildItem(true, false, method.returnType().name().toString()));
+ }
+ }
+ }
+
+ configClasses.produce(toConfigClassBuildItem(instance, configClass, generatedClassesNames, prefix));
+ }
}
@BuildStep
@@ -327,6 +405,12 @@ void registerConfigClasses(
configClassWithPrefix -> Stream.of(configClassWithPrefix.getKlass(), configClassWithPrefix.getPrefix())
.collect(toList()));
+ recorder.registerConfigMappings(
+ configClasses.stream()
+ .filter(ConfigClassBuildItem::isMapping)
+ .map(configMapping -> configClassWithPrefix(configMapping.getConfigClass(), configMapping.getPrefix()))
+ .collect(toSet()));
+
recorder.registerConfigProperties(
configClasses.stream()
.filter(ConfigClassBuildItem::isProperties)
@@ -335,6 +419,45 @@ void registerConfigClasses(
.collect(toSet()));
}
+ private static Class> toClass(DotName dotName) {
+ ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
+ try {
+ return classLoader.loadClass(dotName.toString());
+ } catch (ClassNotFoundException e) {
+ throw new IllegalStateException("The class (" + dotName.toString() + ") cannot be created during deployment.", e);
+ }
+ }
+
+ private static ConfigClassBuildItem toConfigClassBuildItem(
+ AnnotationInstance instance,
+ Class> configClass,
+ String prefix) {
+ return toConfigClassBuildItem(instance, configClass, emptySet(), prefix);
+ }
+
+ private static ConfigClassBuildItem toConfigClassBuildItem(
+ AnnotationInstance instance,
+ Class> configClass,
+ Set generatedClasses,
+ String prefix) {
+ if (instance.name().equals(CONFIG_MAPPING_NAME)) {
+ return new ConfigClassBuildItem(configClass, generatedClasses, prefix, MAPPING);
+ } else if (instance.name().equals(MP_CONFIG_PROPERTIES_NAME)) {
+ return new ConfigClassBuildItem(configClass, generatedClasses, prefix, PROPERTIES);
+ } else {
+ throw new IllegalArgumentException();
+ }
+ }
+
+ private static List> getHierarchy(Class> mapping) {
+ List> interfaces = new ArrayList<>();
+ for (Class> i : mapping.getInterfaces()) {
+ interfaces.add(i);
+ interfaces.addAll(getHierarchy(i));
+ }
+ return interfaces;
+ }
+
private String getPropertyName(String name, ClassInfo declaringClass) {
StringBuilder builder = new StringBuilder();
if (declaringClass.enclosingClass() == null) {
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/ConfigClassBuildItem.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigClassBuildItem.java
similarity index 94%
rename from core/deployment/src/main/java/io/quarkus/deployment/builditem/ConfigClassBuildItem.java
rename to extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigClassBuildItem.java
index fb3e80e17225e2..1d011ddbe5824d 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/ConfigClassBuildItem.java
+++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ConfigClassBuildItem.java
@@ -1,4 +1,4 @@
-package io.quarkus.deployment.builditem;
+package io.quarkus.arc.deployment;
import java.util.Set;
@@ -48,6 +48,6 @@ public boolean isProperties() {
public enum Type {
MAPPING,
- PROPERTIES
+ PROPERTIES;
}
}
diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/ConfigMappingTest.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/ConfigMappingTest.java
index 7c19fb0eba2f2e..019633add73fb9 100644
--- a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/ConfigMappingTest.java
+++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/config/ConfigMappingTest.java
@@ -39,6 +39,7 @@ public class ConfigMappingTest {
static final QuarkusUnitTest TEST = new QuarkusUnitTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
.addAsResource(new StringAsset("config.my.prop=1234\n" +
+ "config.override.my.prop=5678\n" +
"group.host=localhost\n" +
"group.port=8080\n" +
"types.int=9\n" +
@@ -354,15 +355,22 @@ void hierarchy() {
@Dependent
public static class ConstructorInjection {
private String myProp;
+ private String overrideProp;
@Inject
- public ConstructorInjection(@ConfigMapping(prefix = "config") MyConfigMapping myConfigMapping) {
+ public ConstructorInjection(@ConfigMapping(prefix = "config") MyConfigMapping myConfigMapping,
+ @ConfigMapping(prefix = "config.override") MyConfigMapping override) {
this.myProp = myConfigMapping.myProp();
+ this.overrideProp = override.myProp();
}
public String getMyProp() {
return myProp;
}
+
+ public String getOverrideProp() {
+ return overrideProp;
+ }
}
@Inject
@@ -371,5 +379,6 @@ public String getMyProp() {
@Test
void constructorInjection() {
assertEquals("1234", constructorInjection.getMyProp());
+ assertEquals("5678", constructorInjection.getOverrideProp());
}
}
diff --git a/extensions/container-image/container-image-docker/deployment/src/main/java/io/quarkus/container/image/docker/deployment/DockerProcessor.java b/extensions/container-image/container-image-docker/deployment/src/main/java/io/quarkus/container/image/docker/deployment/DockerProcessor.java
index e2afb940d65e58..dba24339b49038 100644
--- a/extensions/container-image/container-image-docker/deployment/src/main/java/io/quarkus/container/image/docker/deployment/DockerProcessor.java
+++ b/extensions/container-image/container-image-docker/deployment/src/main/java/io/quarkus/container/image/docker/deployment/DockerProcessor.java
@@ -136,7 +136,7 @@ private String createContainerImage(ContainerImageConfig containerImageConfig, D
PackageConfig packageConfig) {
DockerfilePaths dockerfilePaths = getDockerfilePaths(dockerConfig, forNative, packageConfig, out);
- String[] dockerArgs = getDockerArgs(containerImageInfo.getImage(), dockerfilePaths, dockerConfig);
+ String[] dockerArgs = getDockerArgs(containerImageInfo.getImage(), dockerfilePaths, containerImageConfig, dockerConfig);
log.infof("Executing the following command to build docker image: '%s %s'", dockerConfig.executableName,
String.join(" ", dockerArgs));
boolean buildSuccessful = ExecUtil.exec(out.getOutputDirectory().toFile(), reader, dockerConfig.executableName,
@@ -178,12 +178,16 @@ private String createContainerImage(ContainerImageConfig containerImageConfig, D
return containerImageInfo.getImage();
}
- private String[] getDockerArgs(String image, DockerfilePaths dockerfilePaths, DockerConfig dockerConfig) {
+ private String[] getDockerArgs(String image, DockerfilePaths dockerfilePaths, ContainerImageConfig containerImageConfig,
+ DockerConfig dockerConfig) {
List dockerArgs = new ArrayList<>(6 + dockerConfig.buildArgs.size());
dockerArgs.addAll(Arrays.asList("build", "-f", dockerfilePaths.getDockerfilePath().toAbsolutePath().toString()));
for (Map.Entry entry : dockerConfig.buildArgs.entrySet()) {
dockerArgs.addAll(Arrays.asList("--build-arg", entry.getKey() + "=" + entry.getValue()));
}
+ for (Map.Entry entry : containerImageConfig.labels.entrySet()) {
+ dockerArgs.addAll(Arrays.asList("--label", String.format("%s=%s", entry.getKey(), entry.getValue())));
+ }
if (dockerConfig.cacheFrom.isPresent()) {
List cacheFrom = dockerConfig.cacheFrom.get();
if (!cacheFrom.isEmpty()) {
diff --git a/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibConfig.java b/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibConfig.java
index c1d476e7e52d99..f8f74492a59795 100644
--- a/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibConfig.java
+++ b/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibConfig.java
@@ -85,8 +85,11 @@ public class JibConfig {
/**
* Custom labels to add to the generated image
+ *
+ * @deprecated Use 'quarkus.container-image.labels' instead
*/
@ConfigItem
+ @Deprecated
public Map labels;
/**
diff --git a/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibProcessor.java b/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibProcessor.java
index 83e01cdd95028e..910bc79d776cca 100644
--- a/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibProcessor.java
+++ b/extensions/container-image/container-image-jib/deployment/src/main/java/io/quarkus/container/image/jib/deployment/JibProcessor.java
@@ -119,10 +119,11 @@ public void buildFromJar(ContainerImageConfig containerImageConfig, JibConfig ji
JibContainerBuilder jibContainerBuilder;
String packageType = packageConfig.type;
if (packageConfig.isLegacyJar() || packageType.equalsIgnoreCase(PackageConfig.UBER_JAR)) {
- jibContainerBuilder = createContainerBuilderFromLegacyJar(jibConfig,
+ jibContainerBuilder = createContainerBuilderFromLegacyJar(jibConfig, containerImageConfig,
sourceJar, outputTarget, mainClass, containerImageLabels);
} else if (packageConfig.isFastJar()) {
- jibContainerBuilder = createContainerBuilderFromFastJar(jibConfig, sourceJar, curateOutcome, containerImageLabels,
+ jibContainerBuilder = createContainerBuilderFromFastJar(jibConfig, containerImageConfig, sourceJar, curateOutcome,
+ containerImageLabels,
appCDSResult);
} else {
throw new IllegalArgumentException(
@@ -161,7 +162,7 @@ public void buildFromNative(ContainerImageConfig containerImageConfig, JibConfig
"The native binary produced by the build is not a Linux binary and therefore cannot be used in a Linux container image. Consider adding \"quarkus.native.container-build=true\" to your configuration");
}
- JibContainerBuilder jibContainerBuilder = createContainerBuilderFromNative(containerImageConfig, jibConfig,
+ JibContainerBuilder jibContainerBuilder = createContainerBuilderFromNative(jibConfig, containerImageConfig,
nativeImage, containerImageLabels);
setUser(jibConfig, jibContainerBuilder);
setPlatforms(jibConfig, jibContainerBuilder);
@@ -280,6 +281,7 @@ private Logger.Level toJBossLoggingLevel(LogEvent.Level level) {
*
*/
private JibContainerBuilder createContainerBuilderFromFastJar(JibConfig jibConfig,
+ ContainerImageConfig containerImageConfig,
JarBuildItem sourceJarBuildItem,
CurateOutcomeBuildItem curateOutcome, List containerImageLabels,
Optional appCDSResult) {
@@ -427,7 +429,7 @@ private JibContainerBuilder createContainerBuilderFromFastJar(JibConfig jibConfi
.setWorkingDirectory(workDirInContainer)
.setEntrypoint(entrypoint)
.setEnvironment(getEnvironmentVariables(jibConfig))
- .setLabels(allLabels(jibConfig, containerImageLabels))
+ .setLabels(allLabels(jibConfig, containerImageConfig, containerImageLabels))
.setCreationTime(Instant.now());
for (int port : jibConfig.ports) {
jibContainerBuilder.addExposedPort(Port.tcp(port));
@@ -468,6 +470,7 @@ private void setPlatforms(JibConfig jibConfig, JibContainerBuilder jibContainerB
}
private JibContainerBuilder createContainerBuilderFromLegacyJar(JibConfig jibConfig,
+ ContainerImageConfig containerImageConfig,
JarBuildItem sourceJarBuildItem,
OutputTargetBuildItem outputTargetBuildItem,
MainClassBuildItem mainClassBuildItem,
@@ -502,7 +505,7 @@ private JibContainerBuilder createContainerBuilderFromLegacyJar(JibConfig jibCon
JibContainerBuilder jibContainerBuilder = javaContainerBuilder.toContainerBuilder()
.setEnvironment(getEnvironmentVariables(jibConfig))
- .setLabels(allLabels(jibConfig, containerImageLabels))
+ .setLabels(allLabels(jibConfig, containerImageConfig, containerImageLabels))
.setCreationTime(Instant.now());
if (jibConfig.jvmEntrypoint.isPresent()) {
@@ -517,7 +520,7 @@ private JibContainerBuilder createContainerBuilderFromLegacyJar(JibConfig jibCon
}
}
- private JibContainerBuilder createContainerBuilderFromNative(ContainerImageConfig containerImageConfig, JibConfig jibConfig,
+ private JibContainerBuilder createContainerBuilderFromNative(JibConfig jibConfig, ContainerImageConfig containerImageConfig,
NativeImageBuildItem nativeImageBuildItem, List containerImageLabels) {
List entrypoint;
@@ -532,8 +535,8 @@ private JibContainerBuilder createContainerBuilderFromNative(ContainerImageConfi
try {
AbsoluteUnixPath workDirInContainer = AbsoluteUnixPath.get("/work");
JibContainerBuilder jibContainerBuilder = Jib
- .from(toRegistryImage(ImageReference.parse(jibConfig.baseNativeImage), containerImageConfig.username,
- containerImageConfig.password))
+ .from(toRegistryImage(ImageReference.parse(jibConfig.baseNativeImage), jibConfig.baseRegistryUsername,
+ jibConfig.baseRegistryPassword))
.addFileEntriesLayer(FileEntriesLayer.builder()
.addEntry(nativeImageBuildItem.getPath(), workDirInContainer.resolve(BINARY_NAME_IN_CONTAINER),
FilePermissions.fromOctalString("775"))
@@ -541,7 +544,7 @@ private JibContainerBuilder createContainerBuilderFromNative(ContainerImageConfi
.setWorkingDirectory(workDirInContainer)
.setEntrypoint(entrypoint)
.setEnvironment(getEnvironmentVariables(jibConfig))
- .setLabels(allLabels(jibConfig, containerImageLabels))
+ .setLabels(allLabels(jibConfig, containerImageConfig, containerImageLabels))
.setCreationTime(Instant.now());
for (int port : jibConfig.ports) {
jibContainerBuilder.addExposedPort(Port.tcp(port));
@@ -604,12 +607,14 @@ private void handleExtraFiles(OutputTargetBuildItem outputTarget, JibContainerBu
}
}
- private Map allLabels(JibConfig jibConfig, List containerImageLabels) {
+ private Map allLabels(JibConfig jibConfig, ContainerImageConfig containerImageConfig,
+ List containerImageLabels) {
if (jibConfig.labels.isEmpty() && containerImageLabels.isEmpty()) {
return Collections.emptyMap();
}
final Map allLabels = new HashMap<>(jibConfig.labels);
+ allLabels.putAll(containerImageConfig.labels);
for (ContainerImageLabelBuildItem containerImageLabel : containerImageLabels) {
// we want the user supplied labels to take precedence so the user can override labels generated from other extensions if desired
allLabels.putIfAbsent(containerImageLabel.getName(), containerImageLabel.getValue());
diff --git a/extensions/container-image/container-image-openshift/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/container-image/container-image-openshift/runtime/src/main/resources/META-INF/quarkus-extension.yaml
index 5f6b78609781c9..9c1ee15c7c8bb0 100644
--- a/extensions/container-image/container-image-openshift/runtime/src/main/resources/META-INF/quarkus-extension.yaml
+++ b/extensions/container-image/container-image-openshift/runtime/src/main/resources/META-INF/quarkus-extension.yaml
@@ -8,4 +8,4 @@ metadata:
- "image"
categories:
- "cloud"
- status: "preview"
\ No newline at end of file
+ status: "stable"
diff --git a/extensions/container-image/deployment/src/main/java/io/quarkus/container/image/deployment/ContainerImageConfig.java b/extensions/container-image/deployment/src/main/java/io/quarkus/container/image/deployment/ContainerImageConfig.java
index 87547b9f92a7b8..986326cfe50c79 100644
--- a/extensions/container-image/deployment/src/main/java/io/quarkus/container/image/deployment/ContainerImageConfig.java
+++ b/extensions/container-image/deployment/src/main/java/io/quarkus/container/image/deployment/ContainerImageConfig.java
@@ -1,10 +1,13 @@
package io.quarkus.container.image.deployment;
import java.util.List;
+import java.util.Map;
import java.util.Optional;
import io.quarkus.runtime.annotations.ConfigItem;
import io.quarkus.runtime.annotations.ConfigRoot;
+import io.quarkus.runtime.annotations.ConvertWith;
+import io.quarkus.runtime.configuration.TrimmedStringConverter;
@ConfigRoot
public class ContainerImageConfig {
@@ -13,12 +16,14 @@ public class ContainerImageConfig {
* The group the container image will be part of
*/
@ConfigItem(defaultValue = "${user.name}")
+ @ConvertWith(TrimmedStringConverter.class)
public Optional group;
/**
* The name of the container image. If not set defaults to the application name
*/
@ConfigItem(defaultValue = "${quarkus.application.name:unset}")
+ @ConvertWith(TrimmedStringConverter.class)
public Optional name;
/**
@@ -33,6 +38,12 @@ public class ContainerImageConfig {
@ConfigItem
public Optional> additionalTags;
+ /**
+ * Custom labels to add to the generated image.
+ */
+ @ConfigItem
+ public Map labels;
+
/**
* The container registry to use
*/
@@ -95,6 +106,9 @@ public Optional getEffectiveGroup() {
if (group.isPresent()) {
String originalGroup = group.get();
if (originalGroup.equals(System.getProperty("user.name"))) {
+ if (originalGroup.isEmpty()) {
+ return Optional.empty();
+ }
return Optional.of(originalGroup.toLowerCase().replace(' ', '-'));
}
}
diff --git a/extensions/datasource/deployment/src/main/java/io/quarkus/datasource/deployment/devservices/DevServicesDatasourceProcessor.java b/extensions/datasource/deployment/src/main/java/io/quarkus/datasource/deployment/devservices/DevServicesDatasourceProcessor.java
index 0881c79369314e..537dbbec2327c1 100644
--- a/extensions/datasource/deployment/src/main/java/io/quarkus/datasource/deployment/devservices/DevServicesDatasourceProcessor.java
+++ b/extensions/datasource/deployment/src/main/java/io/quarkus/datasource/deployment/devservices/DevServicesDatasourceProcessor.java
@@ -27,7 +27,10 @@
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.DevServicesConfigResultBuildItem;
import io.quarkus.deployment.builditem.LaunchModeBuildItem;
+import io.quarkus.deployment.console.ConsoleInstalledBuildItem;
+import io.quarkus.deployment.console.StartupLogCompressor;
import io.quarkus.deployment.dev.devservices.GlobalDevServicesConfig;
+import io.quarkus.deployment.logging.LoggingSetupBuildItem;
import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem;
import io.quarkus.runtime.LaunchMode;
@@ -50,7 +53,9 @@ DevServicesDatasourceResultBuildItem launchDatabases(CurateOutcomeBuildItem cura
DataSourcesBuildTimeConfig dataSourceBuildTimeConfig,
LaunchModeBuildItem launchMode,
List configurationHandlerBuildItems,
- BuildProducer devServicesResultBuildItemBuildProducer) {
+ BuildProducer devServicesResultBuildItemBuildProducer,
+ Optional consoleInstalledBuildItem,
+ LoggingSetupBuildItem loggingSetupBuildItem) {
//figure out if we need to shut down and restart existing databases
//if not and the DB's have already started we just return
if (databases != null) {
@@ -111,60 +116,70 @@ DevServicesDatasourceResultBuildItem launchDatabases(CurateOutcomeBuildItem cura
Map devDBProviderMap = devDBProviders.stream()
.collect(Collectors.toMap(DevServicesDatasourceProviderBuildItem::getDatabase,
DevServicesDatasourceProviderBuildItem::getDevServicesProvider));
-
- defaultResult = startDevDb(null, curateOutcomeBuildItem, installedDrivers,
- !dataSourceBuildTimeConfig.namedDataSources.isEmpty(),
- devDBProviderMap,
- dataSourceBuildTimeConfig.defaultDataSource,
- configHandlersByDbType, propertiesMap, closeableList, launchMode.getLaunchMode());
- List dbConfig = new ArrayList<>();
- if (defaultResult != null) {
- for (Map.Entry i : defaultResult.getConfigProperties().entrySet()) {
- dbConfig.add(new DevServicesConfigResultBuildItem(i.getKey(), i.getValue()));
- }
- }
- for (Map.Entry entry : dataSourceBuildTimeConfig.namedDataSources.entrySet()) {
- DevServicesDatasourceResultBuildItem.DbResult result = startDevDb(entry.getKey(), curateOutcomeBuildItem,
- installedDrivers, true,
- devDBProviderMap, entry.getValue(), configHandlersByDbType, propertiesMap, closeableList,
- launchMode.getLaunchMode());
- if (result != null) {
- namedResults.put(entry.getKey(), result);
- for (Map.Entry i : result.getConfigProperties().entrySet()) {
+ StartupLogCompressor compressor = new StartupLogCompressor(
+ (launchMode.isTest() ? "(test) " : "") + "Database Starting:",
+ consoleInstalledBuildItem,
+ loggingSetupBuildItem);
+ try {
+ defaultResult = startDevDb(null, curateOutcomeBuildItem, installedDrivers,
+ !dataSourceBuildTimeConfig.namedDataSources.isEmpty(),
+ devDBProviderMap,
+ dataSourceBuildTimeConfig.defaultDataSource,
+ configHandlersByDbType, propertiesMap, closeableList, launchMode.getLaunchMode());
+ List dbConfig = new ArrayList<>();
+ if (defaultResult != null) {
+ for (Map.Entry i : defaultResult.getConfigProperties().entrySet()) {
dbConfig.add(new DevServicesConfigResultBuildItem(i.getKey(), i.getValue()));
}
}
- }
- for (DevServicesConfigResultBuildItem i : dbConfig) {
- devServicesResultBuildItemBuildProducer
- .produce(i);
- }
+ for (Map.Entry entry : dataSourceBuildTimeConfig.namedDataSources.entrySet()) {
+ DevServicesDatasourceResultBuildItem.DbResult result = startDevDb(entry.getKey(), curateOutcomeBuildItem,
+ installedDrivers, true,
+ devDBProviderMap, entry.getValue(), configHandlersByDbType, propertiesMap, closeableList,
+ launchMode.getLaunchMode());
+ if (result != null) {
+ namedResults.put(entry.getKey(), result);
+ for (Map.Entry i : result.getConfigProperties().entrySet()) {
+ dbConfig.add(new DevServicesConfigResultBuildItem(i.getKey(), i.getValue()));
+ }
+ }
+ }
+ for (DevServicesConfigResultBuildItem i : dbConfig) {
+ devServicesResultBuildItemBuildProducer
+ .produce(i);
+ }
- if (first) {
- first = false;
- Runnable closeTask = new Runnable() {
- @Override
- public void run() {
- if (databases != null) {
- for (Closeable i : databases) {
- try {
- i.close();
- } catch (Throwable t) {
- log.error("Failed to stop database", t);
+ if (first) {
+ first = false;
+ Runnable closeTask = new Runnable() {
+ @Override
+ public void run() {
+ if (databases != null) {
+ for (Closeable i : databases) {
+ try {
+ i.close();
+ } catch (Throwable t) {
+ log.error("Failed to stop database", t);
+ }
}
}
+ first = true;
+ databases = null;
+ cachedProperties = null;
}
- first = true;
- databases = null;
- cachedProperties = null;
- }
- };
- QuarkusClassLoader cl = (QuarkusClassLoader) Thread.currentThread().getContextClassLoader();
- ((QuarkusClassLoader) cl.parent()).addCloseTask(closeTask);
+ };
+ QuarkusClassLoader cl = (QuarkusClassLoader) Thread.currentThread().getContextClassLoader();
+ ((QuarkusClassLoader) cl.parent()).addCloseTask(closeTask);
+ }
+ databases = closeableList;
+ cachedProperties = propertiesMap;
+ compressor.close();
+ log.info("Dev Services for datasources started.");
+ return new DevServicesDatasourceResultBuildItem(defaultResult, namedResults);
+ } catch (Throwable t) {
+ compressor.closeAndDumpCaptured();
+ throw new RuntimeException(t);
}
- databases = closeableList;
- cachedProperties = propertiesMap;
- return new DevServicesDatasourceResultBuildItem(defaultResult, namedResults);
}
private DevServicesDatasourceResultBuildItem.DbResult startDevDb(String dbName,
diff --git a/extensions/elytron-security-jdbc/deployment/pom.xml b/extensions/elytron-security-jdbc/deployment/pom.xml
index 0e71187e66be69..44c3c8f921931b 100644
--- a/extensions/elytron-security-jdbc/deployment/pom.xml
+++ b/extensions/elytron-security-jdbc/deployment/pom.xml
@@ -46,12 +46,6 @@
quarkus-resteasy-deploymenttest
-
- io.quarkus
- quarkus-vertx-http-deployment
- test-jar
- test
- io.quarkusquarkus-junit5-internal
diff --git a/extensions/elytron-security-jdbc/deployment/src/test/java/io/quarkus/elytron/security/jdbc/CustomRoleDecoderDevModeTest.java b/extensions/elytron-security-jdbc/deployment/src/test/java/io/quarkus/elytron/security/jdbc/CustomRoleDecoderDevModeTest.java
index b55a8c75daf900..772ebd49aeecf4 100644
--- a/extensions/elytron-security-jdbc/deployment/src/test/java/io/quarkus/elytron/security/jdbc/CustomRoleDecoderDevModeTest.java
+++ b/extensions/elytron-security-jdbc/deployment/src/test/java/io/quarkus/elytron/security/jdbc/CustomRoleDecoderDevModeTest.java
@@ -13,9 +13,9 @@
import org.junit.jupiter.api.extension.RegisterExtension;
import io.quarkus.deployment.util.FileUtil;
+import io.quarkus.test.ContinuousTestingTestUtils;
+import io.quarkus.test.ContinuousTestingTestUtils.TestStatus;
import io.quarkus.test.QuarkusDevModeTest;
-import io.quarkus.vertx.http.deployment.devmode.tests.TestStatus;
-import io.quarkus.vertx.http.testrunner.ContinuousTestingTestUtils;
import io.restassured.RestAssured;
//see https://github.com/quarkusio/quarkus/issues/9296
diff --git a/extensions/funqy/funqy-amazon-lambda/deployment/src/main/java/io/quarkus/funqy/deployment/bindings/FunqyLambdaBuildStep.java b/extensions/funqy/funqy-amazon-lambda/deployment/src/main/java/io/quarkus/funqy/deployment/bindings/FunqyLambdaBuildStep.java
index f6b374d81e519b..82887800d171c2 100644
--- a/extensions/funqy/funqy-amazon-lambda/deployment/src/main/java/io/quarkus/funqy/deployment/bindings/FunqyLambdaBuildStep.java
+++ b/extensions/funqy/funqy-amazon-lambda/deployment/src/main/java/io/quarkus/funqy/deployment/bindings/FunqyLambdaBuildStep.java
@@ -7,7 +7,6 @@
import java.util.Optional;
import io.quarkus.amazon.lambda.deployment.LambdaObjectMapperInitializedBuildItem;
-import io.quarkus.amazon.lambda.runtime.LambdaBuildTimeConfig;
import io.quarkus.arc.deployment.BeanContainerBuildItem;
import io.quarkus.builder.item.SimpleBuildItem;
import io.quarkus.deployment.annotations.BuildProducer;
@@ -67,14 +66,13 @@ public void startPoolLoop(FunqyLambdaBindingRecorder recorder,
@BuildStep
@Record(RUNTIME_INIT)
- public void enableNativeEventLoop(LambdaBuildTimeConfig config,
- RuntimeComplete ignored,
+ public void startPoolLoopDevOrTest(RuntimeComplete ignored,
FunqyLambdaBindingRecorder recorder,
List orderServicesFirst, // force some ordering of recorders
ShutdownContextBuildItem shutdownContextBuildItem,
LaunchModeBuildItem launchModeBuildItem) {
LaunchMode mode = launchModeBuildItem.getLaunchMode();
- if (config.enablePollingJvmMode && mode.isDevOrTest()) {
+ if (mode.isDevOrTest()) {
recorder.startPollLoop(shutdownContextBuildItem);
}
}
diff --git a/extensions/grpc/deployment/src/main/java/io/quarkus/grpc/deployment/GrpcServerProcessor.java b/extensions/grpc/deployment/src/main/java/io/quarkus/grpc/deployment/GrpcServerProcessor.java
index dab50d36a386ca..ec726faa57c9ff 100644
--- a/extensions/grpc/deployment/src/main/java/io/quarkus/grpc/deployment/GrpcServerProcessor.java
+++ b/extensions/grpc/deployment/src/main/java/io/quarkus/grpc/deployment/GrpcServerProcessor.java
@@ -40,6 +40,7 @@
import io.grpc.internal.ServerImpl;
import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.arc.deployment.AnnotationsTransformerBuildItem;
+import io.quarkus.arc.deployment.BeanArchivePredicateBuildItem;
import io.quarkus.arc.deployment.CustomScopeAnnotationsBuildItem;
import io.quarkus.arc.deployment.GeneratedBeanBuildItem;
import io.quarkus.arc.deployment.GeneratedBeanGizmoAdaptor;
@@ -49,6 +50,7 @@
import io.quarkus.arc.processor.AnnotationsTransformer;
import io.quarkus.arc.processor.BeanInfo;
import io.quarkus.arc.processor.BuiltinScope;
+import io.quarkus.deployment.ApplicationArchive;
import io.quarkus.deployment.IsDevelopment;
import io.quarkus.deployment.IsNormal;
import io.quarkus.deployment.annotations.BuildProducer;
@@ -546,6 +548,18 @@ void configureMetrics(GrpcBuildTimeConfig configuration, Optional() {
+
+ @Override
+ public boolean test(ApplicationArchive archive) {
+ // Every archive that contains a generated implementor of MutinyBean is considered a bean archive
+ return !archive.getIndex().getKnownDirectImplementors(GrpcDotNames.MUTINY_BEAN).isEmpty();
+ }
+ });
+ }
+
private static class GrpcInterceptors {
final Set globalInterceptors;
final Set nonGlobalInterceptors;
diff --git a/extensions/grpc/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/grpc/runtime/src/main/resources/META-INF/quarkus-extension.yaml
index ba8a04ffba04bc..2a2f8152e8fe75 100644
--- a/extensions/grpc/runtime/src/main/resources/META-INF/quarkus-extension.yaml
+++ b/extensions/grpc/runtime/src/main/resources/META-INF/quarkus-extension.yaml
@@ -8,7 +8,7 @@ metadata:
- "web"
- "serialization"
- "reactive"
- status: "experimental"
+ status: "stable"
codestart:
name: "grpc"
languages:
diff --git a/extensions/hibernate-orm/deployment/pom.xml b/extensions/hibernate-orm/deployment/pom.xml
index 75cea02da2efea..f1cd0a90c9730d 100644
--- a/extensions/hibernate-orm/deployment/pom.xml
+++ b/extensions/hibernate-orm/deployment/pom.xml
@@ -87,12 +87,6 @@
quarkus-smallrye-metrics-deploymenttest
-
- io.quarkus
- quarkus-vertx-http-deployment
- test
- test-jar
- org.awaitilityawaitility
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 214456677d76fc..b51cb54fb66412 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
@@ -41,6 +41,7 @@
import javax.persistence.spi.PersistenceUnitTransactionType;
import javax.transaction.TransactionManager;
+import org.eclipse.microprofile.config.ConfigProvider;
import org.hibernate.MultiTenancyStrategy;
import org.hibernate.boot.archive.scan.spi.ClassDescriptor;
import org.hibernate.boot.archive.scan.spi.PackageDescriptor;
@@ -87,12 +88,14 @@
import io.quarkus.deployment.builditem.ApplicationArchivesBuildItem;
import io.quarkus.deployment.builditem.BytecodeTransformerBuildItem;
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
+import io.quarkus.deployment.builditem.DevServicesLauncherConfigResultBuildItem;
import io.quarkus.deployment.builditem.FeatureBuildItem;
import io.quarkus.deployment.builditem.GeneratedClassBuildItem;
import io.quarkus.deployment.builditem.HotDeploymentWatchedFileBuildItem;
import io.quarkus.deployment.builditem.LaunchModeBuildItem;
import io.quarkus.deployment.builditem.LiveReloadBuildItem;
import io.quarkus.deployment.builditem.LogCategoryBuildItem;
+import io.quarkus.deployment.builditem.RunTimeConfigurationDefaultBuildItem;
import io.quarkus.deployment.builditem.ServiceStartBuildItem;
import io.quarkus.deployment.builditem.SystemPropertyBuildItem;
import io.quarkus.deployment.builditem.nativeimage.NativeImageProxyDefinitionBuildItem;
@@ -177,6 +180,55 @@ void includeArchivesHostingEntityPackagesInIndex(HibernateOrmConfig hibernateOrm
}
}
+ @BuildStep
+ void devServicesAutoGenerateByDefault(DevServicesLauncherConfigResultBuildItem devServicesResult,
+ List schemaReadyBuildItems,
+ HibernateOrmConfig config,
+ BuildProducer runTimeConfigurationDefaultBuildItemBuildProducer) {
+ if (!schemaReadyBuildItems.isEmpty()) {
+ //we don't want to enable auto generation if somebody else is managing the schema
+ return;
+ }
+ String dsName;
+ if (config.defaultPersistenceUnit.datasource.isEmpty()) {
+ dsName = "quarkus.datasource.username";
+ } else {
+ dsName = "quarkus.datasource." + config.defaultPersistenceUnit.datasource.get() + ".username";
+ }
+
+ if (ConfigProvider.getConfig().getOptionalValue(dsName, String.class).isEmpty()) {
+ if (devServicesResult.getConfig().containsKey(dsName)) {
+ if (ConfigProvider.getConfig().getOptionalValue("quarkus.hibernate-orm.database.generation", String.class)
+ .isEmpty()) {
+ LOG.info(
+ "Setting quarkus.hibernate-orm.database.generation=drop-and-create to initialize Dev Services managed database");
+ runTimeConfigurationDefaultBuildItemBuildProducer.produce(new RunTimeConfigurationDefaultBuildItem(
+ "quarkus.hibernate-orm.database.generation", "drop-and-create"));
+ }
+ }
+ }
+
+ for (Entry entry : config.persistenceUnits.entrySet()) {
+
+ if (entry.getValue().datasource.isEmpty()) {
+ dsName = "quarkus.datasource.jdbc.url";
+ } else {
+ dsName = "quarkus.datasource." + entry.getValue().datasource.get() + ".username";
+ }
+ if (ConfigProvider.getConfig().getOptionalValue(dsName, String.class).isEmpty()) {
+ if (devServicesResult.getConfig().containsKey(dsName)) {
+ String propertyName = "quarkus.hibernate-orm." + entry.getKey() + ".database.generation";
+ if (ConfigProvider.getConfig().getOptionalValue(propertyName, String.class).isEmpty()) {
+ LOG.info("Setting " + propertyName + "=drop-and-create to initialize Dev Services managed database");
+ runTimeConfigurationDefaultBuildItemBuildProducer
+ .produce(new RunTimeConfigurationDefaultBuildItem(propertyName, "drop-and-create"));
+ }
+ }
+ }
+ }
+
+ }
+
@BuildStep
AdditionalIndexedClassesBuildItem addPersistenceUnitAnnotationToIndex() {
return new AdditionalIndexedClassesBuildItem(ClassNames.QUARKUS_PERSISTENCE_UNIT.toString());
diff --git a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/HibernateHotReloadTestCase.java b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/HibernateHotReloadTestCase.java
index 64a86b95bfe2b1..52c1bb4fb2f018 100644
--- a/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/HibernateHotReloadTestCase.java
+++ b/extensions/hibernate-orm/deployment/src/test/java/io/quarkus/hibernate/orm/HibernateHotReloadTestCase.java
@@ -11,9 +11,9 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
+import io.quarkus.test.ContinuousTestingTestUtils;
+import io.quarkus.test.ContinuousTestingTestUtils.TestStatus;
import io.quarkus.test.QuarkusDevModeTest;
-import io.quarkus.vertx.http.deployment.devmode.tests.TestStatus;
-import io.quarkus.vertx.http.testrunner.ContinuousTestingTestUtils;
import io.restassured.RestAssured;
public class HibernateHotReloadTestCase {
diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/HibernateOrmRuntimeConfigPersistenceUnit.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/HibernateOrmRuntimeConfigPersistenceUnit.java
index 3ca13e90a43542..3be50c5a488342 100644
--- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/HibernateOrmRuntimeConfigPersistenceUnit.java
+++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/HibernateOrmRuntimeConfigPersistenceUnit.java
@@ -72,6 +72,9 @@ public static class HibernateOrmConfigPersistenceUnitDatabaseGeneration {
*
* `drop-and-create` is awesome in development mode.
*
+ * This defaults to 'none', however if Dev Services is in use and no other extensions that manage the schema are present
+ * this will default to 'drop-and-create'.
+ *
* Accepted values: `none`, `create`, `drop-and-create`, `drop`, `update`, `validate`.
*/
@ConfigItem(name = ConfigItem.PARENT, defaultValue = "none")
diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/devconsole/HibernateOrmDevConsoleInfoSupplier.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/devconsole/HibernateOrmDevConsoleInfoSupplier.java
index 2f16877db3326a..895182ae9e072c 100644
--- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/devconsole/HibernateOrmDevConsoleInfoSupplier.java
+++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/devconsole/HibernateOrmDevConsoleInfoSupplier.java
@@ -58,6 +58,12 @@ public static void pushPersistenceUnit(String persistenceUnitName,
}
}
+ public static void clearData() {
+ INSTANCE.persistenceUnits.clear();
+ INSTANCE.createDDLs.clear();
+ INSTANCE.dropDDLs.clear();
+ }
+
private static String generateDDL(SchemaExport.Action action, Metadata metadata, ServiceRegistry serviceRegistry,
String importFiles) {
SchemaExport schemaExport = new SchemaExport();
diff --git a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/devconsole/HibernateOrmDevConsoleIntegrator.java b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/devconsole/HibernateOrmDevConsoleIntegrator.java
index 59bb961e01c2cf..a7dae284eb97fe 100644
--- a/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/devconsole/HibernateOrmDevConsoleIntegrator.java
+++ b/extensions/hibernate-orm/runtime/src/main/java/io/quarkus/hibernate/orm/runtime/devconsole/HibernateOrmDevConsoleIntegrator.java
@@ -21,6 +21,6 @@ public void integrate(Metadata metadata, SessionFactoryImplementor sessionFactor
@Override
public void disintegrate(SessionFactoryImplementor sessionFactoryImplementor,
SessionFactoryServiceRegistry sessionFactoryServiceRegistry) {
- // Nothing to do
+ HibernateOrmDevConsoleInfoSupplier.clearData();
}
}
diff --git a/extensions/hibernate-validator/deployment/src/main/java/io/quarkus/hibernate/validator/deployment/HibernateValidatorProcessor.java b/extensions/hibernate-validator/deployment/src/main/java/io/quarkus/hibernate/validator/deployment/HibernateValidatorProcessor.java
index b854dcada8f552..aea65fd304db8d 100644
--- a/extensions/hibernate-validator/deployment/src/main/java/io/quarkus/hibernate/validator/deployment/HibernateValidatorProcessor.java
+++ b/extensions/hibernate-validator/deployment/src/main/java/io/quarkus/hibernate/validator/deployment/HibernateValidatorProcessor.java
@@ -49,6 +49,7 @@
import io.quarkus.arc.deployment.AutoAddScopeBuildItem;
import io.quarkus.arc.deployment.BeanArchiveIndexBuildItem;
import io.quarkus.arc.deployment.BeanContainerListenerBuildItem;
+import io.quarkus.arc.deployment.ConfigClassBuildItem;
import io.quarkus.arc.deployment.SyntheticBeanBuildItem;
import io.quarkus.arc.deployment.UnremovableBeanBuildItem;
import io.quarkus.arc.processor.BeanInfo;
@@ -62,11 +63,11 @@
import io.quarkus.deployment.annotations.ExecutionTime;
import io.quarkus.deployment.annotations.Record;
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
-import io.quarkus.deployment.builditem.ConfigClassBuildItem;
import io.quarkus.deployment.builditem.FeatureBuildItem;
import io.quarkus.deployment.builditem.HotDeploymentWatchedFileBuildItem;
import io.quarkus.deployment.builditem.ShutdownContextBuildItem;
import io.quarkus.deployment.builditem.nativeimage.NativeImageConfigBuildItem;
+import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveFieldBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveMethodBuildItem;
import io.quarkus.deployment.logging.LogCleanupFilterBuildItem;
@@ -349,9 +350,13 @@ NativeImageConfigBuildItem nativeImageConfig() {
}
@BuildStep
- ExceptionMapperBuildItem mapper() {
- return new ExceptionMapperBuildItem(ResteasyReactiveViolationExceptionMapper.class.getName(),
- ValidationException.class.getName(), Priorities.USER + 1, true);
+ void exceptionMapper(BuildProducer exceptionMapperProducer,
+ BuildProducer reflectiveClassProducer) {
+ exceptionMapperProducer.produce(new ExceptionMapperBuildItem(ResteasyReactiveViolationExceptionMapper.class.getName(),
+ ValidationException.class.getName(), Priorities.USER + 1, true));
+ reflectiveClassProducer.produce(
+ new ReflectiveClassBuildItem(true, true, ResteasyReactiveViolationExceptionMapper.ViolationReport.class,
+ ResteasyReactiveViolationExceptionMapper.ViolationReport.Violation.class));
}
private static void contributeBuiltinConstraints(Set builtinConstraints,
diff --git a/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/jaxrs/ResteasyReactiveContextLocaleResolver.java b/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/jaxrs/ResteasyReactiveContextLocaleResolver.java
index 38366401d34e95..9f4023f7186993 100644
--- a/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/jaxrs/ResteasyReactiveContextLocaleResolver.java
+++ b/extensions/hibernate-validator/runtime/src/main/java/io/quarkus/hibernate/validator/runtime/jaxrs/ResteasyReactiveContextLocaleResolver.java
@@ -18,6 +18,11 @@ public ResteasyReactiveContextLocaleResolver(HttpHeaders headers) {
@Override
protected HttpHeaders getHeaders() {
- return headers;
+ try {
+ headers.getLength(); // this forces the creation of the actual object which will fail if there is no request in flight
+ return headers;
+ } catch (IllegalStateException e) {
+ return null;
+ }
}
}
diff --git a/extensions/jdbc/jdbc-oracle/deployment/src/main/java/io/quarkus/jdbc/oracle/deployment/OracleMetadataOverrides.java b/extensions/jdbc/jdbc-oracle/deployment/src/main/java/io/quarkus/jdbc/oracle/deployment/OracleMetadataOverrides.java
new file mode 100644
index 00000000000000..3970870f72b534
--- /dev/null
+++ b/extensions/jdbc/jdbc-oracle/deployment/src/main/java/io/quarkus/jdbc/oracle/deployment/OracleMetadataOverrides.java
@@ -0,0 +1,105 @@
+package io.quarkus.jdbc.oracle.deployment;
+
+import io.quarkus.deployment.annotations.BuildProducer;
+import io.quarkus.deployment.annotations.BuildStep;
+import io.quarkus.deployment.builditem.nativeimage.ExcludeConfigBuildItem;
+import io.quarkus.deployment.builditem.nativeimage.NativeImageAllowIncompleteClasspathBuildItem;
+import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
+import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem;
+
+/**
+ * The Oracle JDBC driver includes a {@literal META-INF/native-image} which enables a set
+ * of global flags we need to control better, so to ensure such flags do not interfere
+ * with requirements of other libraries.
+ *
+ * For this reason, the {@literal META-INF/native-image/native-image.properties} resource
+ * is excluded explicitly; then we re-implement the equivalent directives using Quarkus
+ * build items.
+ *
+ * Other resources such as {@literal jni-config.json} and {@literal resource-config.json}
+ * are not excluded, so to ensure we match the recommendations from the Oracle JDBC
+ * engineering team and make it easier to pick up improvements in these when the driver
+ * gets updated.
+ *
+ * Regarding {@literal reflect-config.json}, we also prefer excluding it for the time
+ * being even though it's strictly not necessary: the reason is that the previous driver
+ * version had a build-breaking mistake; this was fixed in version 21.3 so should no
+ * longer be necessary, but the previous driver had been tested more widely and would
+ * require it, so this would facilitate the option to revert to the older version in
+ * case of problems.
+ */
+public final class OracleMetadataOverrides {
+
+ static final String DRIVER_JAR_MATCH_REGEX = ".*com\\.oracle\\.database\\.jdbc.*";
+ static final String NATIVE_IMAGE_RESOURCE_MATCH_REGEX = "/META-INF/native-image/(?:native-image\\.properties|reflect-config\\.json)";
+
+ /**
+ * Should match the contents of {@literal reflect-config.json}
+ *
+ * @param reflectiveClass builItem producer
+ */
+ @BuildStep
+ void build(BuildProducer reflectiveClass) {
+ //This is to match the Oracle metadata (which we excluded so that we can apply fixes):
+ reflectiveClass.produce(new ReflectiveClassBuildItem(true, true, false, "oracle.jdbc.internal.ACProxyable"));
+ reflectiveClass.produce(new ReflectiveClassBuildItem(true, false, false, "oracle.jdbc.driver.T4CDriverExtension"));
+ reflectiveClass.produce(new ReflectiveClassBuildItem(true, false, false, "oracle.jdbc.driver.T2CDriverExtension"));
+ reflectiveClass.produce(new ReflectiveClassBuildItem(true, false, false, "oracle.jdbc.driver.ShardingDriverExtension"));
+ reflectiveClass.produce(new ReflectiveClassBuildItem(true, false, false, "oracle.net.ano.Ano"));
+ reflectiveClass.produce(new ReflectiveClassBuildItem(true, false, false, "oracle.net.ano.AuthenticationService"));
+ reflectiveClass.produce(new ReflectiveClassBuildItem(true, false, false, "oracle.net.ano.DataIntegrityService"));
+ reflectiveClass.produce(new ReflectiveClassBuildItem(true, false, false, "oracle.net.ano.EncryptionService"));
+ reflectiveClass.produce(new ReflectiveClassBuildItem(true, false, false, "oracle.net.ano.SupervisorService"));
+ reflectiveClass.produce(new ReflectiveClassBuildItem(true, false, false, "oracle.jdbc.driver.Message11"));
+ reflectiveClass.produce(new ReflectiveClassBuildItem(true, false, true, "oracle.sql.TypeDescriptor"));
+ reflectiveClass.produce(new ReflectiveClassBuildItem(true, false, false, "oracle.sql.TypeDescriptorFactory"));
+ reflectiveClass.produce(new ReflectiveClassBuildItem(true, false, false, "oracle.sql.AnyDataFactory"));
+ reflectiveClass
+ .produce(new ReflectiveClassBuildItem(true, false, false, "com.sun.rowset.providers.RIOptimisticProvider"));
+ reflectiveClass.produce(new ReflectiveClassBuildItem(true, true, false, "oracle.jdbc.logging.annotations.Supports"));
+ reflectiveClass.produce(new ReflectiveClassBuildItem(true, true, false, "oracle.jdbc.logging.annotations.Feature"));
+ }
+
+ @BuildStep
+ void runtimeInitializeDriver(BuildProducer runtimeInitialized) {
+ //These re-implement all the "--initialize-at-build-time" arguments found in the native-image.properties :
+ runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.jdbc.OracleDriver"));
+ runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.jdbc.driver.OracleDriver"));
+ runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("java.sql.DriverManager"));
+ runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.jdbc.driver.LogicalConnection"));
+ runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.jdbc.pool.OraclePooledConnection"));
+ runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.jdbc.pool.OracleDataSource"));
+ runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.jdbc.datasource.impl.OracleDataSource"));
+ runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.jdbc.pool.OracleOCIConnectionPool"));
+ runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.jdbc.driver.OracleTimeoutThreadPerVM"));
+ runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.net.nt.TimeoutInterruptHandler"));
+ runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.jdbc.driver.HAManager"));
+ runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.net.nt.Clock"));
+ runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.net.nt.TcpMultiplexer"));
+ runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.net.nt.TcpMultiplexer"));
+ runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.net.nt.TcpMultiplexer$LazyHolder"));
+ runtimeInitialized
+ .produce(new RuntimeInitializedClassBuildItem("oracle.jdbc.driver.BlockSource$ThreadedCachingBlockSource"));
+ runtimeInitialized.produce(new RuntimeInitializedClassBuildItem(
+ "oracle.jdbc.driver.BlockSource$ThreadedCachingBlockSource$BlockReleaser"));
+ runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.jdbc.xa.client.OracleXADataSource"));
+ runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.jdbc.replay.OracleXADataSourceImpl"));
+ runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.jdbc.datasource.OracleXAConnection"));
+ runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.jdbc.xa.client.OracleXAConnection"));
+ runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.jdbc.xa.client.OracleXAHeteroConnection"));
+ runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.jdbc.driver.T4CXAConnection"));
+ runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.security.o5logon.O5Logon"));
+ }
+
+ @BuildStep
+ ExcludeConfigBuildItem excludeOracleDirectives() {
+ // Excludes both native-image.properties and reflect-config.json, which are reimplemented above
+ return new ExcludeConfigBuildItem(DRIVER_JAR_MATCH_REGEX, NATIVE_IMAGE_RESOURCE_MATCH_REGEX);
+ }
+
+ @BuildStep
+ NativeImageAllowIncompleteClasspathBuildItem naughtyDriver() {
+ return new NativeImageAllowIncompleteClasspathBuildItem("quarkus-jdbc-oracle");
+ }
+
+}
diff --git a/extensions/jdbc/jdbc-oracle/deployment/src/main/java/io/quarkus/jdbc/oracle/deployment/OracleReflections.java b/extensions/jdbc/jdbc-oracle/deployment/src/main/java/io/quarkus/jdbc/oracle/deployment/OracleReflections.java
index 53854430001ff2..118a24d4c1fddb 100644
--- a/extensions/jdbc/jdbc-oracle/deployment/src/main/java/io/quarkus/jdbc/oracle/deployment/OracleReflections.java
+++ b/extensions/jdbc/jdbc-oracle/deployment/src/main/java/io/quarkus/jdbc/oracle/deployment/OracleReflections.java
@@ -3,7 +3,6 @@
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
-import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem;
/**
* Registers the {@code oracle.jdbc.driver.OracleDriver} so that it can be loaded
@@ -24,22 +23,4 @@ void build(BuildProducer reflectiveClass) {
reflectiveClass.produce(new ReflectiveClassBuildItem(false, false, driverName));
}
- @BuildStep
- void runtimeInitializeDriver(BuildProducer runtimeInitialized) {
- runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.jdbc.driver.OracleDriver"));
- runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.jdbc.driver.SQLUtil$XMLFactory"));
- runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.jdbc.driver.NamedTypeAccessor$XMLFactory"));
- runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.jdbc.driver.OracleTimeoutThreadPerVM"));
- runtimeInitialized
- .produce(new RuntimeInitializedClassBuildItem("oracle.jdbc.driver.BlockSource$ThreadedCachingBlockSource"));
- runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.jdbc.driver.T4CTTIoauthenticate"));
- runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.net.nt.TcpMultiplexer$LazyHolder"));
- runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.security.o5logon.O5Logon"));
- runtimeInitialized.produce(new RuntimeInitializedClassBuildItem(
- "oracle.jdbc.driver.BlockSource$ThreadedCachingBlockSource$BlockReleaser"));
- runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.net.nt.TimeoutInterruptHandler"));
- runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.net.nt.Clock"));
- runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.jdbc.driver.NoSupportHAManager"));
- runtimeInitialized.produce(new RuntimeInitializedClassBuildItem("oracle.jdbc.driver.LogicalConnection"));
- }
}
diff --git a/extensions/jdbc/jdbc-oracle/deployment/src/test/java/io/quarkus/jdbc/oracle/deployment/RegexMatchTest.java b/extensions/jdbc/jdbc-oracle/deployment/src/test/java/io/quarkus/jdbc/oracle/deployment/RegexMatchTest.java
new file mode 100644
index 00000000000000..5a66b4d51b26ab
--- /dev/null
+++ b/extensions/jdbc/jdbc-oracle/deployment/src/test/java/io/quarkus/jdbc/oracle/deployment/RegexMatchTest.java
@@ -0,0 +1,45 @@
+package io.quarkus.jdbc.oracle.deployment;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.junit.jupiter.api.Test;
+
+import io.smallrye.common.constraint.Assert;
+
+/**
+ * The metadata override facility of GraalVM's native-image
+ * works with regular expressions.
+ * We're testing our expressions here to match against the
+ * constants the compiler is expecting (inferred by debugging
+ * the compiler) as it's otherwise a bit tricky to assert
+ * if they have been applied.
+ */
+public class RegexMatchTest {
+
+ @Test
+ public void jarRegexIsMatching() {
+ final String EXAMPLE_CLASSPATH = "/home/sanne/sources/quarkus/integration-tests/jpa-oracle/target/quarkus-integration-test-jpa-oracle-999-SNAPSHOT-native-image-source-jar/lib/com.oracle.database.jdbc.ojdbc11-21.3.0.0.jar";
+ final Pattern pattern = Pattern.compile(OracleMetadataOverrides.DRIVER_JAR_MATCH_REGEX);
+ final Matcher matcher = pattern.matcher(EXAMPLE_CLASSPATH);
+ Assert.assertTrue(matcher.find());
+ }
+
+ @Test
+ public void resourceRegexIsMatching() {
+ //We need to exclude both of these:
+ final String RES1 = "/META-INF/native-image/native-image.properties";
+ final String RES2 = "/META-INF/native-image/reflect-config.json";
+ final Pattern pattern = Pattern.compile(OracleMetadataOverrides.NATIVE_IMAGE_RESOURCE_MATCH_REGEX);
+
+ Assert.assertTrue(pattern.matcher(RES1).find());
+ Assert.assertTrue(pattern.matcher(RES2).find());
+
+ //While this one should NOT be ignored:
+ final String RES3 = "/META-INF/native-image/resource-config.json";
+ final String RES4 = "/META-INF/native-image/jni-config.json";
+ Assert.assertFalse(pattern.matcher(RES3).find());
+ Assert.assertFalse(pattern.matcher(RES4).find());
+ }
+
+}
diff --git a/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/DevServicesKafkaProcessor.java b/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/DevServicesKafkaProcessor.java
index 25d1eb9a06cc7f..00cc224903b825 100644
--- a/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/DevServicesKafkaProcessor.java
+++ b/extensions/kafka-client/deployment/src/main/java/io/quarkus/kafka/client/deployment/DevServicesKafkaProcessor.java
@@ -42,7 +42,10 @@
import io.quarkus.deployment.builditem.DevServicesConfigResultBuildItem;
import io.quarkus.deployment.builditem.DevServicesSharedNetworkBuildItem;
import io.quarkus.deployment.builditem.LaunchModeBuildItem;
+import io.quarkus.deployment.console.ConsoleInstalledBuildItem;
+import io.quarkus.deployment.console.StartupLogCompressor;
import io.quarkus.deployment.dev.devservices.GlobalDevServicesConfig;
+import io.quarkus.deployment.logging.LoggingSetupBuildItem;
import io.quarkus.devservices.common.ContainerAddress;
import io.quarkus.devservices.common.ContainerLocator;
import io.quarkus.runtime.LaunchMode;
@@ -76,7 +79,9 @@ public DevServicesKafkaBrokerBuildItem startKafkaDevService(
LaunchModeBuildItem launchMode,
KafkaBuildTimeConfig kafkaClientBuildTimeConfig,
Optional devServicesSharedNetworkBuildItem,
- BuildProducer devServicePropertiesProducer) {
+ BuildProducer devServicePropertiesProducer,
+ Optional consoleInstalledBuildItem,
+ LoggingSetupBuildItem loggingSetupBuildItem) {
KafkaDevServiceCfg configuration = getConfiguration(kafkaClientBuildTimeConfig);
@@ -88,14 +93,25 @@ public DevServicesKafkaBrokerBuildItem startKafkaDevService(
shutdownBroker();
cfg = null;
}
-
- KafkaBroker kafkaBroker = startKafka(configuration, launchMode, devServicesSharedNetworkBuildItem.isPresent());
- DevServicesKafkaBrokerBuildItem bootstrapServers = null;
- if (kafkaBroker != null) {
- closeable = kafkaBroker.getCloseable();
- devServicePropertiesProducer.produce(new DevServicesConfigResultBuildItem(
- KAFKA_BOOTSTRAP_SERVERS, kafkaBroker.getBootstrapServers()));
- bootstrapServers = new DevServicesKafkaBrokerBuildItem(kafkaBroker.getBootstrapServers());
+ KafkaBroker kafkaBroker;
+ DevServicesKafkaBrokerBuildItem bootstrapServers;
+ StartupLogCompressor compressor = new StartupLogCompressor(
+ (launchMode.isTest() ? "(test) " : "") + "Kafka Dev Services Starting:",
+ consoleInstalledBuildItem, loggingSetupBuildItem);
+ try {
+
+ kafkaBroker = startKafka(configuration, launchMode, devServicesSharedNetworkBuildItem.isPresent());
+ bootstrapServers = null;
+ if (kafkaBroker != null) {
+ closeable = kafkaBroker.getCloseable();
+ devServicePropertiesProducer.produce(new DevServicesConfigResultBuildItem(
+ KAFKA_BOOTSTRAP_SERVERS, kafkaBroker.getBootstrapServers()));
+ bootstrapServers = new DevServicesKafkaBrokerBuildItem(kafkaBroker.getBootstrapServers());
+ }
+ compressor.close();
+ } catch (Throwable t) {
+ compressor.closeAndDumpCaptured();
+ throw new RuntimeException(t);
}
// Configure the watch dog
@@ -361,6 +377,7 @@ protected void containerIsStarting(InspectContainerResponse containerInfo, boole
// Start and configure the advertised address
String command = "#!/bin/bash\n";
command += "/usr/bin/rpk redpanda start --check=false --node-id 0 --smp 1 ";
+ command += "--memory 1G --overprovisioned --reserve-memory 0M ";
command += "--kafka-addr PLAINTEXT://0.0.0.0:29092,OUTSIDE://0.0.0.0:9092 ";
command += String.format("--advertise-kafka-addr PLAINTEXT://%s:29092,OUTSIDE://%s:%d", getHostToUse(),
getHostToUse(), getPortToUse());
diff --git a/extensions/kubernetes-client/deployment-internal/src/main/java/io/quarkus/kubernetes/client/deployment/KubernetesClientErrorHandler.java b/extensions/kubernetes-client/deployment-internal/src/main/java/io/quarkus/kubernetes/client/deployment/KubernetesClientErrorHandler.java
index ed127a722a83bb..58f3eff0b634ef 100644
--- a/extensions/kubernetes-client/deployment-internal/src/main/java/io/quarkus/kubernetes/client/deployment/KubernetesClientErrorHandler.java
+++ b/extensions/kubernetes-client/deployment-internal/src/main/java/io/quarkus/kubernetes/client/deployment/KubernetesClientErrorHandler.java
@@ -12,11 +12,11 @@ public static void handle(Exception e) {
if (e.getCause() instanceof SSLHandshakeException) {
LOG.error(
"The application could not be deployed to the cluster because the Kubernetes API Server certificates are not trusted. The certificates can be configured using the relevant configuration properties under the 'quarkus.kubernetes-client' config root, or \"quarkus.kubernetes-client.trust-certs=true\" can be set to explicitly trust the certificates (not recommended)");
- if (e instanceof RuntimeException) {
- throw (RuntimeException) e;
- } else {
- throw new RuntimeException(e);
- }
+ }
+ if (e instanceof RuntimeException) {
+ throw (RuntimeException) e;
+ } else {
+ throw new RuntimeException(e);
}
}
}
diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesDeployer.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesDeployer.java
index c358fb29ea50d5..362b63ae063578 100644
--- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesDeployer.java
+++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesDeployer.java
@@ -11,11 +11,12 @@
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Path;
-import java.util.Collection;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@@ -25,6 +26,7 @@
import io.dekorate.utils.Serialization;
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.api.model.KubernetesList;
+import io.fabric8.kubernetes.api.model.Service;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClientException;
import io.fabric8.openshift.api.model.Route;
@@ -42,7 +44,6 @@
import io.quarkus.deployment.pkg.builditem.OutputTargetBuildItem;
import io.quarkus.kubernetes.client.deployment.KubernetesClientErrorHandler;
import io.quarkus.kubernetes.client.spi.KubernetesClientBuildItem;
-import io.quarkus.kubernetes.spi.KubernetesDeploymentTargetBuildItem;
public class KubernetesDeployer {
@@ -60,7 +61,7 @@ public void selectDeploymentTarget(ContainerImageInfoBuildItem containerImageInf
Optional activeContainerImageCapability = ContainerImageCapabilitiesUtil
.getActiveContainerImageCapability(capabilities);
- if (!activeContainerImageCapability.isPresent()) {
+ if (activeContainerImageCapability.isEmpty()) {
// we can't thrown an exception here, because it could prevent the Kubernetes resources from being generated
return;
}
@@ -85,10 +86,10 @@ public void deploy(KubernetesClientBuildItem kubernetesClient,
return;
}
- if (!selectedDeploymentTarget.isPresent()) {
+ if (selectedDeploymentTarget.isEmpty()) {
- if (!ContainerImageCapabilitiesUtil
- .getActiveContainerImageCapability(capabilities).isPresent()) {
+ if (ContainerImageCapabilitiesUtil
+ .getActiveContainerImageCapability(capabilities).isEmpty()) {
throw new RuntimeException(
"A Kubernetes deployment was requested but no extension was found to build a container image. Consider adding one of following extensions: "
+ CONTAINER_IMAGE_EXTENSIONS_STR + ".");
@@ -158,11 +159,6 @@ private DeploymentTargetEntry determineDeploymentTarget(
return selectedTarget;
}
- private Optional getOptionalDeploymentTarget(
- List deploymentTargets, String name) {
- return deploymentTargets.stream().filter(d -> name.equals(d.getName())).findFirst();
- }
-
private DeploymentResultBuildItem deploy(DeploymentTargetEntry deploymentTarget,
KubernetesClient client, Path outputDir,
OpenshiftConfig openshiftConfig, ApplicationInfoBuildItem applicationInfo) {
@@ -174,12 +170,13 @@ private DeploymentResultBuildItem deploy(DeploymentTargetEntry deploymentTarget,
try (FileInputStream fis = new FileInputStream(manifest)) {
KubernetesList list = Serialization.unmarshalAsList(fis);
- distinct(list.getItems()).forEach(i -> {
- if (KNATIVE.equals(deploymentTarget.getName().toLowerCase())) {
- client.resource(i).inNamespace(namespace).deletingExisting().createOrReplace();
- } else {
- client.resource(i).inNamespace(namespace).createOrReplace();
+ list.getItems().stream().filter(distinctByResourceKey()).forEach(i -> {
+ final var r = client.resource(i).inNamespace(namespace);
+ if (shouldDeleteExisting(deploymentTarget, i)) {
+ r.delete();
+ r.waitUntilCondition(Objects::isNull, 10, TimeUnit.SECONDS);
}
+ r.createOrReplace();
log.info("Applied: " + i.getKind() + " " + i.getMetadata().getName() + ".");
});
@@ -221,13 +218,15 @@ private void printExposeInformation(KubernetesClient client, KubernetesList list
}
}
- public static Predicate distictByResourceKey() {
+ private static boolean shouldDeleteExisting(DeploymentTargetEntry deploymentTarget, HasMetadata resource) {
+ return KNATIVE.equalsIgnoreCase(deploymentTarget.getName())
+ || resource instanceof Service
+ || (Objects.equals("v1", resource.getApiVersion()) && Objects.equals("Service", resource.getKind()));
+ }
+
+ private static Predicate distinctByResourceKey() {
Map