This is an automated email from the ASF dual-hosted git repository. markt-asf pushed a commit to branch 9.0.x in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit 7f9782e451c23005c5ce842c9a5f6899d83cb076 Author: Mark Thomas <[email protected]> AuthorDate: Tue Nov 3 18:37:23 2026 +0000 Review "Close the descriptor stream in the digester modeler source" Apply a broader fix that catches more error cases Co-authored by: Claude / Sonnet 5.5 --- .../tomcat/util/modeler/LocalStrings.properties | 1 + java/org/apache/tomcat/util/modeler/Registry.java | 32 ++++++-- .../modules/MbeansDescriptorsDigesterSource.java | 2 +- .../TestMbeansDescriptorsDigesterSource.java | 86 ++++++++++++++++++++++ 4 files changed, 115 insertions(+), 6 deletions(-) diff --git a/java/org/apache/tomcat/util/modeler/LocalStrings.properties b/java/org/apache/tomcat/util/modeler/LocalStrings.properties index ed806f5a5b..379659557d 100644 --- a/java/org/apache/tomcat/util/modeler/LocalStrings.properties +++ b/java/org/apache/tomcat/util/modeler/LocalStrings.properties @@ -43,6 +43,7 @@ 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.closeSourceError=Failed to close the source of the descriptors registry.createdServer=Created MBeanServer registry.existingServer=Using existing MBeanServer registry.initError=Error initializing [{0}] diff --git a/java/org/apache/tomcat/util/modeler/Registry.java b/java/org/apache/tomcat/util/modeler/Registry.java index 6a47fd6bfc..13e6c054e9 100644 --- a/java/org/apache/tomcat/util/modeler/Registry.java +++ b/java/org/apache/tomcat/util/modeler/Registry.java @@ -18,6 +18,7 @@ package org.apache.tomcat.util.modeler; import java.io.File; import java.io.FileInputStream; +import java.io.IOException; import java.io.InputStream; import java.lang.management.ManagementFactory; import java.net.URL; @@ -590,6 +591,12 @@ public class Registry implements RegistryMBean, MBeanRegistration { /** * Load descriptors. * + * <p> + * If the source is a {@link URL} or a {@link File}, the stream opened to read it is closed before this method + * returns. If the source is an {@link InputStream}, that stream is closed before this method returns as well. In + * both cases the stream is closed whether or not loading succeeds, so the caller must not use a stream passed to + * this method after the call. + * * @param sourceType The source type * @param source The bean * @param param A type to load @@ -637,12 +644,27 @@ public class Registry implements RegistryMBean, MBeanRegistration { throw new IllegalArgumentException(sm.getString("registry.invalidSource")); } - if (sourceType == null) { - sourceType = "MbeansDescriptorsDigesterSource"; + try { + if (sourceType == null) { + sourceType = "MbeansDescriptorsDigesterSource"; + } + ModelerSource ds = getModelerSource(sourceType); + + return ds.loadDescriptors(this, type, inputsource); + } finally { + // The source does not keep the stream. Close it here, whichever way loading ended, since this method opened + // it or was given it. + if (inputsource instanceof InputStream) { + try { + ((InputStream) inputsource).close(); + } catch (IOException e) { + // Do not hide the outcome of the load + if (log.isDebugEnabled()) { + log.debug(sm.getString("registry.closeSourceError"), e); + } + } + } } - ModelerSource ds = getModelerSource(sourceType); - - return ds.loadDescriptors(this, type, inputsource); } diff --git a/java/org/apache/tomcat/util/modeler/modules/MbeansDescriptorsDigesterSource.java b/java/org/apache/tomcat/util/modeler/modules/MbeansDescriptorsDigesterSource.java index e0d1c96525..2fb8fbb322 100644 --- a/java/org/apache/tomcat/util/modeler/modules/MbeansDescriptorsDigesterSource.java +++ b/java/org/apache/tomcat/util/modeler/modules/MbeansDescriptorsDigesterSource.java @@ -153,7 +153,7 @@ public class MbeansDescriptorsDigesterSource extends ModelerSource { digester = createDigester(); } - // Process the input file to configure our registry + // Process the input file to configure our registry. The stream is closed by the caller (Registry.load()). try { // Push our registry object onto the stack digester.push(loadedMbeans); diff --git a/test/org/apache/tomcat/util/modeler/modules/TestMbeansDescriptorsDigesterSource.java b/test/org/apache/tomcat/util/modeler/modules/TestMbeansDescriptorsDigesterSource.java index ca7a0e56d5..f230da83c2 100644 --- a/test/org/apache/tomcat/util/modeler/modules/TestMbeansDescriptorsDigesterSource.java +++ b/test/org/apache/tomcat/util/modeler/modules/TestMbeansDescriptorsDigesterSource.java @@ -17,10 +17,15 @@ package org.apache.tomcat.util.modeler.modules; import java.io.ByteArrayInputStream; +import java.io.File; import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLStreamHandler; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; @@ -81,6 +86,87 @@ public class TestMbeansDescriptorsDigesterSource { Assert.assertTrue(closed.get()); } + /* + * Registry.load() closes the stream whatever the outcome, including when the modeler source can't be created or + * rejects the source. + */ + @Test + public void testLoadClosesStreamWhenSourceTypeIsUnknown() throws Exception { + AtomicBoolean closed = new AtomicBoolean(); + Registry registry = Registry.getRegistry(null); + + Assert.assertThrows(ClassNotFoundException.class, + () -> registry.load("NoSuchModelerSource", createTrackingStream(XML, closed), null)); + + Assert.assertTrue(closed.get()); + } + + /* + * The stream that Registry.load() opens for a URL is closed too. + */ + @Test + public void testLoadClosesStreamOpenedForUrl() throws Exception { + AtomicBoolean closed = new AtomicBoolean(); + URL url = new URL(null, "memory:/mbeans-descriptors.xml", new URLStreamHandler() { + @Override + protected URLConnection openConnection(URL resource) { + return new URLConnection(resource) { + @Override + public void connect() { + // NO-OP + } + + @Override + public InputStream getInputStream() { + return createTrackingStream(XML, closed); + } + }; + } + }); + Registry registry = Registry.getRegistry(null); + + registry.load(null, url, null); + + Assert.assertTrue(closed.get()); + Assert.assertNotNull(registry.findManagedBean("TestDigesterSourceBean")); + } + + @Test + public void testLoadClosesStreamOpenedForFile() throws Exception { + File file = File.createTempFile("tomcat-test-", ".xml"); + try { + Files.writeString(file.toPath(), XML); + Registry registry = Registry.getRegistry(null); + + registry.load(null, file, null); + + Assert.assertNotNull(registry.findManagedBean("TestDigesterSourceBean")); + } finally { + // Can't be deleted on some platforms if the stream is still open + Assert.assertTrue(file.delete()); + } + } + + /* + * A failure to close the stream must not hide the outcome of the load. + */ + @Test + public void testFailureToCloseStreamDoesNotHideLoadFailure() throws Exception { + AtomicBoolean closed = new AtomicBoolean(); + InputStream stream = new FilterInputStream(new ByteArrayInputStream(XML.getBytes(StandardCharsets.UTF_8))) { + @Override + public void close() throws IOException { + closed.set(true); + throw new IOException("Close failed"); + } + }; + Registry registry = Registry.getRegistry(null); + + Assert.assertThrows(ClassNotFoundException.class, () -> registry.load("NoSuchModelerSource", stream, null)); + + Assert.assertTrue(closed.get()); + } + @Test public void testExecuteRequiresInputStreamSource() throws Exception { Registry registry = Registry.getRegistry(null); --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
