This is an automated email from the ASF dual-hosted git repository. rmaucher pushed a commit to branch 11.0.x in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit d10d57466d941af78373df7169de60e25df148d9 Author: opencode <[email protected]> AuthorDate: Thu Oct 15 10:44:16 2026 +0200 Throw InstanceNotFoundException from the no-JMX MBean server When JMX support is disabled with --no-jmx, Registry.getMBeanServer() returns the NoJmxMBeanServer whose methods returned null or silently did nothing where the MBeanServer contract requires an exception or a non-null value. Downstream code that trusted the contract then hit null pointer exceptions, for example Registry.getMethodInfo() dereferencing the null MBeanInfo returned by getMBeanInfo (reached through the Manager application JMX proxy) and the boolean unboxing of the null result of invoke in ManagerServlet.isDeployed() and tryAddServiced(). Since no MBean can ever be registered with this server, the truthful contract-compliant answer for any operation referencing an object name is InstanceNotFoundException, and the count of registered MBeans is zero. Update all such methods accordingly and return 0 from getMBeanCount. Methods without an InstanceNotFoundException throws clause that are unreachable through the overridden no-op Registry methods keep their previous behavior. Add a unit test covering the previously failing contract paths. --- .../tomcat/util/modeler/LocalStrings.properties | 2 + .../tomcat/util/modeler/NoDescriptorRegistry.java | 55 +++++++----- .../util/modeler/TestNoDescriptorRegistry.java | 100 +++++++++++++++++++++ 3 files changed, 137 insertions(+), 20 deletions(-) diff --git a/java/org/apache/tomcat/util/modeler/LocalStrings.properties b/java/org/apache/tomcat/util/modeler/LocalStrings.properties index dfbd545fe3..fe3238e3e9 100644 --- a/java/org/apache/tomcat/util/modeler/LocalStrings.properties +++ b/java/org/apache/tomcat/util/modeler/LocalStrings.properties @@ -40,6 +40,8 @@ managedMBean.noSet=Cannot find setter method [{0}] on resource [{1}] modules.digesterParseError=Error parsing registry data modules.readDescriptorsError=Error reading descriptors +noJmxMBeanServer.mBeanNotFound=The MBean [{0}] was not found. JMX support is disabled. + registry.cannotAccessRegistry=Guard object [{0}] does not allow access to the registry registry.createdServer=Created MBeanServer registry.existingServer=Using existing MBeanServer diff --git a/java/org/apache/tomcat/util/modeler/NoDescriptorRegistry.java b/java/org/apache/tomcat/util/modeler/NoDescriptorRegistry.java index 10bfce2c2f..e95dc7a23a 100644 --- a/java/org/apache/tomcat/util/modeler/NoDescriptorRegistry.java +++ b/java/org/apache/tomcat/util/modeler/NoDescriptorRegistry.java @@ -45,12 +45,16 @@ import javax.management.QueryExp; import javax.management.ReflectionException; import javax.management.loading.ClassLoaderRepository; +import org.apache.tomcat.util.res.StringManager; + /** * An implementation of the MBean registry that effectively disables MBean registration. This is typically used when low * memory footprint is a primary concern. */ public class NoDescriptorRegistry extends Registry { + private static final StringManager sm = StringManager.getManager(NoDescriptorRegistry.class); + /** * Constructs a new NoDescriptorRegistry instance. */ @@ -149,6 +153,16 @@ public class NoDescriptorRegistry extends Registry { private static class NoJmxMBeanServer implements MBeanServer { + /* + * No MBeans can be registered with this server, so any operation that references an object name fails with + * InstanceNotFoundException as required by the MBeanServer contract. Callers that relied on the previous null + * returns were subject to null pointer exceptions. + */ + + private static InstanceNotFoundException notFound(ObjectName name) { + return new InstanceNotFoundException(sm.getString("noJmxMBeanServer.mBeanNotFound", name)); + } + @Override public ObjectInstance createMBean(String className, ObjectName name) throws ReflectionException, InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException { @@ -189,13 +203,13 @@ public class NoDescriptorRegistry extends Registry { @Override public void unregisterMBean(ObjectName name) throws InstanceNotFoundException, MBeanRegistrationException { - + throw notFound(name); } @Override public ObjectInstance getObjectInstance(ObjectName name) throws InstanceNotFoundException { - return null; + throw notFound(name); } @@ -219,42 +233,43 @@ public class NoDescriptorRegistry extends Registry { @Override public Integer getMBeanCount() { - return null; + // No MBeans are ever registered with this server + return Integer.valueOf(0); } @Override public Object getAttribute(ObjectName name, String attribute) throws MBeanException, AttributeNotFoundException, InstanceNotFoundException, ReflectionException { - return null; + throw notFound(name); } @Override public AttributeList getAttributes(ObjectName name, String[] attributes) throws InstanceNotFoundException, ReflectionException { - return null; + throw notFound(name); } @Override public void setAttribute(ObjectName name, Attribute attribute) throws InstanceNotFoundException, AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException { - + throw notFound(name); } @Override public AttributeList setAttributes(ObjectName name, AttributeList attributes) throws InstanceNotFoundException, ReflectionException { - return null; + throw notFound(name); } @Override public Object invoke(ObjectName name, String operationName, Object[] params, String[] signature) throws InstanceNotFoundException, MBeanException, ReflectionException { - return null; + throw notFound(name); } @@ -273,35 +288,35 @@ public class NoDescriptorRegistry extends Registry { @Override public void addNotificationListener(ObjectName name, NotificationListener listener, NotificationFilter filter, Object handback) throws InstanceNotFoundException { - + throw notFound(name); } @Override public void addNotificationListener(ObjectName name, ObjectName listener, NotificationFilter filter, Object handback) throws InstanceNotFoundException { - + throw notFound(name); } @Override public void removeNotificationListener(ObjectName name, ObjectName listener) throws InstanceNotFoundException, ListenerNotFoundException { - + throw notFound(name); } @Override public void removeNotificationListener(ObjectName name, ObjectName listener, NotificationFilter filter, Object handback) throws InstanceNotFoundException, ListenerNotFoundException { - + throw notFound(name); } @Override public void removeNotificationListener(ObjectName name, NotificationListener listener) throws InstanceNotFoundException, ListenerNotFoundException { - + throw notFound(name); } @@ -309,20 +324,20 @@ public class NoDescriptorRegistry extends Registry { public void removeNotificationListener(ObjectName name, NotificationListener listener, NotificationFilter filter, Object handback) throws InstanceNotFoundException, ListenerNotFoundException { - + throw notFound(name); } @Override public MBeanInfo getMBeanInfo(ObjectName name) throws InstanceNotFoundException, IntrospectionException, ReflectionException { - return null; + throw notFound(name); } @Override public boolean isInstanceOf(ObjectName name, String className) throws InstanceNotFoundException { - return false; + throw notFound(name); } @@ -356,7 +371,7 @@ public class NoDescriptorRegistry extends Registry { @Override public ObjectInputStream deserialize(ObjectName name, byte[] data) throws InstanceNotFoundException, OperationsException { - return null; + throw notFound(name); } @@ -370,19 +385,19 @@ public class NoDescriptorRegistry extends Registry { @Override public ObjectInputStream deserialize(String className, ObjectName loaderName, byte[] data) throws InstanceNotFoundException, OperationsException, ReflectionException { - return null; + throw notFound(loaderName); } @Override public ClassLoader getClassLoaderFor(ObjectName mbeanName) throws InstanceNotFoundException { - return null; + throw notFound(mbeanName); } @Override public ClassLoader getClassLoader(ObjectName loaderName) throws InstanceNotFoundException { - return null; + throw notFound(loaderName); } diff --git a/test/org/apache/tomcat/util/modeler/TestNoDescriptorRegistry.java b/test/org/apache/tomcat/util/modeler/TestNoDescriptorRegistry.java new file mode 100644 index 0000000000..5bdca9987b --- /dev/null +++ b/test/org/apache/tomcat/util/modeler/TestNoDescriptorRegistry.java @@ -0,0 +1,100 @@ +/* + * 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.tomcat.util.modeler; + +import java.util.Collections; + +import javax.management.InstanceNotFoundException; +import javax.management.MBeanServer; +import javax.management.ObjectName; + +import org.junit.Assert; +import org.junit.Test; + +public class TestNoDescriptorRegistry { + + private static final ObjectName ONAME; + + static { + try { + ONAME = new ObjectName("Tomcat:type=Tester,name=NoJmx"); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Test + public void testGetMBeanInfoThrows() throws Exception { + MBeanServer server = new NoDescriptorRegistry().getMBeanServer(); + Assert.assertThrows(InstanceNotFoundException.class, () -> server.getMBeanInfo(ONAME)); + } + + @Test + public void testInvokeThrows() throws Exception { + MBeanServer server = new NoDescriptorRegistry().getMBeanServer(); + Assert.assertThrows(InstanceNotFoundException.class, + () -> server.invoke(ONAME, "isDeployed", new Object[] { "ctx" }, new String[] { "java.lang.String" })); + } + + @Test + public void testGetAttributeThrows() throws Exception { + MBeanServer server = new NoDescriptorRegistry().getMBeanServer(); + Assert.assertThrows(InstanceNotFoundException.class, () -> server.getAttribute(ONAME, "name")); + } + + @Test + public void testGetObjectInstanceThrows() throws Exception { + MBeanServer server = new NoDescriptorRegistry().getMBeanServer(); + Assert.assertThrows(InstanceNotFoundException.class, () -> server.getObjectInstance(ONAME)); + } + + @Test + public void testRegistryGetMethodInfoThrows() throws Exception { + Registry registry = new NoDescriptorRegistry(); + Assert.assertThrows(InstanceNotFoundException.class, () -> registry.getMethodInfo(ONAME, "test", 0)); + } + + @Test + public void testRegistryGetTypeInfoReturnsNull() throws Exception { + // Overridden by NoDescriptorRegistry, must not throw or fail + Registry registry = new NoDescriptorRegistry(); + Assert.assertNull(registry.getType(ONAME, "name")); + Assert.assertNull(registry.getMethodInfo(ONAME, "test")); + } + + @Test + public void testCountsAndQueries() throws Exception { + MBeanServer server = new NoDescriptorRegistry().getMBeanServer(); + Assert.assertEquals(Integer.valueOf(0), server.getMBeanCount()); + Assert.assertFalse(server.isRegistered(ONAME)); + Assert.assertTrue(server.queryNames(null, null).isEmpty()); + Assert.assertTrue(server.queryMBeans(null, null).isEmpty()); + Assert.assertEquals(0, server.getDomains().length); + } + + @Test + public void testUnregisterIsSilentlyIgnoredViaRegistry() throws Exception { + // unregisterComponent is a no-op for NoDescriptorRegistry and must not + // reach the MBeanServer methods that now throw + Registry registry = new NoDescriptorRegistry(); + registry.unregisterComponent(ONAME); + registry.unregisterComponent(ONAME.getCanonicalName()); + registry.registerComponent(new Object(), ONAME, null); + registry.registerComponent(new Object(), ONAME.getCanonicalName(), null); + registry.invoke(Collections.emptyList(), "test", false); + } +} --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
