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

rzo1 pushed a commit to branch tomee-10.x
in repository https://gitbox.apache.org/repos/asf/tomee.git

commit d067de9e768606be1f264fbf50f80292ac6cb801
Author: Markus Jung <[email protected]>
AuthorDate: Fri Jul 31 16:12:57 2026 +0200

    TOMEE-4642 - Don't fail deployment when a servlet/filter/listener class is 
missing (#2848)
    
    (cherry picked from commit 1a3987670b2870a040be27c4929d01f6d0f5d86e)
---
 .../apache/openejb/config/AnnotationDeployer.java  | 53 ++++++++++----
 .../java/org/apache/openejb/config/WsDeployer.java | 16 ++++-
 .../openejb/web/LightweightWebAppBuilder.java      | 11 ++-
 .../openejb/config/AnnotationDeployerTest.java     | 81 ++++++++++++++++++++++
 4 files changed, 143 insertions(+), 18 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 391ef05b78..d50f32fce0 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
@@ -2054,7 +2054,7 @@ public class AnnotationDeployer implements 
DynamicDeployer {
                     buildAnnotatedRefs(client, annotationFinder, classLoader);
                 } catch (final ClassNotFoundException | NoClassDefFoundError 
e) {
 
-                    logger.debug("Could not load main class {1} for client 
module {2} / {3}",
+                    logger.debug("Could not load main class {0} for client 
module {1} / {2}",
                                  className, clientModule.getJarLocation(), 
clientModule.getFile().getName());
 
                     /*
@@ -2079,7 +2079,7 @@ public class AnnotationDeployer implements 
DynamicDeployer {
                     clazz = classLoader.loadClass(className);
                     remoteClients.add(clazz);
                 } catch (final ClassNotFoundException | NoClassDefFoundError 
e) {
-                    logger.debug("Could not load RemoteClient class {1} for 
client module {2} / {3}",
+                    logger.debug("Could not load RemoteClient class {0} for 
client module {1} / {2}",
                                  className, clientModule.getJarLocation(), 
clientModule.getFile().getName());
 
                     throw new OpenEJBException("Unable to load RemoteClient 
class: " + className, e);
@@ -2096,7 +2096,7 @@ public class AnnotationDeployer implements 
DynamicDeployer {
                 try {
                     clazz = classLoader.loadClass(className);
                 } catch (final ClassNotFoundException | NoClassDefFoundError 
e) {
-                    logger.debug("Could not load LocalClient class {1} for 
client module {2} / {3}",
+                    logger.debug("Could not load LocalClient class {0} for 
client module {1} / {2}",
                                  className, clientModule.getJarLocation(), 
clientModule.getFile().getName());
                     throw new OpenEJBException("Unable to load LocalClient 
class: " + className, e);
                 }
@@ -2227,7 +2227,7 @@ public class AnnotationDeployer implements 
DynamicDeployer {
                             clazz = classLoader.loadClass(application);
                             classes.add(clazz);
                         } catch (final ClassNotFoundException | 
NoClassDefFoundError e) {
-                            logger.debug("Could not load rest Application 
class {1} for module {2} / {3}",
+                            logger.debug("Could not load rest Application 
class {0} for module {1} / {2}",
                                          application, 
webModule.getJarLocation(), webModule.getFile().getName());
                             throw new OpenEJBException("Unable to load 
Application class: " + application, e);
                         }
@@ -2301,10 +2301,18 @@ 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.
+                                // Note this also skips @Resource/@EJB 
processing for that servlet, so if the
+                                // class does resolve later it comes up 
without its injections.
+                                logger.warning("Unable to load servlet class: 
" + servletClass + " for web module "
+                                               + webModule.getJarLocation(), 
e);
                             } else {
                                 logger.error("servlet " + servletName + " has 
no servlet-class defined and is not a subclass of Application");
                             }
@@ -2332,9 +2340,16 @@ 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);
+                        // Jakarta Servlet 6.1 section 6.2.1 requires the 
filter to be instantiated before a
+                        // request reaches a resource it is mapped to - i.e. 
later than this, but unlike a
+                        // servlet it cannot be skipped if it is ever used. So 
a missing class is very likely a
+                        // real packaging error; log it at error level, but 
keep it local to the filter rather
+                        // than failing the whole application (the spec's 
"must fail to deploy" rules cover
+                        // web-fragment ordering conflicts, not unresolvable 
component classes).
+                        logger.error("Unable to load servlet filter class: " + 
filterClass + " for web module "
+                                     + webModule.getJarLocation(), e);
                     }
                 }
             }
@@ -2349,9 +2364,13 @@ 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 declared listener is instantiated at application 
startup, so - as for filters
+                        // above - a missing class is very likely a real 
packaging error rather than something
+                        // that can be deferred. Report it at error level but 
keep the failure local.
+                        logger.error("Unable to load servlet listener class: " 
+ listenerClass + " for web module "
+                                     + webModule.getJarLocation(), e);
                     }
                 }
             }
@@ -2367,7 +2386,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);
                         }
@@ -2384,7 +2403,7 @@ public class AnnotationDeployer implements 
DynamicDeployer {
                             final Class clazz = 
classLoader.loadClass(tagClass);
                             classes.add(clazz);
                         } catch (final ClassNotFoundException | 
NoClassDefFoundError e) {
-                            logger.debug("Could not load tag class {1} for web 
module {2} / {3}",
+                            logger.debug("Could not load tag class {0} for web 
module {1} / {2}",
                                          tagClass, webModule.getJarLocation(), 
webModule.getFile().getName());
                             logger.error("Unable to load tag library tag 
class: " + tagClass);
                         }
@@ -2414,8 +2433,12 @@ public class AnnotationDeployer implements 
DynamicDeployer {
                                         final Class clazz = 
classLoader.loadClass(handlerClass);
                                         classes.add(clazz);
                                     } catch (final ClassNotFoundException | 
NoClassDefFoundError e) {
-                                        logger.debug("Could not load web 
service handler class {1} for web module {2} / {3}",
+                                        logger.debug("Could not load web 
service handler class {0} for web module {1} / {2}",
                                                      handlerClass, 
webModule.getJarLocation(), webModule.getFile().getName());
+                                        // Deliberately still fatal, unlike 
the servlet/filter/listener sites above:
+                                        // a handler chain silently missing a 
handler would leave the endpoint
+                                        // running with a different (weaker) 
contract than declared, e.g. dropping
+                                        // a security or logging handler, 
rather than just disabling one component.
                                         throw new OpenEJBException("Unable to 
load webservice handler class: " + handlerClass, e);
                                     }
                                 }
@@ -2964,7 +2987,7 @@ public class AnnotationDeployer implements 
DynamicDeployer {
                 try {
                     clazz = 
classLoader.loadClass(realClassName(interceptor.getInterceptorClass()));
                 } catch (final ClassNotFoundException e) {
-                    logger.debug("Could not load interceptor class {1} for 
enterprise beans module {2} / {3}",
+                    logger.debug("Could not load interceptor class {0} for 
enterprise beans module {1} / {2}",
                                  interceptor.getInterceptorClass(), 
ejbModule.getJarLocation(), ejbModule.getFile().getName());
 
                     throw new OpenEJBException("Unable to load interceptor 
class: " + interceptor.getInterceptorClass(), e);
@@ -5664,7 +5687,7 @@ public class AnnotationDeployer implements 
DynamicDeployer {
                     clazz = classLoader.loadClass(className);
                     classes.add(clazz);
                 } catch (final ClassNotFoundException e) {
-                    logger.debug("Could not load REST class {1} for web module 
{2} / {3}",
+                    logger.debug("Could not load REST class {0} for web module 
{1} / {2}",
                                  className, webModule.getJarLocation(), 
webModule.getFile().getName());
 
                     throw new OpenEJBException("Unable to load REST class: " + 
className, e);
diff --git 
a/container/openejb-core/src/main/java/org/apache/openejb/config/WsDeployer.java
 
b/container/openejb-core/src/main/java/org/apache/openejb/config/WsDeployer.java
index 476c35a587..a445c43276 100644
--- 
a/container/openejb-core/src/main/java/org/apache/openejb/config/WsDeployer.java
+++ 
b/container/openejb-core/src/main/java/org/apache/openejb/config/WsDeployer.java
@@ -146,8 +146,19 @@ public class WsDeployer implements DynamicDeployer {
                 continue;
             }
 
+            // TOMEE-4642: this runs for every servlet, not just webservice 
endpoints, so a servlet
+            // class the war does not package must not fail the whole 
deployment here either.
+            // NoClassDefFoundError is an Error, so the catch below would not 
cover it.
+            final Class<?> clazz;
+            try {
+                clazz = webModule.getClassLoader().loadClass(className);
+            } catch (final ClassNotFoundException | NoClassDefFoundError e) {
+                logger.warning("Unable to load servlet class: " + className + 
" for web module "
+                               + webModule.getJarLocation() + ", skipping 
webservice detection for it", e);
+                continue;
+            }
+
             try {
-                final Class<?> clazz = 
webModule.getClassLoader().loadClass(className);
                 if (JaxWsUtils.isWebService(clazz)) {
                     // add servlet mapping if not already declared
                     ServletMapping servletMapping = 
servletMappings.get(servlet.getServletName());
@@ -231,7 +242,8 @@ public class WsDeployer implements DynamicDeployer {
                     }
                 }
             } catch (final Exception e) {
-                throw new OpenEJBException("Unable to load servlet class: " + 
className, e);
+                // the class resolved fine, so this is a genuine webservice 
configuration problem
+                throw new OpenEJBException("Unable to configure webservice for 
servlet class: " + className, e);
             }
         }
     }
diff --git 
a/container/openejb-core/src/main/java/org/apache/openejb/web/LightweightWebAppBuilder.java
 
b/container/openejb-core/src/main/java/org/apache/openejb/web/LightweightWebAppBuilder.java
index 82afeef171..37e0c446c6 100644
--- 
a/container/openejb-core/src/main/java/org/apache/openejb/web/LightweightWebAppBuilder.java
+++ 
b/container/openejb-core/src/main/java/org/apache/openejb/web/LightweightWebAppBuilder.java
@@ -182,7 +182,16 @@ public class LightweightWebAppBuilder implements 
WebAppBuilder {
 
             // listeners
             for (final ListenerInfo listener : webAppInfo.listeners) {
-                final Class<?> clazz = 
webContext.getClassLoader().loadClass(listener.classname);
+                final Class<?> clazz;
+                try {
+                    clazz = 
webContext.getClassLoader().loadClass(listener.classname);
+                } catch (final ClassNotFoundException | NoClassDefFoundError 
e) {
+                    // TOMEE-4642: a listener class the war does not package 
is reported by the
+                    // deployer and must not abort the rest of the application 
here either.
+                    LOGGER.error("Unable to load listener class: " + 
listener.classname
+                                 + " for web application " + 
webAppInfo.contextRoot, e);
+                    continue;
+                }
                 final Object instance = webContext.newInstance(clazz);
                 if (ServletContextListener.class.isInstance(instance)) {
                     switchServletContextIfNeeded(sce.getServletContext(), new 
Runnable() {
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..5cd18d21b4 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
@@ -20,11 +20,15 @@ import org.apache.openejb.OpenEJB;
 import org.apache.openejb.assembler.classic.AppInfo;
 import org.apache.openejb.assembler.classic.Assembler;
 import org.apache.openejb.assembler.classic.ClientInfo;
+import org.apache.openejb.assembler.classic.WebAppInfo;
 import org.apache.openejb.jee.AssemblyDescriptor;
 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;
@@ -82,6 +86,7 @@ import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.CoreMatchers.notNullValue;
 import static org.hamcrest.CoreMatchers.nullValue;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 
@@ -496,6 +501,82 @@ 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());
+    }
+
+    /**
+     * TOMEE-4642: same as above, but driven through the whole deployer chain 
rather than
+     * AnnotationDeployer alone. WsDeployer is part of that chain whenever 
wsdl4j is on the
+     * classpath (Plus, Plume, openejb-standalone) and loads every servlet 
class before testing
+     * whether it is a webservice, so it used to fail the deployment on those 
distributions even
+     * with AnnotationDeployer fixed.
+     */
+    @Test
+    public void missingServletClassDoesNotFailFullConfiguration() 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);
+
+        final WebModule webModule = new WebModule(webApp, 
webApp.getContextRoot(),
+                Thread.currentThread().getContextClassLoader(), "myapp", 
webApp.getId());
+        webModule.setFinder(new AnnotationFinder(new ClassesArchive()).link());
+
+        final WebAppInfo webAppInfo = new 
ConfigurationFactory().configureApplication(webModule);
+
+        assertNotNull(webAppInfo);
+        assertEquals(1, webAppInfo.servlets.size());
+        assertEquals("TestServlet1", 
webAppInfo.servlets.iterator().next().servletName);
+    }
+
     @Test
     public void findRestClasses() throws Exception {
         final WebApp webApp = new WebApp();

Reply via email to