This is an automated email from the ASF dual-hosted git repository.
jungm pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tomee.git
The following commit(s) were added to refs/heads/main by this push:
new 1a3987670b TOMEE-4642 - Don't fail deployment when a
servlet/filter/listener class is missing (#2848)
1a3987670b is described below
commit 1a3987670b2870a040be27c4929d01f6d0f5d86e
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)
---
.../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 4326211d08..cb2e761105 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
@@ -2016,7 +2016,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());
/*
@@ -2041,7 +2041,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);
@@ -2058,7 +2058,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);
}
@@ -2189,7 +2189,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);
}
@@ -2263,10 +2263,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");
}
@@ -2294,9 +2302,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);
}
}
}
@@ -2311,9 +2326,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);
}
}
}
@@ -2329,7 +2348,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);
}
@@ -2346,7 +2365,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);
}
@@ -2376,8 +2395,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);
}
}
@@ -2926,7 +2949,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);
@@ -5693,7 +5716,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();