This is an automated email from the ASF dual-hosted git repository. rmaucher pushed a commit to branch 9.0.x in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit 1ac7c47cc9c4f4bf13bcf6d15f50ef08678fcde0 Author: opencode <[email protected]> AuthorDate: Tue Sep 29 19:37:47 2026 +0200 Raise a BuildException when a JMX task or condition explicitly specifies a target that conflicts with an existing connection reference instead of silently ignoring it --- .../catalina/ant/jmx/JMXAccessorConditionBase.java | 9 ++- .../apache/catalina/ant/jmx/JMXAccessorTask.java | 80 +++++++++++++++++----- .../catalina/ant/jmx/TestJMXAccessorTask.java | 63 +++++++++++++++++ 3 files changed, 132 insertions(+), 20 deletions(-) diff --git a/java/org/apache/catalina/ant/jmx/JMXAccessorConditionBase.java b/java/org/apache/catalina/ant/jmx/JMXAccessorConditionBase.java index 084daf5b03..be21134b5d 100644 --- a/java/org/apache/catalina/ant/jmx/JMXAccessorConditionBase.java +++ b/java/org/apache/catalina/ant/jmx/JMXAccessorConditionBase.java @@ -38,8 +38,13 @@ public abstract class JMXAccessorConditionBase extends ProjectComponent implemen } private String url = null; - private String host = "localhost"; - private String port = "8050"; + /* + * A null host or port means the attribute was not specified. The defaults (localhost and 8050) are applied when + * the JMX service URL is built, so an explicitly specified value can be detected and compared against an existing + * connection reference. + */ + private String host = null; + private String port = null; private String password = null; private String username = null; private String name = null; diff --git a/java/org/apache/catalina/ant/jmx/JMXAccessorTask.java b/java/org/apache/catalina/ant/jmx/JMXAccessorTask.java index bcefcda81f..7f2a40261b 100644 --- a/java/org/apache/catalina/ant/jmx/JMXAccessorTask.java +++ b/java/org/apache/catalina/ant/jmx/JMXAccessorTask.java @@ -95,6 +95,12 @@ public class JMXAccessorTask extends BaseRedirectorHelperTask { */ public static final String JMX_SERVICE_SUFFIX = "/jmxrmi"; + /* + * Suffix of the project reference under which the JMX service URL of a connection stored under reference + * <em>refId</em> is recorded, to be able to detect later requests that explicitly specify a different target. + */ + private static final String JMX_SERVICE_URL_REF_SUFFIX = "-jmx-service-url"; + // ----------------------------------------------------- Instance Variables private String name = null; @@ -103,9 +109,14 @@ public class JMXAccessorTask extends BaseRedirectorHelperTask { private String url = null; - private String host = "localhost"; + /* + * A null host or port means the attribute was not specified. The defaults (localhost and 8050) are applied when + * the JMX service URL is built, so an explicitly specified value can be detected and compared against an existing + * connection reference. + */ + private String host = null; - private String port = "8050"; + private String port = null; private String password = null; @@ -436,8 +447,8 @@ public class JMXAccessorTask extends BaseRedirectorHelperTask { * * @param url URL to be used for the JMX connection (if specified, it is a complete URL so host and port will * not be used) - * @param host Host name of the JMX server - * @param port Port number for the JMX server + * @param host Host name of the JMX server ({@code null} selects the default host) + * @param port Port number for the JMX server ({@code null} selects the default port) * @param username User name for the connection * @param password Credentials corresponding to the specified user * @@ -450,12 +461,7 @@ public class JMXAccessorTask extends BaseRedirectorHelperTask { */ public static JMXConnector createJMXConnector(String url, String host, String port, String username, String password) throws MalformedURLException, IOException { - String urlForJMX; - if (url != null) { - urlForJMX = url; - } else { - urlForJMX = JMX_SERVICE_PREFIX + host + ":" + port + JMX_SERVICE_SUFFIX; - } + String urlForJMX = resolveJMXServiceURL(url, host, port); Map<String,String[]> environment = null; if (username != null && password != null) { String[] credentials = new String[2]; @@ -467,6 +473,30 @@ public class JMXAccessorTask extends BaseRedirectorHelperTask { return JMXConnectorFactory.connect(new JMXServiceURL(urlForJMX), environment); } + /* + * Build the JMX service URL from the connection parameters, applying the default host and port when they are not + * specified. + */ + private static String resolveJMXServiceURL(String url, String host, String port) { + if (url != null) { + return url; + } + String effectiveHost = host != null ? host : "localhost"; + String effectivePort = port != null ? port : "8050"; + return JMX_SERVICE_PREFIX + effectiveHost + ":" + effectivePort + JMX_SERVICE_SUFFIX; + } + + /* + * The JMX service URL targeted by the given connection parameters, or null when no target was specified at all + * (in which case the parameters say nothing about which server the caller wants to reach). + */ + private static String specifiedJMXServiceURL(String url, String host, String port) { + if (url == null && host == null && port == null) { + return null; + } + return resolveJMXServiceURL(url, host, port); + } + /** * test the if condition * @@ -515,18 +545,32 @@ public class JMXAccessorTask extends BaseRedirectorHelperTask { boolean isRef = project != null && refId != null && !refId.isEmpty(); if (isRef) { Object pref = project.getReference(refId); - try { - jmxServerConnection = (MBeanServerConnection) pref; - } catch (ClassCastException cce) { - project.log("wrong object reference " + refId + " - " + pref.getClass()); - return null; + if (pref != null) { + try { + jmxServerConnection = (MBeanServerConnection) pref; + } catch (ClassCastException cce) { + project.log("wrong object reference " + refId + " - " + pref.getClass()); + return null; + } + } + if (jmxServerConnection != null) { + // The reference already holds an open connection and is reused. If this call explicitly specifies a + // different target, the explicit parameters would be silently ignored, so fail instead. + String specified = specifiedJMXServiceURL(url, host, port); + Object openedTo = project.getReference(refId + JMX_SERVICE_URL_REF_SUFFIX); + if (specified != null && openedTo != null && !specified.equals(openedTo)) { + throw new BuildException("The JMX connection reference '" + refId + "' already refers to a " + + "connection to '" + openedTo + "'. This task specifies a connection to '" + specified + + "'. Open the second server under a different ref or omit the connection parameters " + + "to reuse the existing connection."); + } + return jmxServerConnection; } } - if (jmxServerConnection == null) { - jmxServerConnection = createJMXConnection(url, host, port, username, password); - } + jmxServerConnection = createJMXConnection(url, host, port, username, password); if (isRef && jmxServerConnection != null) { project.addReference(refId, jmxServerConnection); + project.addReference(refId + JMX_SERVICE_URL_REF_SUFFIX, resolveJMXServiceURL(url, host, port)); } return jmxServerConnection; } diff --git a/test/org/apache/catalina/ant/jmx/TestJMXAccessorTask.java b/test/org/apache/catalina/ant/jmx/TestJMXAccessorTask.java index b7104604d7..71927130c0 100644 --- a/test/org/apache/catalina/ant/jmx/TestJMXAccessorTask.java +++ b/test/org/apache/catalina/ant/jmx/TestJMXAccessorTask.java @@ -16,6 +16,14 @@ */ package org.apache.catalina.ant.jmx; +import java.lang.management.ManagementFactory; +import java.net.ServerSocket; +import java.rmi.registry.LocateRegistry; +import java.rmi.registry.Registry; +import java.rmi.server.UnicastRemoteObject; + +import javax.management.MBeanServer; +import javax.management.MBeanServerConnection; import javax.management.openmbean.CompositeDataSupport; import javax.management.openmbean.CompositeType; import javax.management.openmbean.OpenDataException; @@ -23,11 +31,15 @@ import javax.management.openmbean.OpenType; import javax.management.openmbean.SimpleType; import javax.management.openmbean.TabularDataSupport; import javax.management.openmbean.TabularType; +import javax.management.remote.JMXConnectorServer; +import javax.management.remote.JMXConnectorServerFactory; +import javax.management.remote.JMXServiceURL; import org.junit.Assert; import org.junit.Test; import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; public class TestJMXAccessorTask { @@ -89,6 +101,57 @@ public class TestJMXAccessorTask { } + /* + * A cached connection reference is reused. If the caller explicitly specifies a different target than the one the + * reference was opened to, a BuildException is raised instead of the explicit parameters being silently ignored. + */ + @Test + public void testAccessJMXConnectionReuseAndMismatch() throws Exception { + int port = getAvailablePort(); + Registry registry = LocateRegistry.createRegistry(port); + MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer(); + JMXServiceURL serviceUrl = new JMXServiceURL( + JMXAccessorTask.JMX_SERVICE_PREFIX + "localhost:" + port + JMXAccessorTask.JMX_SERVICE_SUFFIX); + JMXConnectorServer connectorServer = + JMXConnectorServerFactory.newJMXConnectorServer(serviceUrl, null, mbeanServer); + connectorServer.start(); + try { + Project project = new Project(); + String ref = "jmx.server.test"; + String host = "localhost"; + String openPort = Integer.toString(port); + + // First call opens the connection and stores it under the reference. + MBeanServerConnection first = + JMXAccessorTask.accessJMXConnection(project, null, host, openPort, null, null, ref); + Assert.assertNotNull(first); + + // Reuse with no explicit target: the cached connection is returned. + MBeanServerConnection reused = + JMXAccessorTask.accessJMXConnection(project, null, null, null, null, null, ref); + Assert.assertSame(first, reused); + + // Explicitly specifying a different port than the open connection must fail rather than be ignored. + try { + JMXAccessorTask.accessJMXConnection(project, null, host, "1", null, null, ref); + Assert.fail("Expected a BuildException for an explicit conflicting target"); + } catch (BuildException expected) { + // Expected + } + } finally { + connectorServer.stop(); + UnicastRemoteObject.unexportObject(registry, true); + } + } + + + private static int getAvailablePort() throws Exception { + try (ServerSocket socket = new ServerSocket(0)) { + return socket.getLocalPort(); + } + } + + private static TabularDataSupport createTabularData() throws OpenDataException { CompositeType detailsType = new CompositeType("details", "details", new String[] { "name" }, new String[] { "name" }, new OpenType<?>[] { SimpleType.STRING }); --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
