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 a14f643ee79a22e0ad82515b10a345c6bd52eb75 Author: opencode <[email protected]> AuthorDate: Sun Oct 25 17:55:39 2026 +0100 Fail the naming factory with NamingExceptions when the reference type is unusable GenericNamingResourcesFactory used the reference class name, the "type" attribute of a <Resource>, without validation. Nothing requires the type attribute, so a reference could carry a null class name and the factory then failed with a bare NullPointerException from Class.forName, or with an empty name with a ClassNotFoundException naming nothing, or with a bare NoSuchMethodException when the type named an interface or a class without a public no-argument constructor, the natural thing to write being type="javax.sql.DataSource". None of these diagnostics named the resource or explained what was wrong with its configuration. Validate the class name and translate the failure modes into NamingExceptions that name the reference and the class, following the container's BeanFactory: a missing or empty type reports that the type attribute is required, a class that cannot be loaded reports that it was not found, a class with no usable public no-argument constructor, including interfaces and abstract classes, reports that it cannot be created by the factory, and a constructor that throws reports that, while still propagating a fatal VirtualMachineError. As before, input that is not a Reference returns null. Add a test covering the null type, an unknown class, an interface and a throwing constructor. --- .../jdbc/naming/GenericNamingResourcesFactory.java | 49 ++++++++++++++++++-- .../tomcat/jdbc/naming/TestNamingFactory.java | 53 ++++++++++++++++++++++ 2 files changed, 97 insertions(+), 5 deletions(-) diff --git a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/naming/GenericNamingResourcesFactory.java b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/naming/GenericNamingResourcesFactory.java index b265c2a8c6..f0dfa54a31 100644 --- a/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/naming/GenericNamingResourcesFactory.java +++ b/modules/jdbc-pool/src/main/java/org/apache/tomcat/jdbc/naming/GenericNamingResourcesFactory.java @@ -29,6 +29,7 @@ import java.util.Set; import javax.naming.Context; import javax.naming.Name; +import javax.naming.NamingException; import javax.naming.RefAddr; import javax.naming.Reference; import javax.naming.spi.ObjectFactory; @@ -81,11 +82,49 @@ public class GenericNamingResourcesFactory implements ObjectFactory { Enumeration<RefAddr> refs = ref.getAll(); String type = ref.getClassName(); - Object o = - ClassLoaderUtil.loadClass( - type, - GenericNamingResourcesFactory.class.getClassLoader(), - Thread.currentThread().getContextClassLoader()).getConstructor().newInstance(); + if (type == null || type.isEmpty()) { + throw new NamingException( + "No resource type (the \"type\" attribute) is defined for reference [" + + name + "]; GenericNamingResourcesFactory needs the class of the " + + "object to create."); + } + Object o; + try { + Class<?> clazz = ClassLoaderUtil.loadClass( + type, + GenericNamingResourcesFactory.class.getClassLoader(), + Thread.currentThread().getContextClassLoader()); + o = clazz.getConstructor().newInstance(); + } catch (ClassNotFoundException e) { + NamingException ne = new NamingException( + "Class [" + type + "] of reference [" + name + "] was not found"); + ne.setRootCause(e); + throw ne; + } catch (InvocationTargetException e) { + // The class has a no-argument constructor, but it threw. Propagate + // only a fatal VirtualMachineError, in the spirit of + // ExceptionUtils.handleThrowable(), which tomcat-jdbc.jar cannot + // use directly. + Throwable cause = e.getCause(); + if (cause instanceof VirtualMachineError) { + throw (VirtualMachineError) cause; + } + NamingException ne = new NamingException("The constructor of class [" + + type + "] of reference [" + name + + "] threw an exception and the object cannot be created"); + ne.setRootCause(e); + throw ne; + } catch (ReflectiveOperationException e) { + // No public no-arg constructor (NoSuchMethodException), or the + // class cannot be instantiated (abstract, interface, illegal + // access). + NamingException ne = new NamingException("Class [" + type + + "] of reference [" + name + + "] has no usable public no-argument constructor and " + + "cannot be created by this factory"); + ne.setRootCause(e); + throw ne; + } while (refs.hasMoreElements()) { RefAddr addr = refs.nextElement(); diff --git a/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/naming/TestNamingFactory.java b/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/naming/TestNamingFactory.java index 860eabb00f..d8ab28892a 100644 --- a/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/naming/TestNamingFactory.java +++ b/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/naming/TestNamingFactory.java @@ -25,12 +25,14 @@ import java.util.logging.Logger; import javax.naming.CompositeName; import javax.naming.Name; +import javax.naming.NamingException; import javax.naming.Reference; import javax.naming.StringRefAddr; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; @@ -306,6 +308,57 @@ class TestNamingFactory extends JdbcPoolBaseTest { } } + @Test + void unusableClassNamesFailWithNamingExceptions() throws Exception { + GenericNamingResourcesFactory factory = + new GenericNamingResourcesFactory(); + Name refName = new CompositeName("jdbc/test"); + + // missing type attribute: a ResourceRef may carry a null class name + NamingException ne = assertThrows(NamingException.class, + () -> factory.getObjectInstance( + new Reference(null, null, null), refName, null, null)); + assertTrue(ne.getMessage().contains("type"), + "message must name the missing type attribute"); + + // unknown class: a NamingException naming the class, not a bare + // ClassNotFoundException with an empty name + ne = assertThrows(NamingException.class, + () -> factory.getObjectInstance(new Reference( + "com.example.NoSuchClass", null, null), + refName, null, null)); + assertTrue(ne.getMessage().contains("com.example.NoSuchClass"), + "message must name the missing class"); + assertInstanceOf(ClassNotFoundException.class, ne.getRootCause()); + + // interface: a NamingException naming the class, not a bare + // NoSuchMethodException + ne = assertThrows(NamingException.class, + () -> factory.getObjectInstance(new Reference( + "javax.sql.DataSource", null, null), + refName, null, null)); + assertTrue(ne.getMessage().contains("javax.sql.DataSource"), + "message must name the unusable class"); + assertInstanceOf(ReflectiveOperationException.class, + ne.getRootCause()); + // a class whose no-argument constructor exists but throws + ne = assertThrows(NamingException.class, + () -> factory.getObjectInstance(new Reference( + ThrowingCtorTarget.class.getName(), null, null), + refName, null, null)); + assertTrue(ne.getMessage().contains("constructor"), + "message must report the failing constructor"); + assertInstanceOf(java.lang.reflect.InvocationTargetException.class, + ne.getRootCause()); + } + + /** Target whose no-argument constructor throws. */ + public static class ThrowingCtorTarget { + public ThrowingCtorTarget() { + throw new IllegalStateException("boom"); + } + } + /** Plain configuration target for the generic naming factory. */ public static class NamingTarget { private String name; --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
