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

rmaucher pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomcat.git

commit b945d5fdb5b12e7411cd8b13ac5f85730c425e52
Author: opencode <[email protected]>
AuthorDate: Fri Nov 6 15:48:11 2026 +0100

    Add optional OWB module coverage and documentation
    
    Add JSP/EL, redeploy and multi-webapp isolation, session scope and webapp 
security filter tests. Document the module build, test prerequisites, 
configuration and known limitations.
---
 modules/owb/README.md                              | 187 +++++++++++++++++++++
 modules/owb/pom.xml                                |  28 +++
 .../webbeans/web/tomcat/OpenWebBeansCdi41IT.java   |   7 +
 .../webbeans/web/tomcat/OpenWebBeansJspIT.java     |  48 ++++++
 .../web/tomcat/OpenWebBeansRedeployIT.java         | 105 ++++++++++++
 .../web/tomcat/OpenWebBeansSecurityFilterIT.java   |  58 +++++++
 .../apache/webbeans/web/tomcat/OwbTestServer.java  |  88 +++++++---
 .../apache/webbeans/web/tomcat/OwbTestSupport.java |   6 +
 .../apache/webbeans/web/tomcat/WebAppBuilder.java  |  11 ++
 .../webbeans/web/tomcat/testapp/Cdi41Servlet.java  |  10 +-
 .../web/tomcat/testapp/SessionScopedBean.java      |  33 ++++
 modules/owb/src/test/webapp/jsp/WEB-INF/beans.xml  |  21 +++
 modules/owb/src/test/webapp/jsp/index.jsp          |  20 +++
 .../webbeans/web/tomcat/testapp/JspGreeting.java   |  29 ++++
 .../src/test/webapp/multi/one/WEB-INF/beans.xml    |  21 +++
 .../webapp/multi/one/WEB-INF/classes/value.txt     |   1 +
 .../webbeans/web/tomcat/testapp/MultiGreeting.java |  35 ++++
 .../webbeans/web/tomcat/testapp/MultiServlet.java  |  40 +++++
 .../src/test/webapp/multi/two/WEB-INF/beans.xml    |  21 +++
 .../webapp/multi/two/WEB-INF/classes/value.txt     |   1 +
 .../webbeans/web/tomcat/testapp/MultiGreeting.java |  35 ++++
 .../webbeans/web/tomcat/testapp/MultiServlet.java  |  40 +++++
 .../owb/src/test/webapp/redeploy/WEB-INF/beans.xml |  21 +++
 .../web/tomcat/testapp/RedeployGreeting.java       |  27 +++
 .../web/tomcat/testapp/RedeployServlet.java        |  66 ++++++++
 .../test/webapp/security-filter/WEB-INF/beans.xml  |  21 +++
 .../test/webapp/security-filter/WEB-INF/web.xml    |  53 ++++++
 .../web/tomcat/testapp/FilterWhoAmIServlet.java    |  45 +++++
 webapps/docs/cdi.xml                               | 107 ++++++++++--
 29 files changed, 1150 insertions(+), 35 deletions(-)

diff --git a/modules/owb/README.md b/modules/owb/README.md
new file mode 100644
index 0000000000..8c69eda852
--- /dev/null
+++ b/modules/owb/README.md
@@ -0,0 +1,187 @@
+<!--
+
+    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.
+
+-->
+
+# Apache Tomcat CDI support module
+
+This module packages Apache OpenWebBeans and provides CDI support for
+Tomcat. The resulting shaded JAR is intended to be placed in the Tomcat
+`lib` directory.
+
+## Prerequisites
+
+- Java 21 or later.
+- Apache Maven.
+- The Tomcat artifacts for `${tomcat.version}` available in the local Maven
+  repository.
+
+The default build uses `12.0.0-SNAPSHOT`, which is not published to Maven
+Central. Install the required Tomcat artifacts from a local Tomcat build, or
+override `tomcat.version` with a released Tomcat version.
+
+## Building
+
+```bash
+cd modules/owb
+mvn clean package
+```
+
+The shaded JAR is written to:
+
+```text
+target/tomcat-owb-<owb-version>.jar
+```
+
+`mvn package` builds the JAR but does not run the integration tests.
+
+## Testing
+
+Run the full integration test suite:
+
+```bash
+mvn clean verify
+```
+
+Run a single test class:
+
+```bash
+mvn -Dit.test=OpenWebBeansSecurityIT verify
+```
+
+The integration tests:
+
+- Use an embedded Tomcat server.
+- Use port `0` and temporary directories.
+- Run against the shaded module JAR, not `target/classes`.
+- Do not require an external Tomcat installation or a running server.
+
+### JSP/EL test prerequisites
+
+The JSP/EL integration test uses `tomcat-embed-jasper` and
+`tomcat-embed-el`. For a snapshot build, install them from the local Tomcat
+build:
+
+```bash
+cd $TOMCAT_SRC
+ant embed-jars
+
+mvn install:install-file \
+  -Dfile=output/embed/tomcat-embed-jasper.jar \
+  -DgroupId=org.apache.tomcat.embed \
+  -DartifactId=tomcat-embed-jasper \
+  -Dversion=12.0.0-SNAPSHOT \
+  -Dpackaging=jar
+
+mvn install:install-file \
+  -Dfile=output/embed/tomcat-embed-el.jar \
+  -DgroupId=org.apache.tomcat.embed \
+  -DartifactId=tomcat-embed-el \
+  -Dversion=12.0.0-SNAPSHOT \
+  -Dpackaging=jar
+```
+
+The test suite also uses the Eclipse compiler (`ecj`) for JSP compilation.
+
+## Test architecture
+
+The tests are standard Maven Failsafe integration tests:
+
+- Test classes are named `*IT` and live in `src/test/java`.
+- Test webapps live under `src/test/webapp`.
+- `WebAppBuilder` compiles and assembles a temporary exploded webapp.
+- `OwbTestServer` starts and stops embedded Tomcat.
+- `OwbTestSupport` provides HTTP, shaded-JAR and marker-file helpers.
+
+### Classpath isolation
+
+The Failsafe configuration deliberately:
+
+- Points `classesDirectory` at an empty directory so `target/classes` is not
+  on the test runtime classpath.
+- Adds the shaded module JAR explicitly.
+- Excludes the unshaded OpenWebBeans, CDI, Interceptor and XBean dependencies
+  from the test runtime.
+
+Tests assert that both `OpenWebBeansListener` and `WebBeansContext` are loaded
+from the shaded JAR. This prevents the tests from accidentally validating an
+unshaded build.
+
+The test webapps do not contain the module JAR in `WEB-INF/lib`; the module is
+provided through the test/common classpath, matching the production
+`lib` directory model.
+
+## Test coverage
+
+| Test class | Coverage |
+|---|---|
+| `OpenWebBeansCdi41IT` | CDI injection, request/session scope, 
`isMatchingBean`, `isMatchingEvent`, `getContexts`, interceptor bindings, 
method invoker |
+| `OpenWebBeansTomcatIntegrationIT` | Server/context listener modes, 
`startWithoutBeansXml`, alternate `beans.xml` location, security valve 
configuration, filter/servlet injection, lifecycle callbacks |
+| `OpenWebBeansSecurityIT` | BASIC authentication, `@Inject Principal`, 
security valve principal |
+| `OpenWebBeansSecurityFilterIT` | Webapp-level `OpenWebBeansSecurityFilter` |
+| `OpenWebBeansJspIT` | JSP/EL resolution through OWB |
+| `OpenWebBeansRedeployIT` | Multiple webapps, classloader isolation, context 
restart, `@PreDestroy`, listener registration |
+
+## Adding a test
+
+1. Add a webapp template under `src/test/webapp/<name>`.
+2. Include `WEB-INF/beans.xml` for a CDI webapp.
+3. Put Java sources under `src/`.
+4. Add a `*IT` test class.
+5. Build the webapp with `WebAppBuilder`.
+6. Start it with `OwbTestServer`.
+7. Verify HTTP endpoints and, where relevant, marker files or context state.
+
+Example skeleton:
+
+```java
+Path webapp = new WebAppBuilder(tempDir.resolve("app"), "my-template")
+        .build(OwbTestSupport.shadedJar());
+
+try (OwbTestServer server = OwbTestServer.start(webapp,
+        ListenerMode.SERVER, false, true)) {
+    HttpClient client = OwbTestSupport.newClient();
+    assertEquals("expected", OwbTestSupport.get(client,
+            server.baseUri().resolve("endpoint")));
+}
+```
+
+## Known limitations
+
+- Field injection into `ServletContextListener` is not supported because
+  Tomcat instantiates listeners before the CDI container starts. Use
+  `CDI.current().select(...)` inside the listener instead.
+- Producer-level `@Priority` on competing alternative producers is not
+  covered. OpenWebBeans reports an ambiguous resolution for that scenario,
+  so no failing gate test is included.
+- `ELAwareBeanManager` is not covered. OpenWebBeans provides the core
+  `BeanManager`, and the supplemental CDI EL API is not part of this module.
+- Classloader garbage-collection leak detection is not asserted. The redeploy
+  tests verify deterministic behavior: pre-destroy callbacks, separate
+  contexts, no duplicate listener registration and successful restart.
+
+## Troubleshooting
+
+- **Missing Tomcat artifacts**: install `tomcat-catalina` and its transitive
+  Tomcat dependencies for `${tomcat.version}` into the local Maven repository.
+- **Missing Jasper/EL artifacts**: run `ant embed-jars` and install
+  `tomcat-embed-jasper` and `tomcat-embed-el` as described above.
+- **Tests run against the wrong classes**: check the Failsafe
+  `classesDirectory`, `additionalClasspathElements` and
+  `classpathDependencyExcludes` settings.
+- **Port or directory conflicts**: the tests use port `0` and JUnit temporary
+  directories; do not hard-code either in new tests.
diff --git a/modules/owb/pom.xml b/modules/owb/pom.xml
index c15db4fffe..f6be93d23a 100644
--- a/modules/owb/pom.xml
+++ b/modules/owb/pom.xml
@@ -40,6 +40,7 @@
         <jakarta-el.version>6.0.1</jakarta-el.version>
         <tomcat.version>12.0.0-SNAPSHOT</tomcat.version>
         <junit.version>5.11.4</junit.version>
+        <ecj.version>3.41.0</ecj.version>
     </properties>
 
     <dependencies>
@@ -105,6 +106,24 @@
             <version>${junit.version}</version>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.tomcat.embed</groupId>
+            <artifactId>tomcat-embed-el</artifactId>
+            <version>${tomcat.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.tomcat.embed</groupId>
+            <artifactId>tomcat-embed-jasper</artifactId>
+            <version>${tomcat.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jdt</groupId>
+            <artifactId>ecj</artifactId>
+            <version>${ecj.version}</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>
@@ -116,6 +135,9 @@
                     <release>21</release>
                 </configuration>
             </plugin>
+            <!-- Create an empty directory used to keep target/classes off the
+                 integration test runtime classpath. The tests must run against
+                 the shaded JAR. -->
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-antrun-plugin</artifactId>
@@ -177,6 +199,8 @@
                     </execution>
                 </executions>
             </plugin>
+            <!-- Integration tests use embedded Tomcat. They run after the
+                 shade plugin has produced the deployable JAR. -->
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-failsafe-plugin</artifactId>
@@ -195,6 +219,8 @@
                     <additionalClasspathElements>
                         
<additionalClasspathElement>${project.build.directory}/${project.build.finalName}.jar</additionalClasspathElement>
                     </additionalClasspathElements>
+                    <!-- Remove unshaded copies of the classes contained in the
+                         shaded JAR so the test validates the deployable JAR. 
-->
                     <classpathDependencyExcludes>
                         
<classpathDependencyExclude>org.apache.openwebbeans:openwebbeans-spi</classpathDependencyExclude>
                         
<classpathDependencyExclude>org.apache.openwebbeans:openwebbeans-impl</classpathDependencyExclude>
@@ -208,6 +234,8 @@
                         
<classpathDependencyExclude>jakarta.interceptor:jakarta.interceptor-api</classpathDependencyExclude>
                         
<classpathDependencyExclude>jakarta.annotation:jakarta.annotation-api</classpathDependencyExclude>
                     </classpathDependencyExcludes>
+                    <!-- Test webapp templates and the shaded JAR are passed to
+                         the test JVM explicitly; target/classes is not used. 
-->
                     <systemPropertyVariables>
                         
<owb.test.webapp.root>${project.basedir}/src/test/webapp</owb.test.webapp.root>
                         
<owb.test.jar>${project.build.directory}/${project.build.finalName}.jar</owb.test.jar>
diff --git 
a/modules/owb/src/test/java/org/apache/webbeans/web/tomcat/OpenWebBeansCdi41IT.java
 
b/modules/owb/src/test/java/org/apache/webbeans/web/tomcat/OpenWebBeansCdi41IT.java
index fda921cab8..0e4d7af80a 100644
--- 
a/modules/owb/src/test/java/org/apache/webbeans/web/tomcat/OpenWebBeansCdi41IT.java
+++ 
b/modules/owb/src/test/java/org/apache/webbeans/web/tomcat/OpenWebBeansCdi41IT.java
@@ -53,6 +53,13 @@ class OpenWebBeansCdi41IT {
             assertFalse(firstRequest.isEmpty());
             assertFalse(secondRequest.isEmpty());
 
+            HttpClient sessionClient = OwbTestSupport.newCookieClient();
+            String firstSession = OwbTestSupport.get(sessionClient, 
baseUri.resolve("session"));
+            String secondSession = OwbTestSupport.get(sessionClient, 
baseUri.resolve("session"));
+            assertEquals(firstSession, secondSession);
+            String differentSession = 
OwbTestSupport.get(OwbTestSupport.newCookieClient(), 
baseUri.resolve("session"));
+            assertNotEquals(firstSession, differentSession);
+
             assertEquals("true", OwbTestSupport.get(client, 
baseUri.resolve("matching")));
             assertEquals("true", OwbTestSupport.get(client, 
baseUri.resolve("matching-event")));
             assertEquals("true", OwbTestSupport.get(client, 
baseUri.resolve("contexts")));
diff --git 
a/modules/owb/src/test/java/org/apache/webbeans/web/tomcat/OpenWebBeansJspIT.java
 
b/modules/owb/src/test/java/org/apache/webbeans/web/tomcat/OpenWebBeansJspIT.java
new file mode 100644
index 0000000000..3824979aa2
--- /dev/null
+++ 
b/modules/owb/src/test/java/org/apache/webbeans/web/tomcat/OpenWebBeansJspIT.java
@@ -0,0 +1,48 @@
+/*
+ * 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.webbeans.web.tomcat;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.net.http.HttpClient;
+import java.net.http.HttpResponse;
+import java.nio.file.Path;
+
+import org.apache.webbeans.web.tomcat.OwbTestServer.ListenerMode;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+class OpenWebBeansJspIT {
+
+    @TempDir
+    Path tempDir;
+
+    @Test
+    void jspElResolvesCdiBean() throws Exception {
+        Path shadedJar = OwbTestSupport.shadedJar();
+        Path webapp = new WebAppBuilder(tempDir.resolve("jsp"), 
"jsp").build(shadedJar);
+        try (OwbTestServer server = OwbTestServer.start(webapp, 
ListenerMode.SERVER, false, true, "/owb-jsp", true,
+                null)) {
+            HttpClient client = OwbTestSupport.newClient();
+            HttpResponse<String> response = OwbTestSupport.getResponse(client, 
server.baseUri().resolve("index.jsp"));
+            assertEquals(200, response.statusCode(), response.body());
+            
assertTrue(response.headers().firstValue("Content-Type").orElse("").startsWith("text/html"));
+            assertTrue(response.body().contains("hello-owb-4.1.1"), 
response.body());
+        }
+    }
+}
diff --git 
a/modules/owb/src/test/java/org/apache/webbeans/web/tomcat/OpenWebBeansRedeployIT.java
 
b/modules/owb/src/test/java/org/apache/webbeans/web/tomcat/OpenWebBeansRedeployIT.java
new file mode 100644
index 0000000000..943c0754fb
--- /dev/null
+++ 
b/modules/owb/src/test/java/org/apache/webbeans/web/tomcat/OpenWebBeansRedeployIT.java
@@ -0,0 +1,105 @@
+/*
+ * 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.webbeans.web.tomcat;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.net.http.HttpClient;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.webbeans.servlet.WebBeansConfigurationListener;
+import org.apache.webbeans.web.tomcat.OwbTestServer.Deployment;
+import org.apache.webbeans.web.tomcat.OwbTestServer.ListenerMode;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+class OpenWebBeansRedeployIT {
+
+    @TempDir
+    Path tempDir;
+
+    @Test
+    void multipleWebappsAreIsolated() throws Exception {
+        Path shadedJar = OwbTestSupport.shadedJar();
+        Path one = new WebAppBuilder(tempDir.resolve("one"), 
"multi/one").build(shadedJar);
+        Path two = new WebAppBuilder(tempDir.resolve("two"), 
"multi/two").build(shadedJar);
+
+        try (OwbTestServer server = OwbTestServer.start(
+                List.of(new Deployment("/owb-one", one), new 
Deployment("/owb-two", two)),
+                ListenerMode.SERVER, false, true, false, null)) {
+            assertEquals("one", get(server, "/owb-one", "value"));
+            assertEquals("two", get(server, "/owb-two", "value"));
+
+            ClassLoader oneLoader = 
server.context("/owb-one").getLoader().getClassLoader();
+            ClassLoader twoLoader = 
server.context("/owb-two").getLoader().getClassLoader();
+            assertNotSame(oneLoader, twoLoader);
+
+            server.stopContext("/owb-one");
+            assertEquals("two", get(server, "/owb-two", "value"));
+        }
+    }
+
+    @Test
+    void contextRestartStartsFreshContainer() throws Exception {
+        Path markerDirectory = tempDir.resolve("markers");
+        Files.createDirectories(markerDirectory);
+        String previousMarkerDirectory = 
System.getProperty("owb.test.marker.dir");
+        System.setProperty("owb.test.marker.dir", markerDirectory.toString());
+        try {
+            Path shadedJar = OwbTestSupport.shadedJar();
+            Path webapp = new WebAppBuilder(tempDir.resolve("redeploy"), 
"redeploy").build(shadedJar);
+            try (OwbTestServer server = OwbTestServer.start(webapp, 
ListenerMode.SERVER, false, true,
+                    "/owb-redeploy", null)) {
+                assertEquals("redeploy-owb-4.1.1", get(server, 
"/owb-redeploy", "redeploy"));
+                String firstPostConstruct = 
Files.readString(markerDirectory.resolve("postConstruct.marker"));
+                
assertTrue(Files.notExists(markerDirectory.resolve("preDestroy.marker")));
+
+                server.stopContext("/owb-redeploy");
+                
assertTrue(Files.exists(markerDirectory.resolve("preDestroy.marker")));
+
+                server.startContext("/owb-redeploy");
+                assertEquals("redeploy-owb-4.1.1", get(server, 
"/owb-redeploy", "redeploy"));
+                String secondPostConstruct = 
Files.readString(markerDirectory.resolve("postConstruct.marker"));
+                assertNotEquals(firstPostConstruct, secondPostConstruct);
+                assertEquals(1, listenerCount(server));
+            }
+        } finally {
+            if (previousMarkerDirectory == null) {
+                System.clearProperty("owb.test.marker.dir");
+            } else {
+                System.setProperty("owb.test.marker.dir", 
previousMarkerDirectory);
+            }
+        }
+    }
+
+    private static String get(OwbTestServer server, String contextPath, String 
path) throws Exception {
+        HttpClient client = OwbTestSupport.newClient();
+        return OwbTestSupport.get(client, 
server.baseUri(contextPath).resolve(path));
+    }
+
+    private static int listenerCount(OwbTestServer server) {
+        return (int) 
Arrays.stream(server.context("/owb-redeploy").findApplicationListeners())
+                .filter(WebBeansConfigurationListener.class.getName()::equals)
+                .count();
+    }
+}
diff --git 
a/modules/owb/src/test/java/org/apache/webbeans/web/tomcat/OpenWebBeansSecurityFilterIT.java
 
b/modules/owb/src/test/java/org/apache/webbeans/web/tomcat/OpenWebBeansSecurityFilterIT.java
new file mode 100644
index 0000000000..2e429542f3
--- /dev/null
+++ 
b/modules/owb/src/test/java/org/apache/webbeans/web/tomcat/OpenWebBeansSecurityFilterIT.java
@@ -0,0 +1,58 @@
+/*
+ * 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.webbeans.web.tomcat;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpResponse;
+import java.nio.file.Path;
+
+import org.apache.webbeans.web.tomcat.OwbTestServer.ListenerMode;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+class OpenWebBeansSecurityFilterIT {
+
+    @TempDir
+    Path tempDir;
+
+    @Test
+    void webappSecurityFilterPropagatesPrincipal() throws Exception {
+        Path shadedJar = OwbTestSupport.shadedJar();
+        Path webapp = new WebAppBuilder(tempDir.resolve("security-filter"), 
"security-filter").build(shadedJar);
+        try (OwbTestServer server = OwbTestServer.start(webapp, 
ListenerMode.SERVER, false, false,
+                "/owb-security-filter", tomcat -> {
+                    tomcat.addUser("testuser", "testpass");
+                    tomcat.addRole("testuser", "test");
+                })) {
+            HttpClient client = OwbTestSupport.newClient();
+            URI uri = server.baseUri().resolve("whoami");
+
+            HttpResponse<String> unauthenticated = 
OwbTestSupport.getResponse(client, uri);
+            assertEquals(401, unauthenticated.statusCode(), 
unauthenticated.body());
+            
assertTrue(unauthenticated.headers().firstValue("WWW-Authenticate").orElse("").startsWith("Basic"));
+
+            HttpResponse<String> authenticated = 
OwbTestSupport.getResponse(client, uri,
+                    OwbTestSupport.basicAuth("testuser", "testpass"));
+            assertEquals(200, authenticated.statusCode(), 
authenticated.body());
+            assertEquals("testuser|testuser", authenticated.body().strip());
+        }
+    }
+}
diff --git 
a/modules/owb/src/test/java/org/apache/webbeans/web/tomcat/OwbTestServer.java 
b/modules/owb/src/test/java/org/apache/webbeans/web/tomcat/OwbTestServer.java
index 47ac599bae..164e46ef46 100644
--- 
a/modules/owb/src/test/java/org/apache/webbeans/web/tomcat/OwbTestServer.java
+++ 
b/modules/owb/src/test/java/org/apache/webbeans/web/tomcat/OwbTestServer.java
@@ -19,6 +19,10 @@ package org.apache.webbeans.web.tomcat;
 import java.net.URI;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
 import java.util.function.Consumer;
 
 import org.apache.catalina.Context;
@@ -34,26 +38,48 @@ final class OwbTestServer implements AutoCloseable {
         CONTEXT
     }
 
+    record Deployment(String contextPath, Path webapp) {
+    }
+
     private final Tomcat tomcat;
-    private final Context context;
-    private final URI baseUri;
+    private final Map<String, Context> contexts;
+    private final Map<String, URI> baseUris;
 
-    private OwbTestServer(Tomcat tomcat, Context context, URI baseUri) {
+    private OwbTestServer(Tomcat tomcat, Map<String, Context> contexts, 
Map<String, URI> baseUris) {
         this.tomcat = tomcat;
-        this.context = context;
-        this.baseUri = baseUri;
+        this.contexts = contexts;
+        this.baseUris = baseUris;
+    }
+
+    static OwbTestServer start(Path webapp, ListenerMode mode, boolean 
startWithoutBeansXml,
+            boolean addSecurityValve) throws Exception {
+        return start(List.of(new Deployment("/owb-test", webapp)), mode, 
startWithoutBeansXml, addSecurityValve,
+                false, null);
     }
 
     static OwbTestServer start(Path webapp, ListenerMode mode, boolean 
startWithoutBeansXml,
             boolean addSecurityValve, String contextPath, Consumer<Tomcat> 
configurer) throws Exception {
-        Path base = Files.createTempDirectory(webapp.getParent(), "tomcat-");
+        return start(List.of(new Deployment(contextPath, webapp)), mode, 
startWithoutBeansXml, addSecurityValve,
+                false, configurer);
+    }
+
+    static OwbTestServer start(Path webapp, ListenerMode mode, boolean 
startWithoutBeansXml,
+            boolean addSecurityValve, String contextPath, boolean 
useDefaultWebXml,
+            Consumer<Tomcat> configurer) throws Exception {
+        return start(List.of(new Deployment(contextPath, webapp)), mode, 
startWithoutBeansXml, addSecurityValve,
+                useDefaultWebXml, configurer);
+    }
+
+    static OwbTestServer start(List<Deployment> deployments, ListenerMode 
mode, boolean startWithoutBeansXml,
+            boolean addSecurityValve, boolean useDefaultWebXml, 
Consumer<Tomcat> configurer) throws Exception {
+        Path base = 
Files.createTempDirectory(deployments.get(0).webapp().getParent(), "tomcat-");
 
         Tomcat tomcat = new Tomcat();
         tomcat.setBaseDir(base.toString());
         tomcat.setPort(0);
         tomcat.getConnector();
         tomcat.getServer().setPort(-1);
-        tomcat.setAddDefaultWebXmlToWebapp(false);
+        tomcat.setAddDefaultWebXmlToWebapp(useDefaultWebXml);
         // Enable naming so that Tomcat's DefaultInstanceManager invokes
         // @PostConstruct and @PreDestroy callbacks.
         tomcat.enableNaming();
@@ -62,17 +88,23 @@ final class OwbTestServer implements AutoCloseable {
             configurer.accept(tomcat);
         }
 
-        Context context = tomcat.addWebapp(contextPath, webapp.toString());
+        Map<String, Context> contexts = new LinkedHashMap<>();
+        for (Deployment deployment : deployments) {
+            Context context = tomcat.addWebapp(deployment.contextPath(), 
deployment.webapp().toString());
+            contexts.put(deployment.contextPath(), context);
+            if (mode == ListenerMode.CONTEXT) {
+                OpenWebBeansContextLifecycleListener listener = new 
OpenWebBeansContextLifecycleListener();
+                listener.setStartWithoutBeansXml(startWithoutBeansXml);
+                listener.setAddSecurityValve(addSecurityValve);
+                context.addLifecycleListener(listener);
+            }
+        }
+
         if (mode == ListenerMode.SERVER) {
             OpenWebBeansListener listener = new OpenWebBeansListener();
             listener.setStartWithoutBeansXml(startWithoutBeansXml);
             listener.setAddSecurityValve(addSecurityValve);
             tomcat.getServer().addLifecycleListener(listener);
-        } else {
-            OpenWebBeansContextLifecycleListener listener = new 
OpenWebBeansContextLifecycleListener();
-            listener.setStartWithoutBeansXml(startWithoutBeansXml);
-            listener.setAddSecurityValve(addSecurityValve);
-            context.addLifecycleListener(listener);
         }
 
         try {
@@ -83,27 +115,41 @@ final class OwbTestServer implements AutoCloseable {
         }
 
         int port = tomcat.getConnector().getLocalPort();
-        URI baseUri = URI.create("http://127.0.0.1:"; + port + contextPath + 
"/");
-        return new OwbTestServer(tomcat, context, baseUri);
+        Map<String, URI> baseUris = new LinkedHashMap<>();
+        for (String contextPath : contexts.keySet()) {
+            baseUris.put(contextPath, URI.create("http://127.0.0.1:"; + port + 
contextPath + "/"));
+        }
+        return new OwbTestServer(tomcat, 
Collections.unmodifiableMap(contexts), Collections.unmodifiableMap(baseUris));
     }
 
-    static OwbTestServer start(Path webapp, ListenerMode mode, boolean 
startWithoutBeansXml,
-            boolean addSecurityValve) throws Exception {
-        return start(webapp, mode, startWithoutBeansXml, addSecurityValve, 
"/owb-test", null);
+    Context context() {
+        return contexts.values().iterator().next();
     }
 
-    Context context() {
-        return context;
+    Context context(String contextPath) {
+        return contexts.get(contextPath);
     }
 
     URI baseUri() {
-        return baseUri;
+        return baseUris.values().iterator().next();
+    }
+
+    URI baseUri(String contextPath) {
+        return baseUris.get(contextPath);
     }
 
     Tomcat tomcat() {
         return tomcat;
     }
 
+    void stopContext(String contextPath) throws Exception {
+        context(contextPath).stop();
+    }
+
+    void startContext(String contextPath) throws Exception {
+        context(contextPath).start();
+    }
+
     @Override
     public void close() throws Exception {
         try {
diff --git 
a/modules/owb/src/test/java/org/apache/webbeans/web/tomcat/OwbTestSupport.java 
b/modules/owb/src/test/java/org/apache/webbeans/web/tomcat/OwbTestSupport.java
index 7c4f4c38e7..7ffedaf360 100644
--- 
a/modules/owb/src/test/java/org/apache/webbeans/web/tomcat/OwbTestSupport.java
+++ 
b/modules/owb/src/test/java/org/apache/webbeans/web/tomcat/OwbTestSupport.java
@@ -19,6 +19,7 @@ package org.apache.webbeans.web.tomcat;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
+import java.net.CookieManager;
 import java.net.URI;
 import java.net.http.HttpClient;
 import java.net.http.HttpRequest;
@@ -55,6 +56,11 @@ final class OwbTestSupport {
         return 
HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(5)).build();
     }
 
+    static HttpClient newCookieClient() {
+        return HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(5))
+                .cookieHandler(new CookieManager()).build();
+    }
+
     static HttpResponse<String> getResponse(HttpClient client, URI uri) throws 
Exception {
         return getResponse(client, uri, null);
     }
diff --git 
a/modules/owb/src/test/java/org/apache/webbeans/web/tomcat/WebAppBuilder.java 
b/modules/owb/src/test/java/org/apache/webbeans/web/tomcat/WebAppBuilder.java
index 309e9b0151..c7194f57f6 100644
--- 
a/modules/owb/src/test/java/org/apache/webbeans/web/tomcat/WebAppBuilder.java
+++ 
b/modules/owb/src/test/java/org/apache/webbeans/web/tomcat/WebAppBuilder.java
@@ -84,6 +84,17 @@ final class WebAppBuilder {
             copyTree(webInf, destination.resolve("WEB-INF"));
         }
 
+        // Copy root-level web resources such as JSP pages.
+        try (Stream<Path> stream = Files.list(template)) {
+            for (Path child : stream.toList()) {
+                String name = child.getFileName().toString();
+                if ("WEB-INF".equals(name) || "src".equals(name)) {
+                    continue;
+                }
+                copyTree(child, destination.resolve(name));
+            }
+        }
+
         Path classes = destination.resolve("WEB-INF/classes");
         Files.createDirectories(classes);
         compile(classes, shadedJar);
diff --git 
a/modules/owb/src/test/webapp/cdi41/src/org/apache/webbeans/web/tomcat/testapp/Cdi41Servlet.java
 
b/modules/owb/src/test/webapp/cdi41/src/org/apache/webbeans/web/tomcat/testapp/Cdi41Servlet.java
index b5bd4e6fd0..a7b759fa28 100644
--- 
a/modules/owb/src/test/webapp/cdi41/src/org/apache/webbeans/web/tomcat/testapp/Cdi41Servlet.java
+++ 
b/modules/owb/src/test/webapp/cdi41/src/org/apache/webbeans/web/tomcat/testapp/Cdi41Servlet.java
@@ -24,8 +24,8 @@ import jakarta.servlet.http.HttpServlet;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
 
-@WebServlet(urlPatterns = { "/hello", "/request", "/matching", 
"/matching-event", "/contexts", "/interceptor",
-        "/invoker" })
+@WebServlet(urlPatterns = { "/hello", "/request", "/session", "/matching", 
"/matching-event", "/contexts",
+        "/interceptor", "/invoker" })
 public class Cdi41Servlet extends HttpServlet {
 
     private static final long serialVersionUID = 1L;
@@ -36,6 +36,9 @@ public class Cdi41Servlet extends HttpServlet {
     @Inject
     private RequestScopedBean requestScopedBean;
 
+    @Inject
+    private SessionScopedBean sessionScopedBean;
+
     @Inject
     private Cdi41Features cdi41Features;
 
@@ -52,6 +55,9 @@ public class Cdi41Servlet extends HttpServlet {
             case "/request":
                 result = requestScopedBean.getId();
                 break;
+            case "/session":
+                result = sessionScopedBean.getId();
+                break;
             case "/matching":
                 result = Boolean.toString(cdi41Features.matching());
                 break;
diff --git 
a/modules/owb/src/test/webapp/cdi41/src/org/apache/webbeans/web/tomcat/testapp/SessionScopedBean.java
 
b/modules/owb/src/test/webapp/cdi41/src/org/apache/webbeans/web/tomcat/testapp/SessionScopedBean.java
new file mode 100644
index 0000000000..df8c0e1db0
--- /dev/null
+++ 
b/modules/owb/src/test/webapp/cdi41/src/org/apache/webbeans/web/tomcat/testapp/SessionScopedBean.java
@@ -0,0 +1,33 @@
+/*
+ * 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.webbeans.web.tomcat.testapp;
+
+import java.util.UUID;
+
+import jakarta.enterprise.context.SessionScoped;
+
+@SessionScoped
+public class SessionScopedBean implements java.io.Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    private final String id = UUID.randomUUID().toString();
+
+    public String getId() {
+        return id;
+    }
+}
diff --git a/modules/owb/src/test/webapp/jsp/WEB-INF/beans.xml 
b/modules/owb/src/test/webapp/jsp/WEB-INF/beans.xml
new file mode 100644
index 0000000000..abc684f5de
--- /dev/null
+++ b/modules/owb/src/test/webapp/jsp/WEB-INF/beans.xml
@@ -0,0 +1,21 @@
+<!--
+  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.
+-->
+<beans xmlns="https://jakarta.ee/xml/ns/jakartaee";
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+       xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee 
https://jakarta.ee/xml/ns/jakartaee/beans_4_1.xsd";
+       version="4.1"
+       bean-discovery-mode="all"/>
diff --git a/modules/owb/src/test/webapp/jsp/index.jsp 
b/modules/owb/src/test/webapp/jsp/index.jsp
new file mode 100644
index 0000000000..21e1a03c3b
--- /dev/null
+++ b/modules/owb/src/test/webapp/jsp/index.jsp
@@ -0,0 +1,20 @@
+<%--
+  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.
+--%>
+<%@ page contentType="text/html; charset=UTF-8" %>
+<html>
+<body>${greeting.message}</body>
+</html>
diff --git 
a/modules/owb/src/test/webapp/jsp/src/org/apache/webbeans/web/tomcat/testapp/JspGreeting.java
 
b/modules/owb/src/test/webapp/jsp/src/org/apache/webbeans/web/tomcat/testapp/JspGreeting.java
new file mode 100644
index 0000000000..c1de05e7ef
--- /dev/null
+++ 
b/modules/owb/src/test/webapp/jsp/src/org/apache/webbeans/web/tomcat/testapp/JspGreeting.java
@@ -0,0 +1,29 @@
+/*
+ * 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.webbeans.web.tomcat.testapp;
+
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Named;
+
+@Named("greeting")
+@ApplicationScoped
+public class JspGreeting {
+
+    public String getMessage() {
+        return "hello-owb-4.1.1";
+    }
+}
diff --git a/modules/owb/src/test/webapp/multi/one/WEB-INF/beans.xml 
b/modules/owb/src/test/webapp/multi/one/WEB-INF/beans.xml
new file mode 100644
index 0000000000..abc684f5de
--- /dev/null
+++ b/modules/owb/src/test/webapp/multi/one/WEB-INF/beans.xml
@@ -0,0 +1,21 @@
+<!--
+  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.
+-->
+<beans xmlns="https://jakarta.ee/xml/ns/jakartaee";
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+       xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee 
https://jakarta.ee/xml/ns/jakartaee/beans_4_1.xsd";
+       version="4.1"
+       bean-discovery-mode="all"/>
diff --git a/modules/owb/src/test/webapp/multi/one/WEB-INF/classes/value.txt 
b/modules/owb/src/test/webapp/multi/one/WEB-INF/classes/value.txt
new file mode 100644
index 0000000000..5626abf0f7
--- /dev/null
+++ b/modules/owb/src/test/webapp/multi/one/WEB-INF/classes/value.txt
@@ -0,0 +1 @@
+one
diff --git 
a/modules/owb/src/test/webapp/multi/one/src/org/apache/webbeans/web/tomcat/testapp/MultiGreeting.java
 
b/modules/owb/src/test/webapp/multi/one/src/org/apache/webbeans/web/tomcat/testapp/MultiGreeting.java
new file mode 100644
index 0000000000..be34d9dea8
--- /dev/null
+++ 
b/modules/owb/src/test/webapp/multi/one/src/org/apache/webbeans/web/tomcat/testapp/MultiGreeting.java
@@ -0,0 +1,35 @@
+/*
+ * 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.webbeans.web.tomcat.testapp;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+import jakarta.enterprise.context.ApplicationScoped;
+
+@ApplicationScoped
+public class MultiGreeting {
+
+    public String value() {
+        try (InputStream input = 
MultiGreeting.class.getResourceAsStream("/value.txt")) {
+            return new String(input.readAllBytes(), 
StandardCharsets.UTF_8).strip();
+        } catch (IOException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+}
diff --git 
a/modules/owb/src/test/webapp/multi/one/src/org/apache/webbeans/web/tomcat/testapp/MultiServlet.java
 
b/modules/owb/src/test/webapp/multi/one/src/org/apache/webbeans/web/tomcat/testapp/MultiServlet.java
new file mode 100644
index 0000000000..9ec9dc883c
--- /dev/null
+++ 
b/modules/owb/src/test/webapp/multi/one/src/org/apache/webbeans/web/tomcat/testapp/MultiServlet.java
@@ -0,0 +1,40 @@
+/*
+ * 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.webbeans.web.tomcat.testapp;
+
+import java.io.IOException;
+
+import jakarta.inject.Inject;
+import jakarta.servlet.annotation.WebServlet;
+import jakarta.servlet.http.HttpServlet;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+@WebServlet("/value")
+public class MultiServlet extends HttpServlet {
+
+    private static final long serialVersionUID = 1L;
+
+    @Inject
+    private MultiGreeting greeting;
+
+    @Override
+    protected void doGet(HttpServletRequest request, HttpServletResponse 
response) throws IOException {
+        response.setContentType("text/plain");
+        response.getWriter().println(greeting.value());
+    }
+}
diff --git a/modules/owb/src/test/webapp/multi/two/WEB-INF/beans.xml 
b/modules/owb/src/test/webapp/multi/two/WEB-INF/beans.xml
new file mode 100644
index 0000000000..abc684f5de
--- /dev/null
+++ b/modules/owb/src/test/webapp/multi/two/WEB-INF/beans.xml
@@ -0,0 +1,21 @@
+<!--
+  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.
+-->
+<beans xmlns="https://jakarta.ee/xml/ns/jakartaee";
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+       xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee 
https://jakarta.ee/xml/ns/jakartaee/beans_4_1.xsd";
+       version="4.1"
+       bean-discovery-mode="all"/>
diff --git a/modules/owb/src/test/webapp/multi/two/WEB-INF/classes/value.txt 
b/modules/owb/src/test/webapp/multi/two/WEB-INF/classes/value.txt
new file mode 100644
index 0000000000..f719efd430
--- /dev/null
+++ b/modules/owb/src/test/webapp/multi/two/WEB-INF/classes/value.txt
@@ -0,0 +1 @@
+two
diff --git 
a/modules/owb/src/test/webapp/multi/two/src/org/apache/webbeans/web/tomcat/testapp/MultiGreeting.java
 
b/modules/owb/src/test/webapp/multi/two/src/org/apache/webbeans/web/tomcat/testapp/MultiGreeting.java
new file mode 100644
index 0000000000..be34d9dea8
--- /dev/null
+++ 
b/modules/owb/src/test/webapp/multi/two/src/org/apache/webbeans/web/tomcat/testapp/MultiGreeting.java
@@ -0,0 +1,35 @@
+/*
+ * 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.webbeans.web.tomcat.testapp;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+import jakarta.enterprise.context.ApplicationScoped;
+
+@ApplicationScoped
+public class MultiGreeting {
+
+    public String value() {
+        try (InputStream input = 
MultiGreeting.class.getResourceAsStream("/value.txt")) {
+            return new String(input.readAllBytes(), 
StandardCharsets.UTF_8).strip();
+        } catch (IOException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+}
diff --git 
a/modules/owb/src/test/webapp/multi/two/src/org/apache/webbeans/web/tomcat/testapp/MultiServlet.java
 
b/modules/owb/src/test/webapp/multi/two/src/org/apache/webbeans/web/tomcat/testapp/MultiServlet.java
new file mode 100644
index 0000000000..9ec9dc883c
--- /dev/null
+++ 
b/modules/owb/src/test/webapp/multi/two/src/org/apache/webbeans/web/tomcat/testapp/MultiServlet.java
@@ -0,0 +1,40 @@
+/*
+ * 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.webbeans.web.tomcat.testapp;
+
+import java.io.IOException;
+
+import jakarta.inject.Inject;
+import jakarta.servlet.annotation.WebServlet;
+import jakarta.servlet.http.HttpServlet;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+@WebServlet("/value")
+public class MultiServlet extends HttpServlet {
+
+    private static final long serialVersionUID = 1L;
+
+    @Inject
+    private MultiGreeting greeting;
+
+    @Override
+    protected void doGet(HttpServletRequest request, HttpServletResponse 
response) throws IOException {
+        response.setContentType("text/plain");
+        response.getWriter().println(greeting.value());
+    }
+}
diff --git a/modules/owb/src/test/webapp/redeploy/WEB-INF/beans.xml 
b/modules/owb/src/test/webapp/redeploy/WEB-INF/beans.xml
new file mode 100644
index 0000000000..abc684f5de
--- /dev/null
+++ b/modules/owb/src/test/webapp/redeploy/WEB-INF/beans.xml
@@ -0,0 +1,21 @@
+<!--
+  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.
+-->
+<beans xmlns="https://jakarta.ee/xml/ns/jakartaee";
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+       xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee 
https://jakarta.ee/xml/ns/jakartaee/beans_4_1.xsd";
+       version="4.1"
+       bean-discovery-mode="all"/>
diff --git 
a/modules/owb/src/test/webapp/redeploy/src/org/apache/webbeans/web/tomcat/testapp/RedeployGreeting.java
 
b/modules/owb/src/test/webapp/redeploy/src/org/apache/webbeans/web/tomcat/testapp/RedeployGreeting.java
new file mode 100644
index 0000000000..e398bb34d1
--- /dev/null
+++ 
b/modules/owb/src/test/webapp/redeploy/src/org/apache/webbeans/web/tomcat/testapp/RedeployGreeting.java
@@ -0,0 +1,27 @@
+/*
+ * 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.webbeans.web.tomcat.testapp;
+
+import jakarta.enterprise.context.ApplicationScoped;
+
+@ApplicationScoped
+public class RedeployGreeting {
+
+    public String message() {
+        return "redeploy-owb-4.1.1";
+    }
+}
diff --git 
a/modules/owb/src/test/webapp/redeploy/src/org/apache/webbeans/web/tomcat/testapp/RedeployServlet.java
 
b/modules/owb/src/test/webapp/redeploy/src/org/apache/webbeans/web/tomcat/testapp/RedeployServlet.java
new file mode 100644
index 0000000000..1f3687aff2
--- /dev/null
+++ 
b/modules/owb/src/test/webapp/redeploy/src/org/apache/webbeans/web/tomcat/testapp/RedeployServlet.java
@@ -0,0 +1,66 @@
+/*
+ * 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.webbeans.web.tomcat.testapp;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.UUID;
+
+import jakarta.annotation.PostConstruct;
+import jakarta.annotation.PreDestroy;
+import jakarta.inject.Inject;
+import jakarta.servlet.annotation.WebServlet;
+import jakarta.servlet.http.HttpServlet;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+@WebServlet("/redeploy")
+public class RedeployServlet extends HttpServlet {
+
+    private static final long serialVersionUID = 1L;
+
+    @Inject
+    private RedeployGreeting greeting;
+
+    @PostConstruct
+    void postConstruct() {
+        writeMarker("postConstruct.marker", UUID.randomUUID().toString());
+    }
+
+    @PreDestroy
+    void preDestroy() {
+        writeMarker("preDestroy.marker", "destroyed");
+    }
+
+    @Override
+    protected void doGet(HttpServletRequest request, HttpServletResponse 
response) throws IOException {
+        response.setContentType("text/plain");
+        response.getWriter().println(greeting.message());
+    }
+
+    private static void writeMarker(String name, String content) {
+        String markerDirectory = System.getProperty("owb.test.marker.dir");
+        if (markerDirectory != null) {
+            try {
+                Files.writeString(Path.of(markerDirectory).resolve(name), 
content);
+            } catch (IOException e) {
+                throw new IllegalStateException("Unable to write lifecycle 
marker " + name, e);
+            }
+        }
+    }
+}
diff --git a/modules/owb/src/test/webapp/security-filter/WEB-INF/beans.xml 
b/modules/owb/src/test/webapp/security-filter/WEB-INF/beans.xml
new file mode 100644
index 0000000000..abc684f5de
--- /dev/null
+++ b/modules/owb/src/test/webapp/security-filter/WEB-INF/beans.xml
@@ -0,0 +1,21 @@
+<!--
+  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.
+-->
+<beans xmlns="https://jakarta.ee/xml/ns/jakartaee";
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+       xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee 
https://jakarta.ee/xml/ns/jakartaee/beans_4_1.xsd";
+       version="4.1"
+       bean-discovery-mode="all"/>
diff --git a/modules/owb/src/test/webapp/security-filter/WEB-INF/web.xml 
b/modules/owb/src/test/webapp/security-filter/WEB-INF/web.xml
new file mode 100644
index 0000000000..c3ea0117af
--- /dev/null
+++ b/modules/owb/src/test/webapp/security-filter/WEB-INF/web.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  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.
+-->
+<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee";
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee 
https://jakarta.ee/xml/ns/jakartaee/web-app_6_1.xsd";
+         version="6.1">
+
+  <display-name>OWB Security Filter Test</display-name>
+
+  <filter>
+    <filter-name>owbSecurityFilter</filter-name>
+    
<filter-class>org.apache.webbeans.web.tomcat.OpenWebBeansSecurityFilter</filter-class>
+  </filter>
+  <filter-mapping>
+    <filter-name>owbSecurityFilter</filter-name>
+    <url-pattern>/*</url-pattern>
+  </filter-mapping>
+
+  <security-constraint>
+    <web-resource-collection>
+      <web-resource-name>Protected</web-resource-name>
+      <url-pattern>/whoami</url-pattern>
+    </web-resource-collection>
+    <auth-constraint>
+      <role-name>test</role-name>
+    </auth-constraint>
+  </security-constraint>
+
+  <login-config>
+    <auth-method>BASIC</auth-method>
+    <realm-name>OWB Test</realm-name>
+  </login-config>
+
+  <security-role>
+    <role-name>test</role-name>
+  </security-role>
+
+</web-app>
diff --git 
a/modules/owb/src/test/webapp/security-filter/src/org/apache/webbeans/web/tomcat/testapp/FilterWhoAmIServlet.java
 
b/modules/owb/src/test/webapp/security-filter/src/org/apache/webbeans/web/tomcat/testapp/FilterWhoAmIServlet.java
new file mode 100644
index 0000000000..da80ebf30a
--- /dev/null
+++ 
b/modules/owb/src/test/webapp/security-filter/src/org/apache/webbeans/web/tomcat/testapp/FilterWhoAmIServlet.java
@@ -0,0 +1,45 @@
+/*
+ * 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.webbeans.web.tomcat.testapp;
+
+import java.io.IOException;
+import java.security.Principal;
+
+import jakarta.inject.Inject;
+import jakarta.servlet.annotation.WebServlet;
+import jakarta.servlet.http.HttpServlet;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+import org.apache.webbeans.web.tomcat.OpenWebBeansSecurityFilter;
+
+@WebServlet("/whoami")
+public class FilterWhoAmIServlet extends HttpServlet {
+
+    private static final long serialVersionUID = 1L;
+
+    @Inject
+    private Principal principal;
+
+    @Override
+    protected void doGet(HttpServletRequest request, HttpServletResponse 
response) throws IOException {
+        Principal filterPrincipal = OpenWebBeansSecurityFilter.getPrincipal();
+        String filterName = filterPrincipal == null ? "null" : 
filterPrincipal.getName();
+        response.setContentType("text/plain");
+        response.getWriter().println(principal.getName() + "|" + filterName);
+    }
+}
diff --git a/webapps/docs/cdi.xml b/webapps/docs/cdi.xml
index d8e9f5e98e..32fc23b644 100644
--- a/webapps/docs/cdi.xml
+++ b/webapps/docs/cdi.xml
@@ -45,34 +45,117 @@
   <section name="CDI support">
 
   <p>
-    CDI 4.1 support is provided by the <code>modules/owb</code> optional 
module.
-    It packages the Apache OpenWebBeans project and allows adding CDI 4.1
-    support to the Tomcat container. The build process of the module uses
-    Apache Maven, and is not available as a binary bundle as it is built using
-    a number of publicly available JARs.
+    CDI support is provided by the <code>modules/owb</code> optional module.
+    It packages Apache OpenWebBeans and allows adding CDI support
+    to the Tomcat container. The build process of the module uses Apache
+    Maven, and is not available as a binary bundle as it is built using a
+    number of publicly available JARs.
+  </p>
+
+  </section>
+
+  <section name="Building">
+
+  <p>
+    Building the module requires Java 21 or later, Apache Maven and the Tomcat
+    artifacts for the Tomcat version used by the module in the local Maven
+    repository. The default configuration uses the Tomcat version from the
+    current source tree.
   </p>
 
   <p>
     The process to build CDI support is the following.
     <source><![CDATA[cd $TOMCAT_SRC/modules/owb
 mvn clean && mvn package]]></source>
-    The module integration tests can be run with <code>mvn clean verify</code>.
     The resulting JAR at
     <code>target/tomcat-owb-x.y.z.jar</code> (where x.y.z depends on the
-    Apache OpenWebBeans version used during the build)
-    should be processed by the Tomcat migration tool for Jakarta EE, and
-    then be placed into the <code>lib</code> folder of the Tomcat
-    installation.<br/>
-    CDI support can then be enabled for all webapps in the container by adding
+    Apache OpenWebBeans version used during the build) can be placed directly
+    into the <code>lib</code> folder of the Tomcat installation. The JAR is
+    built against Jakarta EE APIs. The Tomcat migration tool for Jakarta EE is
+    only required for applications that still use <code>javax</code> APIs.
+  </p>
+
+  </section>
+
+  <section name="Testing">
+
+  <p>
+    The module integration tests use an embedded Tomcat server and run
+    against the shaded module JAR. They are Failsafe integration tests and
+    are not run by <code>mvn package</code>.
+  </p>
+
+  <p>
+    To build the module and run all integration tests:
+    <source><![CDATA[cd $TOMCAT_SRC/modules/owb
+mvn clean verify]]></source>
+    A single integration test can be run with:
+    <source><![CDATA[mvn -Dit.test=OpenWebBeansSecurityIT verify]]></source>
+  </p>
+
+  <p>
+    The tests cover CDI APIs, both supported listener configuration modes,
+    <code>beans.xml</code> discovery, security integration, lifecycle
+    callbacks and JSP/EL integration. See
+    <code>modules/owb/README.md</code> for the test architecture and for the
+    prerequisites needed when Tomcat snapshot artifacts are used.
+  </p>
+
+  </section>
+
+  <section name="Enabling CDI support">
+
+  <p>
+    CDI support can be enabled for all webapps in the container by adding
     the following listener in <code>server.xml</code> nested inside the
     <code>Server</code> element:
     <source><![CDATA[<Listener 
className="org.apache.webbeans.web.tomcat.OpenWebBeansListener" optional="true" 
startWithoutBeansXml="false" />]]></source>
     The listener will produce a non fatal error if the CDI container loading
     fails.<br/>
+    The listener supports the following properties:
+  </p>
+
+  <ul>
+    <li><code>startWithoutBeansXml</code> - if <code>true</code>, CDI is
+    started even when no <code>beans.xml</code> is present. The default is
+    <code>true</code>.</li>
+    <li><code>addSecurityValve</code> - if <code>true</code>, the
+    <code>OpenWebBeansSecurityValve</code> is added to each context pipeline.
+    The default is <code>true</code>.</li>
+  </ul>
+
+  <p>
     CDI support can also be enabled at the individual webapp level by adding
     the following listener to the webapp <code>context.xml</code> file nested
-    inside the <code>Server</code> element:
+    inside the <code>Context</code> element:
     <source><![CDATA[<Listener 
className="org.apache.webbeans.web.tomcat.OpenWebBeansContextLifecycleListener" 
/>]]></source>
+    The context-level listener supports the same properties as the server-level
+    listener.
+  </p>
+
+  <p>
+    When security integration is required, the listener can add the
+    <code>OpenWebBeansSecurityValve</code> automatically. Alternatively, the
+    <code>OpenWebBeansSecurityFilter</code> can be configured in the webapp
+    <code>WEB-INF/web.xml</code> file.
+  </p>
+
+  </section>
+
+  <section name="Known limitations">
+
+  <p>
+    Servlet and filter injection are supported. A
+    <code>ServletContextListener</code> is instantiated before the CDI
+    container starts, so field injection into a listener is not available.
+    Use programmatic lookup with <code>CDI.current().select(...)</code> inside
+    a listener instead.
+  </p>
+
+  <p>
+    Support for features that depend on optional CDI EL APIs or on CDI
+    features not implemented by OpenWebBeans may vary. See
+    <code>modules/owb/README.md</code> for the currently tested feature set.
   </p>
 
   </section>


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to