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

jungm pushed a commit to branch claude/tomee-4642-fix-23d281
in repository https://gitbox.apache.org/repos/asf/tomee.git

commit 3faf4fcb4ce7727cd2ed0027eec8385fe73e8faa
Author: Markus Jung <[email protected]>
AuthorDate: Thu Jul 23 21:01:49 2026 +0200

    TOMEE-4642 - Don't fail deployment when a servlet/filter/listener class is 
missing
    
    When a war's web.xml or annotations name a servlet, filter or listener
    class that is not packaged in the war, ProcessAnnotatedBeans.deploy
    rethrew the ClassNotFoundException/NoClassDefFoundError as an
    OpenEJBException, which aborted startup of the whole web context.
    
    These loads only feed the annotation scanner, so a missing class simply
    means there is nothing to scan - it must not bring down the context.
    Jakarta Servlet 6.1 section 2.3.1 allows servlet loading to be delayed
    "until the container determines the servlet is needed to service a
    request", so an unresolved class is deferred, not fatal. The three paths
    now log a warning and continue, matching the existing tolerant handling
    of taglib listeners and the servlet name-fallback case.
    
    Also fixed off-by-one MessageFormat placeholder indices ({1}{2}{3} ->
    {0}{1}{2}) in the four related logger.debug calls, which dropped the
    first argument.
    
    Two Jakarta Servlet TCK deployments triggered this (RegistrationTests
    naming filter AddFilterString, DefaultMappingTests naming servlet
    TestServlet1).
---
 .../apache/openejb/config/AnnotationDeployer.java  | 23 +++++++++----
 .../openejb/config/AnnotationDeployerTest.java     | 40 ++++++++++++++++++++++
 2 files changed, 56 insertions(+), 7 deletions(-)

diff --git 
a/container/openejb-core/src/main/java/org/apache/openejb/config/AnnotationDeployer.java
 
b/container/openejb-core/src/main/java/org/apache/openejb/config/AnnotationDeployer.java
index 4326211d08..50a4f18ac5 100644
--- 
a/container/openejb-core/src/main/java/org/apache/openejb/config/AnnotationDeployer.java
+++ 
b/container/openejb-core/src/main/java/org/apache/openejb/config/AnnotationDeployer.java
@@ -2263,10 +2263,15 @@ public class AnnotationDeployer implements 
DynamicDeployer {
                                 servlet.setServletClass(servletClass);
                             }
                         } catch (final ClassNotFoundException | 
NoClassDefFoundError e) {
-                            logger.debug("Could not load Servlet class {1} for 
web module {2} / {3}",
+                            logger.debug("Could not load Servlet class {0} for 
web module {1} / {2}",
                                          servletClass, 
webModule.getJarLocation(), webModule.getFile().getName());
                             if (servlet.getServletClass() != null) {
-                                throw new OpenEJBException("Unable to load 
servlet class: " + servletClass, e);
+                                // The class is not available for annotation 
scanning, but that must not fail the
+                                // whole context. Jakarta Servlet 6.1, section 
2.3.1 "Loading and Instantiation"
+                                // allows loading to be "delayed until the 
container determines the servlet is
+                                // needed to service a request", so a servlet 
whose class is absent may simply
+                                // never be used (or be registered 
programmatically). Let resolution happen later.
+                                logger.warning("Unable to load servlet class: 
" + servletClass);
                             } else {
                                 logger.error("servlet " + servletName + " has 
no servlet-class defined and is not a subclass of Application");
                             }
@@ -2294,9 +2299,11 @@ public class AnnotationDeployer implements 
DynamicDeployer {
                         final Class clazz = classLoader.loadClass(filterClass);
                         classes.add(clazz);
                     } catch (final ClassNotFoundException | 
NoClassDefFoundError e) {
-                        logger.debug("Could not load Servlet Filter class {1} 
for web module {2} / {3}",
+                        logger.debug("Could not load Servlet Filter class {0} 
for web module {1} / {2}",
                                      filterClass, webModule.getJarLocation(), 
webModule.getFile().getName());
-                        throw new OpenEJBException("Unable to load servlet 
filter class: " + filterClass, e);
+                        // A missing filter class must not fail the whole 
context; as with servlets
+                        // (Jakarta Servlet 6.1, section 2.3.1) an unresolved 
class is deferred, not fatal here.
+                        logger.warning("Unable to load servlet filter class: " 
+ filterClass);
                     }
                 }
             }
@@ -2311,9 +2318,11 @@ public class AnnotationDeployer implements 
DynamicDeployer {
                         final Class clazz = 
classLoader.loadClass(listenerClass);
                         classes.add(clazz);
                     } catch (final ClassNotFoundException | 
NoClassDefFoundError e) {
-                        logger.debug("Could not load Servlet listener class 
{1} for web module {2} / {3}",
+                        logger.debug("Could not load Servlet listener class 
{0} for web module {1} / {2}",
                                      listenerClass, 
webModule.getJarLocation(), webModule.getFile().getName());
-                        throw new OpenEJBException("Unable to load servlet 
listener class: " + listenerClass, e);
+                        // A missing listener class must not fail the whole 
context; as with servlets
+                        // (Jakarta Servlet 6.1, section 2.3.1) an unresolved 
class is deferred, not fatal here.
+                        logger.warning("Unable to load servlet listener class: 
" + listenerClass);
                     }
                 }
             }
@@ -2329,7 +2338,7 @@ public class AnnotationDeployer implements 
DynamicDeployer {
                             final Class clazz = 
classLoader.loadClass(listenerClass);
                             classes.add(clazz);
                         } catch (final ClassNotFoundException | 
NoClassDefFoundError e) {
-                            logger.debug("Could not load TagLib listener class 
{1} for web module {2} / {3}",
+                            logger.debug("Could not load TagLib listener class 
{0} for web module {1} / {2}",
                                          listenerClass, 
webModule.getJarLocation(), webModule.getFile().getName());
                             logger.error("Unable to load tag library servlet 
listener class: " + listenerClass);
                         }
diff --git 
a/container/openejb-core/src/test/java/org/apache/openejb/config/AnnotationDeployerTest.java
 
b/container/openejb-core/src/test/java/org/apache/openejb/config/AnnotationDeployerTest.java
index 9e547ac635..4f1dda0ea7 100644
--- 
a/container/openejb-core/src/test/java/org/apache/openejb/config/AnnotationDeployerTest.java
+++ 
b/container/openejb-core/src/test/java/org/apache/openejb/config/AnnotationDeployerTest.java
@@ -25,6 +25,9 @@ import org.apache.openejb.jee.ConfigProperty;
 import org.apache.openejb.jee.Connector;
 import org.apache.openejb.jee.EjbJar;
 import org.apache.openejb.jee.EnterpriseBean;
+import org.apache.openejb.jee.Filter;
+import org.apache.openejb.jee.Listener;
+import org.apache.openejb.jee.Servlet;
 import org.apache.openejb.jee.SessionBean;
 import org.apache.openejb.jee.TransactionSupportType;
 import org.apache.openejb.jee.WebApp;
@@ -496,6 +499,43 @@ public class AnnotationDeployerTest {
         }
     }
 
+    /**
+     * TOMEE-4642: a servlet, filter or listener class named in web.xml but 
not packaged in the war
+     * must not abort the deployment of the whole web module.
+     */
+    @Test
+    public void missingServletFilterAndListenerClassesDoNotFailDeployment() 
throws Exception {
+        final WebApp webApp = new WebApp();
+        webApp.setContextRoot("/");
+        webApp.setId("web");
+        webApp.setVersion("2.5");
+
+        final Servlet servlet = new Servlet();
+        servlet.setServletName("TestServlet1");
+        
servlet.setServletClass("org.apache.openejb.config.missing.TestServlet1");
+        webApp.getServlet().add(servlet);
+
+        final Filter filter = new Filter();
+        filter.setFilterName("AddFilterString");
+        
filter.setFilterClass("org.apache.openejb.config.missing.AddFilterString");
+        webApp.getFilter().add(filter);
+
+        final Listener listener = new Listener();
+        
listener.setListenerClass("org.apache.openejb.config.missing.MissingListener");
+        webApp.getListener().add(listener);
+
+        WebModule webModule = new WebModule(webApp, webApp.getContextRoot(), 
Thread.currentThread().getContextClassLoader(), "myapp", webApp.getId());
+        webModule.setFinder(new AnnotationFinder(new ClassesArchive()).link());
+
+        final AnnotationDeployer annotationDeployer = new AnnotationDeployer();
+        webModule = annotationDeployer.deploy(webModule);
+
+        // the declarations are kept as-is, they are simply not resolved for 
annotation scanning
+        assertEquals(1, webModule.getWebApp().getServlet().size());
+        assertEquals(1, webModule.getWebApp().getFilter().size());
+        assertEquals(1, webModule.getWebApp().getListener().size());
+    }
+
     @Test
     public void findRestClasses() throws Exception {
         final WebApp webApp = new WebApp();

Reply via email to