You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by ja...@apache.org on 2024/03/05 19:32:40 UTC
(camel-quarkus) 04/07: Support loadOnStartup, async, forceAwait & executorRef configuration options
This is an automated email from the ASF dual-hosted git repository.
jamesnetherton pushed a commit to branch 3.8.x
in repository https://gitbox.apache.org/repos/asf/camel-quarkus.git
commit e77cbd02a8cd6c947c12f1f93b5e2371bf42ad6e
Author: James Netherton <ja...@gmail.com>
AuthorDate: Mon Mar 4 15:50:48 2024 +0000
Support loadOnStartup, async, forceAwait & executorRef configuration options
Fixes #5834
---
.../ROOT/pages/reference/extensions/servlet.adoc | 48 ++++++++++++
.../servlet/deployment/ServletProcessor.java | 19 ++++-
.../servlet/runtime/CamelServletConfig.java | 27 +++++++
.../quarkus/component/servlet/CamelRoute.java | 34 +++++++--
.../component/servlet/ServletProducers.java | 71 +++++++++++++++++
.../src/main/resources/application.properties | 50 ++++++++----
.../component/servlet/CamelServletTest.java | 89 ++++++++++++++++++----
7 files changed, 304 insertions(+), 34 deletions(-)
diff --git a/docs/modules/ROOT/pages/reference/extensions/servlet.adoc b/docs/modules/ROOT/pages/reference/extensions/servlet.adoc
index ac9303ed92..848ac1299c 100644
--- a/docs/modules/ROOT/pages/reference/extensions/servlet.adoc
+++ b/docs/modules/ROOT/pages/reference/extensions/servlet.adoc
@@ -156,6 +156,30 @@ A servletName as it would be defined in a `web.xml` file or in the `jakarta.serv
| `string`
| `CamelServlet`
+|icon:lock[title=Fixed at build time] [[quarkus.camel.servlet.load-on-startup]]`link:#quarkus.camel.servlet.load-on-startup[quarkus.camel.servlet.load-on-startup]`
+
+Sets the loadOnStartup priority on the Servlet. A loadOnStartup is a value greater than or equal to zero, indicates to the container the initialization priority of the Servlet. If loadOnStartup is a negative integer, the Servlet is initialized lazily.
+| `java.lang.Integer`
+| `-1`
+
+|icon:lock[title=Fixed at build time] [[quarkus.camel.servlet.async]]`link:#quarkus.camel.servlet.async[quarkus.camel.servlet.async]`
+
+Enables Camel to benefit from asynchronous Servlet support.
+| `boolean`
+| `false`
+
+|icon:lock[title=Fixed at build time] [[quarkus.camel.servlet.force-await]]`link:#quarkus.camel.servlet.force-await[quarkus.camel.servlet.force-await]`
+
+When set to `true` used in conjunction with `quarkus.camel.servlet.async = true`, this will force route processing to run synchronously.
+| `boolean`
+| `false`
+
+|icon:lock[title=Fixed at build time] [[quarkus.camel.servlet.executor-ref]]`link:#quarkus.camel.servlet.executor-ref[quarkus.camel.servlet.executor-ref]`
+
+The name of a bean to configure an optional custom thread pool for handling Camel Servlet processing.
+| `string`
+|
+
|icon:lock[title=Fixed at build time] [[quarkus.camel.servlet.multipart.location]]`link:#quarkus.camel.servlet.multipart.location[quarkus.camel.servlet.multipart.location]`
An absolute path to a directory on the file system to store files temporarily while the parts are processed or when the size of the file exceeds the specified file-size-threshold configuration value.
@@ -198,6 +222,30 @@ A servletName as it would be defined in a `web.xml` file or in the `jakarta.serv
| `string`
| `CamelServlet`
+|icon:lock[title=Fixed at build time] [[quarkus.camel.servlet.-named-servlets-.load-on-startup]]`link:#quarkus.camel.servlet.-named-servlets-.load-on-startup[quarkus.camel.servlet."named-servlets".load-on-startup]`
+
+Sets the loadOnStartup priority on the Servlet. A loadOnStartup is a value greater than or equal to zero, indicates to the container the initialization priority of the Servlet. If loadOnStartup is a negative integer, the Servlet is initialized lazily.
+| `java.lang.Integer`
+| `-1`
+
+|icon:lock[title=Fixed at build time] [[quarkus.camel.servlet.-named-servlets-.async]]`link:#quarkus.camel.servlet.-named-servlets-.async[quarkus.camel.servlet."named-servlets".async]`
+
+Enables Camel to benefit from asynchronous Servlet support.
+| `boolean`
+| `false`
+
+|icon:lock[title=Fixed at build time] [[quarkus.camel.servlet.-named-servlets-.force-await]]`link:#quarkus.camel.servlet.-named-servlets-.force-await[quarkus.camel.servlet."named-servlets".force-await]`
+
+When set to `true` used in conjunction with `quarkus.camel.servlet.async = true`, this will force route processing to run synchronously.
+| `boolean`
+| `false`
+
+|icon:lock[title=Fixed at build time] [[quarkus.camel.servlet.-named-servlets-.executor-ref]]`link:#quarkus.camel.servlet.-named-servlets-.executor-ref[quarkus.camel.servlet."named-servlets".executor-ref]`
+
+The name of a bean to configure an optional custom thread pool for handling Camel Servlet processing.
+| `string`
+|
+
|icon:lock[title=Fixed at build time] [[quarkus.camel.servlet.-named-servlets-.multipart.location]]`link:#quarkus.camel.servlet.-named-servlets-.multipart.location[quarkus.camel.servlet."named-servlets".multipart.location]`
An absolute path to a directory on the file system to store files temporarily while the parts are processed or when the size of the file exceeds the specified file-size-threshold configuration value.
diff --git a/extensions/servlet/deployment/src/main/java/org/apache/camel/quarkus/component/servlet/deployment/ServletProcessor.java b/extensions/servlet/deployment/src/main/java/org/apache/camel/quarkus/component/servlet/deployment/ServletProcessor.java
index 7e21a43adb..6afaf14202 100644
--- a/extensions/servlet/deployment/src/main/java/org/apache/camel/quarkus/component/servlet/deployment/ServletProcessor.java
+++ b/extensions/servlet/deployment/src/main/java/org/apache/camel/quarkus/component/servlet/deployment/ServletProcessor.java
@@ -63,7 +63,6 @@ class ServletProcessor {
throw new IllegalStateException(
"Map at least one servlet to a path using quarkus.camel.servlet.url-patterns or quarkus.camel.servlet.[your-servlet-name].url-patterns");
}
-
}
static ServletBuildItem newServlet(String key, ServletConfig servletConfig) {
@@ -80,6 +79,24 @@ class ServletProcessor {
builder.addMapping(pattern);
}
+ // NOTE: We only configure loadOnStartup, async & forceAwait if the default values were overridden
+ if (servletConfig.loadOnStartup > -1) {
+ builder.setLoadOnStartup(servletConfig.loadOnStartup);
+ }
+
+ if (servletConfig.async) {
+ builder.setAsyncSupported(servletConfig.async);
+ builder.addInitParam("async", "true");
+ }
+
+ if (servletConfig.forceAwait) {
+ builder.addInitParam("forceAwait", "true");
+ }
+
+ servletConfig.executorRef.ifPresent(executorRef -> {
+ builder.addInitParam("executorRef", executorRef);
+ });
+
MultipartConfig multipartConfig = servletConfig.multipart;
if (multipartConfig != null) {
builder.setMultipartConfig(new MultipartConfigElement(
diff --git a/extensions/servlet/runtime/src/main/java/org/apache/camel/quarkus/servlet/runtime/CamelServletConfig.java b/extensions/servlet/runtime/src/main/java/org/apache/camel/quarkus/servlet/runtime/CamelServletConfig.java
index 62ac5c7131..ff20269995 100644
--- a/extensions/servlet/runtime/src/main/java/org/apache/camel/quarkus/servlet/runtime/CamelServletConfig.java
+++ b/extensions/servlet/runtime/src/main/java/org/apache/camel/quarkus/servlet/runtime/CamelServletConfig.java
@@ -64,6 +64,33 @@ public final class CamelServletConfig {
@ConfigItem(defaultValue = DEFAULT_SERVLET_NAME)
public String servletName;
+ /**
+ * Sets the loadOnStartup priority on the Servlet. A loadOnStartup is a value greater than or equal to zero,
+ * indicates to the container the initialization priority of the Servlet. If loadOnStartup is a negative
+ * integer, the Servlet is initialized lazily.
+ */
+ @ConfigItem(defaultValue = "-1")
+ public Integer loadOnStartup;
+
+ /**
+ * Enables Camel to benefit from asynchronous Servlet support.
+ */
+ @ConfigItem(defaultValue = "false")
+ public boolean async;
+
+ /**
+ * When set to {@code true} used in conjunction with {@code quarkus.camel.servlet.async = true}, this will force
+ * route processing to run synchronously.
+ */
+ @ConfigItem(defaultValue = "false")
+ public boolean forceAwait;
+
+ /**
+ * The name of a bean to configure an optional custom thread pool for handling Camel Servlet processing.
+ */
+ @ConfigItem
+ public Optional<String> executorRef;
+
/**
* Servlet multipart request configuration.
*/
diff --git a/integration-tests/servlet/src/main/java/org/apache/camel/quarkus/component/servlet/CamelRoute.java b/integration-tests/servlet/src/main/java/org/apache/camel/quarkus/component/servlet/CamelRoute.java
index aabc992357..6c8c117c35 100644
--- a/integration-tests/servlet/src/main/java/org/apache/camel/quarkus/component/servlet/CamelRoute.java
+++ b/integration-tests/servlet/src/main/java/org/apache/camel/quarkus/component/servlet/CamelRoute.java
@@ -18,6 +18,8 @@ package org.apache.camel.quarkus.component.servlet;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
+import jakarta.inject.Named;
+import org.apache.camel.Processor;
import org.apache.camel.builder.RouteBuilder;
@ApplicationScoped
@@ -25,6 +27,10 @@ public class CamelRoute extends RouteBuilder {
@Inject
MultiPartProcessor multiPartProcessor;
+ @Inject
+ @Named("servletConfigInfoProcessor")
+ Processor servletConfigInfoProcessor;
+
@Override
public void configure() {
// by default the camel-quarkus-rest component sets platform-http
@@ -43,11 +49,14 @@ public class CamelRoute extends RouteBuilder {
from("servlet://hello?matchOnUriPrefix=true")
.setBody(constant("GET: /hello"));
- from("servlet://custom?servletName=my-named-servlet")
+ from("servlet://configuration")
+ .process(servletConfigInfoProcessor);
+
+ from("servlet://custom?servletName=custom-servlet")
.setBody(constant("GET: /custom"));
- from("servlet://favorite?servletName=my-favorite-servlet")
- .setBody(constant("GET: /favorite"));
+ from("servlet://named?servletName=my-named-servlet")
+ .setBody(constant("GET: /my-named-servlet"));
from("direct:echoMethodPath")
.setBody().simple("${header.CamelHttpMethod}: ${header.CamelServletContextPath}");
@@ -55,8 +64,23 @@ public class CamelRoute extends RouteBuilder {
from("servlet://multipart/default?attachmentMultipartBinding=true")
.process(multiPartProcessor);
- from("servlet://multipart?servletName=my-named-servlet&attachmentMultipartBinding=true")
+ from("servlet://multipart?servletName=multipart-servlet&attachmentMultipartBinding=true")
.process(multiPartProcessor);
- }
+ from("servlet://eager-init?servletName=eager-init-servlet&matchOnUriPrefix=true")
+ .setHeader("servletName").constant("eager-init-servlet")
+ .process(servletConfigInfoProcessor);
+
+ from("servlet://async?servletName=async-servlet&matchOnUriPrefix=true")
+ .setHeader("servletName").constant("async-servlet")
+ .process(servletConfigInfoProcessor);
+
+ from("servlet://force-await?servletName=sync-async-servlet&matchOnUriPrefix=true")
+ .setHeader("servletName").constant("sync-async-servlet")
+ .process(servletConfigInfoProcessor);
+
+ from("servlet://execute?servletName=custom-executor-servlet&matchOnUriPrefix=true")
+ .setHeader("servletName").constant("custom-executor-servlet")
+ .process(servletConfigInfoProcessor);
+ }
}
diff --git a/integration-tests/servlet/src/main/java/org/apache/camel/quarkus/component/servlet/ServletProducers.java b/integration-tests/servlet/src/main/java/org/apache/camel/quarkus/component/servlet/ServletProducers.java
new file mode 100644
index 0000000000..292be43a1e
--- /dev/null
+++ b/integration-tests/servlet/src/main/java/org/apache/camel/quarkus/component/servlet/ServletProducers.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.quarkus.component.servlet;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+import io.undertow.servlet.api.Deployment;
+import io.undertow.servlet.api.ServletInfo;
+import io.undertow.servlet.core.ManagedServlet;
+import io.undertow.servlet.core.ManagedServlets;
+import io.undertow.servlet.spec.ServletContextImpl;
+import io.vertx.core.json.JsonObject;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Named;
+import jakarta.inject.Singleton;
+import jakarta.servlet.ServletContext;
+import jakarta.servlet.http.HttpServletRequest;
+import org.apache.camel.Exchange;
+import org.apache.camel.Message;
+import org.apache.camel.Processor;
+import org.apache.camel.spi.MimeType;
+
+import static org.apache.camel.quarkus.servlet.runtime.CamelServletConfig.ServletConfig.DEFAULT_SERVLET_NAME;
+
+@ApplicationScoped
+public class ServletProducers {
+ @Singleton
+ @Named("customServletExecutor")
+ public Executor customServletExecutor() {
+ return Executors.newSingleThreadExecutor(r -> new Thread(r, "custom-executor"));
+ }
+
+ @Singleton
+ @Named("servletConfigInfoProcessor")
+ public Processor servletConfigInfoProcessor() {
+ return exchange -> {
+ JsonObject json = new JsonObject();
+ Message message = exchange.getMessage();
+ HttpServletRequest request = message.getHeader(Exchange.HTTP_SERVLET_REQUEST, HttpServletRequest.class);
+ String servletName = message.getHeader("servletName", DEFAULT_SERVLET_NAME, String.class);
+ ServletContext servletContext = request.getServletContext();
+ Deployment deployment = ((ServletContextImpl) servletContext).getDeployment();
+ ManagedServlets servlets = deployment.getServlets();
+ ManagedServlet servlet = servlets.getManagedServlet(servletName);
+ ServletInfo servletInfo = servlet.getServletInfo();
+
+ json.put("isAsync", request.isAsyncSupported());
+ json.put("threadName", Thread.currentThread().getName());
+ json.put("loadOnStartup", servletInfo.getLoadOnStartup());
+ json.put("initParams", servletInfo.getInitParams());
+
+ message.setHeader(Exchange.CONTENT_TYPE, MimeType.JSON.type());
+ message.setBody(json.encode());
+ };
+ }
+}
diff --git a/integration-tests/servlet/src/main/resources/application.properties b/integration-tests/servlet/src/main/resources/application.properties
index 0251262894..06920af73f 100644
--- a/integration-tests/servlet/src/main/resources/application.properties
+++ b/integration-tests/servlet/src/main/resources/application.properties
@@ -15,17 +15,39 @@
## limitations under the License.
## ---------------------------------------------------------------------------
-#
-# Quarkus :: Camel :: Servlet
-#
-quarkus.camel.servlet.url-patterns=/folder-1/*,/folder-2/*
-
-quarkus.camel.servlet.my-named-servlet.url-patterns=/my-named-folder/*
-quarkus.camel.servlet.my-named-servlet.servlet-class=org.apache.camel.quarkus.component.servlet.CustomServlet
-quarkus.camel.servlet.my-named-servlet.multipart.location=${java.io.tmpdir}/my-named-servlet
-quarkus.camel.servlet.my-named-servlet.multipart.max-file-size=11
-quarkus.camel.servlet.my-named-servlet.multipart.max-request-size=11
-quarkus.camel.servlet.my-named-servlet.multipart.file-size-threshold=5
-
-quarkus.camel.servlet.ignored-key.servlet-name=my-favorite-servlet
-quarkus.camel.servlet.ignored-key.url-patterns=/my-favorite-folder/*
+# Default servlet configuration
+quarkus.camel.servlet.url-patterns=/folder-1/*,/folder-2/*,/debug/*
+
+# Explicit definition of the servlet name
+quarkus.camel.servlet.ignored-key.servlet-name=my-named-servlet
+quarkus.camel.servlet.ignored-key.url-patterns=/my-named-folder/*
+
+# Custom servlet class
+quarkus.camel.servlet.custom-servlet.url-patterns=/my-custom-folder/*
+quarkus.camel.servlet.custom-servlet.servlet-class=org.apache.camel.quarkus.component.servlet.CustomServlet
+
+# Servlet configured with multipart support
+quarkus.camel.servlet.multipart-servlet.url-patterns=/multipart-servlet/*
+quarkus.camel.servlet.multipart-servlet.multipart.location=${java.io.tmpdir}/my-named-servlet
+quarkus.camel.servlet.multipart-servlet.multipart.max-file-size=11
+quarkus.camel.servlet.multipart-servlet.multipart.max-request-size=11
+quarkus.camel.servlet.multipart-servlet.multipart.file-size-threshold=5
+
+# Servlet configured for eager initialization
+quarkus.camel.servlet.eager-init-servlet.url-patterns=/eager-init-servlet/*
+quarkus.camel.servlet.eager-init-servlet.load-on-startup=1
+
+# Servlet configured for async processing
+quarkus.camel.servlet.async-servlet.url-patterns=/async-servlet/*
+quarkus.camel.servlet.async-servlet.async=true
+
+# Servlet configured for async + sync processing
+quarkus.camel.servlet.sync-async-servlet.url-patterns=/sync-async-servlet/*
+quarkus.camel.servlet.sync-async-servlet.async=true
+quarkus.camel.servlet.sync-async-servlet.force-await=true
+
+# Servlet with custom executor
+quarkus.camel.servlet.custom-executor-servlet.url-patterns=/custom-executor/*
+quarkus.camel.servlet.custom-executor-servlet.async=true
+quarkus.camel.servlet.custom-executor-servlet.executor-ref=customServletExecutor
+
diff --git a/integration-tests/servlet/src/test/java/org/apache/camel/quarkus/component/servlet/CamelServletTest.java b/integration-tests/servlet/src/test/java/org/apache/camel/quarkus/component/servlet/CamelServletTest.java
index 593e56cb9b..73bdd78dbe 100644
--- a/integration-tests/servlet/src/test/java/org/apache/camel/quarkus/component/servlet/CamelServletTest.java
+++ b/integration-tests/servlet/src/test/java/org/apache/camel/quarkus/component/servlet/CamelServletTest.java
@@ -20,36 +20,52 @@ import java.nio.charset.StandardCharsets;
import io.quarkus.test.junit.QuarkusTest;
import io.restassured.RestAssured;
-import org.hamcrest.core.IsEqual;
import org.junit.jupiter.api.Test;
+import static org.hamcrest.Matchers.anEmptyMap;
import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.Matchers.oneOf;
+import static org.hamcrest.Matchers.startsWith;
+import static org.hamcrest.core.IsEqual.equalTo;
@QuarkusTest
public class CamelServletTest {
+ @Test
+ public void defaultConfiguration() {
+ RestAssured.get("/debug/configuration")
+ .then()
+ .body(
+ "isAsync", equalTo(false),
+ "threadName", startsWith("executor-thread"),
+ "initParams", anEmptyMap(),
+ "loadOnStartup", nullValue());
+ }
@Test
public void multiplePaths() {
- RestAssured.when().get("/folder-1/rest-get").then().body(IsEqual.equalTo("GET: /rest-get"));
- RestAssured.when().get("/folder-2/rest-get").then().body(IsEqual.equalTo("GET: /rest-get"));
- RestAssured.when().post("/folder-1/rest-post").then().body(IsEqual.equalTo("POST: /rest-post"));
- RestAssured.when().post("/folder-2/rest-post").then().body(IsEqual.equalTo("POST: /rest-post"));
- RestAssured.when().get("/folder-1/hello").then().body(IsEqual.equalTo("GET: /hello"));
- RestAssured.when().get("/folder-2/hello").then().body(IsEqual.equalTo("GET: /hello"));
+ RestAssured.get("/folder-1/rest-get").then().body(equalTo("GET: /rest-get"));
+ RestAssured.get("/folder-2/rest-get").then().body(equalTo("GET: /rest-get"));
+ RestAssured.post("/folder-1/rest-post").then().body(equalTo("POST: /rest-post"));
+ RestAssured.post("/folder-2/rest-post").then().body(equalTo("POST: /rest-post"));
+ RestAssured.get("/folder-1/hello").then().body(equalTo("GET: /hello"));
+ RestAssured.get("/folder-2/hello").then().body(equalTo("GET: /hello"));
}
@Test
- public void namedWithservletClass() {
- RestAssured.when().get("/my-named-folder/custom").then()
- .body(IsEqual.equalTo("GET: /custom"))
- .and().header("x-servlet-class-name", CustomServlet.class.getName());
+ public void namedWithServletClass() {
+ RestAssured.get("/my-custom-folder/custom")
+ .then()
+ .body(equalTo("GET: /custom"))
+ .and()
+ .header("x-servlet-class-name", CustomServlet.class.getName());
}
@Test
public void ignoredKey() {
- RestAssured.when().get("/my-favorite-folder/favorite").then()
- .body(IsEqual.equalTo("GET: /favorite"));
+ RestAssured.get("/my-named-folder/named")
+ .then()
+ .body(equalTo("GET: /my-named-servlet"));
}
@Test
@@ -76,9 +92,54 @@ public class CamelServletTest {
// Request body exceeding the limits defined on the multipart config
RestAssured.given()
.multiPart("test-multipart", "file", body.repeat(10).getBytes(StandardCharsets.UTF_8))
- .post("/my-named-folder/multipart")
+ .post("/multipart-servlet/multipart")
.then()
// TODO: Expect 413 only - https://github.com/apache/camel-quarkus/issues/5830
.statusCode(oneOf(413, 500));
}
+
+ @Test
+ public void eagerInitServlet() {
+ RestAssured.get("/eager-init-servlet/eager-init")
+ .then()
+ .body(
+ "isAsync", equalTo(false),
+ "threadName", startsWith("executor-thread"),
+ "initParams", anEmptyMap(),
+ "loadOnStartup", equalTo(1));
+ }
+
+ @Test
+ public void asyncServlet() {
+ RestAssured.get("/async-servlet/async")
+ .then()
+ .body(
+ "isAsync", equalTo(true),
+ "threadName", startsWith("executor-thread"),
+ "initParams.async", equalTo("true"),
+ "loadOnStartup", nullValue());
+ }
+
+ @Test
+ public void asyncWithForceAwaitServlet() {
+ RestAssured.get("/sync-async-servlet/force-await")
+ .then()
+ .body(
+ "isAsync", equalTo(true),
+ "threadName", startsWith("executor-thread"),
+ "initParams.async", equalTo("true"),
+ "initParams.forceAwait", equalTo("true"),
+ "loadOnStartup", nullValue());
+ }
+
+ @Test
+ public void asyncWithCustomExecutor() {
+ RestAssured.get("/custom-executor/execute/get")
+ .then()
+ .body(
+ "isAsync", equalTo(true),
+ "threadName", equalTo("custom-executor"),
+ "initParams.async", equalTo("true"),
+ "loadOnStartup", nullValue());
+ }
}