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 15:27:52 UTC

(camel-quarkus) branch main updated (3002b27ae0 -> f7abb3e982)

This is an automated email from the ASF dual-hosted git repository.

jamesnetherton pushed a change to branch main
in repository https://gitbox.apache.org/repos/asf/camel-quarkus.git


    from 3002b27ae0 Bump quarkiverse-pooled-jms.version from 2.3.0 to 2.3.1 (#5836)
     new b71bca76be Document basic servlet extension usage and configuration options
     new 4877f4e365 Support loadOnStartup, async, forceAwait & executorRef configuration options
     new d04332fed2 Enable web.xml to be used to configure CamelHttpTransportServlet for the servlet extension
     new f7abb3e982 Add additional Servlet test coverage

The 4 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../ROOT/pages/reference/extensions/servlet.adoc   | 158 +++++++++++++++++++
 .../servlet/deployment/ServletProcessor.java       |  33 +++-
 ...nimalConfigTest.java => WebXmlServletTest.java} |  55 +++----
 extensions/servlet/runtime/src/main/doc/usage.adoc | 103 ++++++++++++
 .../servlet/runtime/CamelServletConfig.java        |  27 ++++
 .../quarkus/component/servlet/CamelRoute.java      |  60 ++++++-
 .../component/servlet}/CustomException.java        |  11 +-
 .../component/servlet/ServletProducers.java        |  71 +++++++++
 .../src/main/resources/application.properties      |  56 +++++--
 .../component/servlet/CamelServletTest.java        | 172 +++++++++++++++++++--
 10 files changed, 673 insertions(+), 73 deletions(-)
 copy extensions/servlet/deployment/src/test/java/org/apache/camel/quarkus/component/servlet/test/{MinimalConfigTest.java => WebXmlServletTest.java} (59%)
 create mode 100644 extensions/servlet/runtime/src/main/doc/usage.adoc
 copy integration-tests/{main-yaml/src/main/java/org/apache/camel/quarkus/main => servlet/src/main/java/org/apache/camel/quarkus/component/servlet}/CustomException.java (83%)
 create mode 100644 integration-tests/servlet/src/main/java/org/apache/camel/quarkus/component/servlet/ServletProducers.java


(camel-quarkus) 02/04: Support loadOnStartup, async, forceAwait & executorRef configuration options

Posted by ja...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jamesnetherton pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-quarkus.git

commit 4877f4e36568d81fb050ce5618977de171b1fb83
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());
+    }
 }


(camel-quarkus) 01/04: Document basic servlet extension usage and configuration options

Posted by ja...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jamesnetherton pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-quarkus.git

commit b71bca76be83132c80a82d30a08af7ef002a7ba4
Author: James Netherton <ja...@gmail.com>
AuthorDate: Tue Mar 5 07:52:43 2024 +0000

    Document basic servlet extension usage and configuration options
---
 .../ROOT/pages/reference/extensions/servlet.adoc   | 73 ++++++++++++++++++++++
 extensions/servlet/runtime/src/main/doc/usage.adoc | 66 +++++++++++++++++++
 2 files changed, 139 insertions(+)

diff --git a/docs/modules/ROOT/pages/reference/extensions/servlet.adoc b/docs/modules/ROOT/pages/reference/extensions/servlet.adoc
index fd56d2364a..ac9303ed92 100644
--- a/docs/modules/ROOT/pages/reference/extensions/servlet.adoc
+++ b/docs/modules/ROOT/pages/reference/extensions/servlet.adoc
@@ -45,6 +45,79 @@ ifeval::[{doc-show-user-guide-link} == true]
 Check the xref:user-guide/index.adoc[User guide] for more information about writing Camel Quarkus applications.
 endif::[]
 
+[id="extensions-servlet-usage"]
+== Usage
+[id="extensions-servlet-usage-configuring-camelhttptransportservlet"]
+=== Configuring CamelHttpTransportServlet
+
+[id="extensions-servlet-usage-minimal-configuration"]
+==== Minimal configuration
+
+The simplest way to configure `CamelHttpTransportServlet` is with configuration properties.
+The most minimal setup requires that you define one or more URL patterns for the Servlet with `quarkus.camel.servlet.url-patterns`.
+
+For example with configuration like the following.
+
+[source,properties]
+----
+quarkus.camel.servlet.url-patterns = /*
+----
+
+And a Camel route.
+
+[source,java]
+----
+from("servlet://greet")
+    .setBody().constant("Hello World");
+----
+
+Produces the message `Hello World`.
+
+[id="extensions-servlet-usage-advanced-configuration"]
+==== Advanced configuration
+
+*Servlet name*
+
+To give a specific name to the Servlet you can use the `quarkus.camel.servlet.servlet-name` configuration option.
+
+[source,properties]
+----
+quarkus.camel.servlet.url-patterns = My Custom Name
+----
+
+*Servlet class*
+
+You may use a custom Servlet class (E.g one that extends `CamelHttpTransportServlet`) in your Camel routes.
+
+[source,properties]
+----
+quarkus.camel.servlet.servlet-class = org.acme.MyCustomServlet
+----
+
+*Multiple named Servlets*
+
+For more advanced use cases you can configure multiple 'named' Servlets.
+
+[source,properties]
+----
+quarkus.camel.servlet.my-servlet-a.servlet-name = my-custom-a
+quarkus.camel.servlet.my-servlet-a.url-patterns = /custom/a/*
+
+quarkus.camel.servlet.my-servlet-b.servlet-name = my-custom-b
+quarkus.camel.servlet.my-servlet-b.servlet-class = org.acme.CustomServletB
+quarkus.camel.servlet.my-servlet-b.url-patterns = /custom/b/*
+----
+
+[source,java]
+----
+from("servlet://greet?servletName=my-custom-a")
+    .setBody().constant("Hello World");
+
+from("servlet://goodbye?servletName=my-custom-b")
+    .setBody().constant("Goodbye World");
+----
+
+
 [id="extensions-servlet-transferexception-option-in-native-mode"]
 == transferException option in native mode
 
diff --git a/extensions/servlet/runtime/src/main/doc/usage.adoc b/extensions/servlet/runtime/src/main/doc/usage.adoc
new file mode 100644
index 0000000000..7ad44a268c
--- /dev/null
+++ b/extensions/servlet/runtime/src/main/doc/usage.adoc
@@ -0,0 +1,66 @@
+=== Configuring CamelHttpTransportServlet
+
+==== Minimal configuration
+
+The simplest way to configure `CamelHttpTransportServlet` is with configuration properties.
+The most minimal setup requires that you define one or more URL patterns for the Servlet with `quarkus.camel.servlet.url-patterns`.
+
+For example with configuration like the following.
+
+[source,properties]
+----
+quarkus.camel.servlet.url-patterns = /*
+----
+
+And a Camel route.
+
+[source,java]
+----
+from("servlet://greet")
+    .setBody().constant("Hello World");
+----
+
+Produces the message `Hello World`.
+
+==== Advanced configuration
+
+*Servlet name*
+
+To give a specific name to the Servlet you can use the `quarkus.camel.servlet.servlet-name` configuration option.
+
+[source,properties]
+----
+quarkus.camel.servlet.url-patterns = My Custom Name
+----
+
+*Servlet class*
+
+You may use a custom Servlet class (E.g one that extends `CamelHttpTransportServlet`) in your Camel routes.
+
+[source,properties]
+----
+quarkus.camel.servlet.servlet-class = org.acme.MyCustomServlet
+----
+
+*Multiple named Servlets*
+
+For more advanced use cases you can configure multiple 'named' Servlets.
+
+[source,properties]
+----
+quarkus.camel.servlet.my-servlet-a.servlet-name = my-custom-a
+quarkus.camel.servlet.my-servlet-a.url-patterns = /custom/a/*
+
+quarkus.camel.servlet.my-servlet-b.servlet-name = my-custom-b
+quarkus.camel.servlet.my-servlet-b.servlet-class = org.acme.CustomServletB
+quarkus.camel.servlet.my-servlet-b.url-patterns = /custom/b/*
+----
+
+[source,java]
+----
+from("servlet://greet?servletName=my-custom-a")
+    .setBody().constant("Hello World");
+
+from("servlet://goodbye?servletName=my-custom-b")
+    .setBody().constant("Goodbye World");
+----


(camel-quarkus) 04/04: Add additional Servlet test coverage

Posted by ja...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jamesnetherton pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-quarkus.git

commit f7abb3e9827cd03da6f83bb403a0855ba494cd11
Author: James Netherton <ja...@gmail.com>
AuthorDate: Tue Mar 5 09:48:35 2024 +0000

    Add additional Servlet test coverage
---
 .../quarkus/component/servlet/CamelRoute.java      | 26 +++++-
 .../quarkus/component/servlet/CustomException.java | 28 +++++++
 .../src/main/resources/application.properties      |  6 ++
 .../component/servlet/CamelServletTest.java        | 97 ++++++++++++++++++++--
 4 files changed, 148 insertions(+), 9 deletions(-)

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 6c8c117c35..2ca381f295 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
@@ -44,10 +44,34 @@ public class CamelRoute extends RouteBuilder {
                 .to("direct:echoMethodPath")
 
                 .post("/rest-post")
+                .to("direct:echoMethodPath")
+
+                .put("/rest-put")
+                .to("direct:echoMethodPath")
+
+                .patch("/rest-patch")
+                .to("direct:echoMethodPath")
+
+                .delete("/rest-delete")
+                .to("direct:echoMethodPath")
+
+                .head("/rest-head")
                 .to("direct:echoMethodPath");
 
         from("servlet://hello?matchOnUriPrefix=true")
-                .setBody(constant("GET: /hello"));
+                .to("direct:echoMethodPath");
+
+        from("servlet://options?servletName=options-method-servlet&optionsEnabled=true")
+                .to("direct:echoMethodPath");
+
+        from("servlet://trace?servletName=trace-method-servlet&traceEnabled=true")
+                .to("direct:echoMethodPath");
+
+        from("servlet://transfer/exception?transferException=true&muteException=false")
+                .throwException(new CustomException());
+
+        from("servlet://params")
+                .setBody().simple("${header.prefix} ${header.suffix}");
 
         from("servlet://configuration")
                 .process(servletConfigInfoProcessor);
diff --git a/integration-tests/servlet/src/main/java/org/apache/camel/quarkus/component/servlet/CustomException.java b/integration-tests/servlet/src/main/java/org/apache/camel/quarkus/component/servlet/CustomException.java
new file mode 100644
index 0000000000..33fb3db78d
--- /dev/null
+++ b/integration-tests/servlet/src/main/java/org/apache/camel/quarkus/component/servlet/CustomException.java
@@ -0,0 +1,28 @@
+/*
+ * 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 io.quarkus.runtime.annotations.RegisterForReflection;
+
+@RegisterForReflection(serialization = true)
+public class CustomException extends Exception {
+    public static final String MESSAGE = "Something went wrong";
+
+    public CustomException() {
+        super(MESSAGE);
+    }
+}
diff --git a/integration-tests/servlet/src/main/resources/application.properties b/integration-tests/servlet/src/main/resources/application.properties
index 06920af73f..76be5f65b3 100644
--- a/integration-tests/servlet/src/main/resources/application.properties
+++ b/integration-tests/servlet/src/main/resources/application.properties
@@ -15,6 +15,9 @@
 ## limitations under the License.
 ## ---------------------------------------------------------------------------
 
+# For transferException
+quarkus.camel.native.reflection.serialization-enabled=true
+
 # Default servlet configuration
 quarkus.camel.servlet.url-patterns=/folder-1/*,/folder-2/*,/debug/*
 
@@ -51,3 +54,6 @@ 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
 
+# Servlet to allow OPTIONS & TRACE
+quarkus.camel.servlet.options-method-servlet.url-patterns=/method-options/*
+quarkus.camel.servlet.trace-method-servlet.url-patterns=/method-trace/*
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 73bdd78dbe..b87966872b 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
@@ -16,11 +16,18 @@
  */
 package org.apache.camel.quarkus.component.servlet;
 
+import java.io.IOException;
+import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
 
 import io.quarkus.test.junit.QuarkusTest;
 import io.restassured.RestAssured;
+import org.apache.camel.CamelContext;
+import org.apache.camel.http.common.HttpHelper;
+import org.apache.camel.impl.DefaultCamelContext;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
 
 import static org.hamcrest.Matchers.anEmptyMap;
 import static org.hamcrest.Matchers.is;
@@ -28,6 +35,9 @@ 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;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
 
 @QuarkusTest
 public class CamelServletTest {
@@ -42,22 +52,72 @@ public class CamelServletTest {
                         "loadOnStartup", nullValue());
     }
 
+    @ParameterizedTest
+    @ValueSource(strings = { "GET", "POST", "PUT", "PATCH", "DELETE", "HEAD" })
+    public void multiplePaths(String method) {
+        String lowercaseMethod = method.toLowerCase();
+        String restResponse = method.equals("HEAD") ? "" : method + ": /rest-" + lowercaseMethod;
+        RestAssured.given()
+                .request(method, "/folder-1/rest-" + lowercaseMethod)
+                .then()
+                .statusCode(200)
+                .body(equalTo(restResponse));
+
+        RestAssured.given()
+                .request(method, "/folder-2/rest-" + lowercaseMethod)
+                .then()
+                .statusCode(200)
+                .body(equalTo(restResponse));
+
+        String helloResponse = method.equals("HEAD") ? "" : method + ": /hello";
+        RestAssured.given()
+                .request(method, "/folder-1/hello")
+                .then()
+                .statusCode(200)
+                .body(equalTo(helloResponse));
+
+        RestAssured.given()
+                .request(method, "/folder-2/hello")
+                .then()
+                .statusCode(200)
+                .body(equalTo(helloResponse));
+    }
+
     @Test
-    public void multiplePaths() {
-        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"));
+    public void options() {
+        RestAssured.given()
+                .request("OPTIONS", "/method-options/options")
+                .then()
+                .statusCode(200)
+                .body(equalTo("OPTIONS: /options"));
+    }
+
+    @Test
+    public void trace() {
+        RestAssured.given()
+                .request("TRACE", "/method-trace/trace")
+                .then()
+                .statusCode(200)
+                .body(equalTo("TRACE: /trace"));
+    }
+
+    @Test
+    public void requestParameters() {
+        RestAssured.given()
+                .formParam("prefix", "Hello")
+                .queryParam("suffix", "World")
+                .post("/folder-1/params")
+                .then()
+                .statusCode(200)
+                .body(equalTo("Hello World"));
     }
 
     @Test
     public void namedWithServletClass() {
         RestAssured.get("/my-custom-folder/custom")
                 .then()
+                .statusCode(200)
                 .body(equalTo("GET: /custom"))
-                .and()
                 .header("x-servlet-class-name", CustomServlet.class.getName());
     }
 
@@ -142,4 +202,25 @@ public class CamelServletTest {
                         "initParams.async", equalTo("true"),
                         "loadOnStartup", nullValue());
     }
+
+    @Test
+    public void transferException() throws IOException, ClassNotFoundException {
+        InputStream response = RestAssured.given()
+                .get("/folder-1/transfer/exception")
+                .then()
+                .statusCode(500)
+                .contentType("application/x-java-serialized-object")
+                .extract()
+                .body()
+                .asInputStream();
+
+        // The CamelContext instance is only needed to ensure the correct ClassLoader is used for deserialization
+        CamelContext context = new DefaultCamelContext();
+        context.setApplicationContextClassLoader(Thread.currentThread().getContextClassLoader());
+        Object exception = HttpHelper.deserializeJavaObjectFromStream(response, context);
+        assertNotNull(exception);
+
+        CustomException cause = assertInstanceOf(CustomException.class, exception);
+        assertEquals(CustomException.MESSAGE, cause.getMessage());
+    }
 }


(camel-quarkus) 03/04: Enable web.xml to be used to configure CamelHttpTransportServlet for the servlet extension

Posted by ja...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

jamesnetherton pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-quarkus.git

commit d04332fed209b7b97c36beca0a0698b79093c93c
Author: James Netherton <ja...@gmail.com>
AuthorDate: Mon Mar 4 15:54:57 2024 +0000

    Enable web.xml to be used to configure CamelHttpTransportServlet for the servlet extension
    
    Fixes #5835
---
 .../ROOT/pages/reference/extensions/servlet.adoc   | 37 ++++++++++++
 .../servlet/deployment/ServletProcessor.java       | 14 ++++-
 .../component/servlet/test/WebXmlServletTest.java  | 65 ++++++++++++++++++++++
 extensions/servlet/runtime/src/main/doc/usage.adoc | 37 ++++++++++++
 4 files changed, 152 insertions(+), 1 deletion(-)

diff --git a/docs/modules/ROOT/pages/reference/extensions/servlet.adoc b/docs/modules/ROOT/pages/reference/extensions/servlet.adoc
index 848ac1299c..39fca43e78 100644
--- a/docs/modules/ROOT/pages/reference/extensions/servlet.adoc
+++ b/docs/modules/ROOT/pages/reference/extensions/servlet.adoc
@@ -117,6 +117,43 @@ from("servlet://goodbye?servletName=my-custom-b")
     .setBody().constant("Goodbye World");
 ----
 
+*Finer control of Servlet configuration*
+
+If you need more control of the Servlet configuration, for example to configure custom init parameters,
+then you can do this with a custom Servlet class through the `jakarta.servlet.annotation.WebServlet` annotation options.
+
+[source,java]
+----
+import jakarta.servlet.annotation.WebServlet;
+import org.apache.camel.component.servlet.CamelHttpTransportServlet;
+
+@WebServlet(
+    urlPatterns = {"/*"},
+    initParams = {
+        @WebInitParam(name = "myParam", value = "myValue")
+    }
+)
+public class MyCustomServlet extends CamelHttpTransportServlet {
+}
+----
+
+Or you can configure the `CamelHttpTransportServlet` using a `web-app` descriptor placed into `src/main/resources/META-INF/web.xml`.
+
+[source,xml]
+----
+<web-app>
+  <servlet>
+    <servlet-name>CamelServlet</servlet-name>
+    <servlet-class>org.apache.camel.component.servlet.CamelHttpTransportServlet</servlet-class>
+  </servlet>
+
+  <servlet-mapping>
+    <servlet-name>CamelServlet</servlet-name>
+    <url-pattern>/services/*</url-pattern>
+  </servlet-mapping>
+</web-app>
+----
+
 
 [id="extensions-servlet-transferexception-option-in-native-mode"]
 == transferException option in native mode
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 6afaf14202..a7ab69969f 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
@@ -25,10 +25,14 @@ import io.quarkus.deployment.annotations.BuildStep;
 import io.quarkus.deployment.builditem.FeatureBuildItem;
 import io.quarkus.undertow.deployment.ServletBuildItem;
 import io.quarkus.undertow.deployment.ServletBuildItem.Builder;
+import io.quarkus.undertow.deployment.WebMetadataBuildItem;
 import jakarta.servlet.MultipartConfigElement;
 import org.apache.camel.quarkus.servlet.runtime.CamelServletConfig;
 import org.apache.camel.quarkus.servlet.runtime.CamelServletConfig.ServletConfig;
 import org.apache.camel.quarkus.servlet.runtime.CamelServletConfig.ServletConfig.MultipartConfig;
+import org.jboss.metadata.web.spec.WebMetaData;
+
+import static org.apache.camel.quarkus.servlet.runtime.CamelServletConfig.ServletConfig.DEFAULT_SERVLET_CLASS;
 
 class ServletProcessor {
     private static final String FEATURE = "camel-servlet";
@@ -41,8 +45,16 @@ class ServletProcessor {
     }
 
     @BuildStep
-    void build(BuildProducer<ServletBuildItem> servlet) {
+    void build(BuildProducer<ServletBuildItem> servlet, WebMetadataBuildItem webMetadata) {
         boolean servletCreated = false;
+
+        WebMetaData metaData = webMetadata.getWebMetaData();
+        if (metaData != null && metaData.getServlets() != null) {
+            servletCreated = metaData.getServlets()
+                    .stream()
+                    .anyMatch(meta -> meta.getServletClass().equals(DEFAULT_SERVLET_CLASS));
+        }
+
         if (camelServletConfig.defaultServlet.isValid()) {
             servlet.produce(
                     newServlet(ServletConfig.DEFAULT_SERVLET_NAME, camelServletConfig.defaultServlet));
diff --git a/extensions/servlet/deployment/src/test/java/org/apache/camel/quarkus/component/servlet/test/WebXmlServletTest.java b/extensions/servlet/deployment/src/test/java/org/apache/camel/quarkus/component/servlet/test/WebXmlServletTest.java
new file mode 100644
index 0000000000..1c39eb7954
--- /dev/null
+++ b/extensions/servlet/deployment/src/test/java/org/apache/camel/quarkus/component/servlet/test/WebXmlServletTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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.test;
+
+import io.quarkus.test.QuarkusUnitTest;
+import io.restassured.RestAssured;
+import org.apache.camel.builder.RouteBuilder;
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.asset.StringAsset;
+import org.jboss.shrinkwrap.api.spec.JavaArchive;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import static org.hamcrest.Matchers.equalTo;
+
+public class WebXmlServletTest {
+    static final String WEB_XML = """
+            <web-app>
+                <servlet>
+                  <servlet-name>my-servlet</servlet-name>
+                  <servlet-class>org.apache.camel.component.servlet.CamelHttpTransportServlet</servlet-class>
+                  <load-on-startup>1</load-on-startup>
+                </servlet>
+
+                <servlet-mapping>
+                  <servlet-name>my-servlet</servlet-name>
+                  <url-pattern>/*</url-pattern>
+                </servlet-mapping>
+            </web-app>
+            """;
+    static final String MESSAGE = "This servlet was configured from web.xml";
+
+    @RegisterExtension
+    static final QuarkusUnitTest CONFIG = new QuarkusUnitTest()
+            .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
+                    .addAsResource(new StringAsset(WEB_XML), "META-INF/web.xml"));
+
+    @Test
+    public void noDefaultServlet() throws Exception {
+        RestAssured.when().get("/web/xml").then()
+                .body(equalTo(MESSAGE));
+    }
+
+    public static final class Routes extends RouteBuilder {
+        @Override
+        public void configure() {
+            from("servlet://web/xml?servletName=my-servlet")
+                    .setBody(constant(MESSAGE));
+        }
+    }
+}
diff --git a/extensions/servlet/runtime/src/main/doc/usage.adoc b/extensions/servlet/runtime/src/main/doc/usage.adoc
index 7ad44a268c..6124e06f72 100644
--- a/extensions/servlet/runtime/src/main/doc/usage.adoc
+++ b/extensions/servlet/runtime/src/main/doc/usage.adoc
@@ -64,3 +64,40 @@ from("servlet://greet?servletName=my-custom-a")
 from("servlet://goodbye?servletName=my-custom-b")
     .setBody().constant("Goodbye World");
 ----
+
+*Finer control of Servlet configuration*
+
+If you need more control of the Servlet configuration, for example to configure custom init parameters,
+then you can do this with a custom Servlet class through the `jakarta.servlet.annotation.WebServlet` annotation options.
+
+[source,java]
+----
+import jakarta.servlet.annotation.WebServlet;
+import org.apache.camel.component.servlet.CamelHttpTransportServlet;
+
+@WebServlet(
+    urlPatterns = {"/*"},
+    initParams = {
+        @WebInitParam(name = "myParam", value = "myValue")
+    }
+)
+public class MyCustomServlet extends CamelHttpTransportServlet {
+}
+----
+
+Or you can configure the `CamelHttpTransportServlet` using a `web-app` descriptor placed into `src/main/resources/META-INF/web.xml`.
+
+[source,xml]
+----
+<web-app>
+  <servlet>
+    <servlet-name>CamelServlet</servlet-name>
+    <servlet-class>org.apache.camel.component.servlet.CamelHttpTransportServlet</servlet-class>
+  </servlet>
+
+  <servlet-mapping>
+    <servlet-name>CamelServlet</servlet-name>
+    <url-pattern>/services/*</url-pattern>
+  </servlet-mapping>
+</web-app>
+----