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 251751fcbbb4853f3ee25235d78cfa11a1119823 Author: opencode <[email protected]> AuthorDate: Wed Oct 14 13:28:54 2026 +0200 Store ContextService port-component refs outside generic properties The port-component-ref mappings of a <service-ref> (the Service Endpoint Interface to port-component-link associations) were stored in the generic properties map inherited from ResourceBase, via addPortcomponent() -> setProperty(). The same map also holds the <mapped-name> value, which MappedNameRule stores under the key mappedName for every resource that allows that element. Since getServiceendpoints() simply returned listProperties(), any <service-ref> that declared a <mapped-name> - which is legal per the Servlet specification - was reported as having a bogus service endpoint named mappedName, whose port-component-link was the mapped-name value. That bogus endpoint leaked to several consumers: * NamingContextListener.addService() bound a SERVICEENDPOINTINTERFACE/PORTCOMPONENTLINK pair for mappedName into the application's JNDI ServiceRef. * WebXml.toXml() serialized an extra <port-component-ref><service-endpoint-interface>mappedName </service-endpoint-interface>...</port-component-ref> element into the effective web.xml. * ContextService.toString() printed mappedName as an endpoint, as did the storeconfig output for a global ServiceRef. Port-component mappings now live in a dedicated map, kept separate from the generic properties, and are included in equals()/hashCode(). WebXml.toXml() reads the port link through getPortlink() rather than by treating the endpoint name as a property key; the mapped-name element continues to be read from the mappedName property. As a side effect, endpoint names are no longer emitted as generic attributes when a global <ServiceRef> is written by storeconfig. Tests: TestWebXml now checks that a service-ref with a mapped-name reports only its real endpoint, retains the mapped-name as a property, and serializes a single correct port-component-ref in the effective web.xml. --- .../tomcat/util/descriptor/web/ContextService.java | 15 ++++- .../apache/tomcat/util/descriptor/web/WebXml.java | 2 +- .../tomcat/util/descriptor/web/TestWebXml.java | 77 ++++++++++++++++++++++ 3 files changed, 90 insertions(+), 4 deletions(-) diff --git a/java/org/apache/tomcat/util/descriptor/web/ContextService.java b/java/org/apache/tomcat/util/descriptor/web/ContextService.java index 80c0852494..eef4825c47 100644 --- a/java/org/apache/tomcat/util/descriptor/web/ContextService.java +++ b/java/org/apache/tomcat/util/descriptor/web/ContextService.java @@ -265,6 +265,11 @@ public class ContextService extends ResourceBase { this.serviceqname[1] = localpart; } + /** + * The port component mappings, keyed by service endpoint interface. + */ + private final Map<String,String> portComponents = new HashMap<>(); + /** * Declares a client dependency on the container to resolving a Service Endpoint Interface to a WSDL port. It * optionally associates the Service Endpoint Interface with a particular port-component. @@ -272,7 +277,7 @@ public class ContextService extends ResourceBase { * @return the endpoint names iterator */ public Iterator<String> getServiceendpoints() { - return this.listProperties(); + return this.portComponents.keySet().iterator(); } /** @@ -282,7 +287,7 @@ public class ContextService extends ResourceBase { * @return the port link */ public String getPortlink(String serviceendpoint) { - return (String) this.getProperty(serviceendpoint); + return this.portComponents.get(serviceendpoint); } /** @@ -295,7 +300,7 @@ public class ContextService extends ResourceBase { if (portlink == null) { portlink = ""; } - this.setProperty(serviceendpoint, portlink); + this.portComponents.put(serviceendpoint, portlink); } /** @@ -407,6 +412,7 @@ public class ContextService extends ResourceBase { result = prime * result + handlers.hashCode(); result = prime * result + ((jaxrpcmappingfile == null) ? 0 : jaxrpcmappingfile.hashCode()); result = prime * result + ((largeIcon == null) ? 0 : largeIcon.hashCode()); + result = prime * result + portComponents.hashCode(); result = prime * result + ((serviceInterface == null) ? 0 : serviceInterface.hashCode()); result = prime * result + Arrays.hashCode(serviceqname); result = prime * result + ((smallIcon == null) ? 0 : smallIcon.hashCode()); @@ -451,6 +457,9 @@ public class ContextService extends ResourceBase { } else if (!largeIcon.equals(other.largeIcon)) { return false; } + if (!portComponents.equals(other.portComponents)) { + return false; + } if (serviceInterface == null) { if (other.serviceInterface != null) { return false; diff --git a/java/org/apache/tomcat/util/descriptor/web/WebXml.java b/java/org/apache/tomcat/util/descriptor/web/WebXml.java index a27943d2a3..4e63ef8454 100644 --- a/java/org/apache/tomcat/util/descriptor/web/WebXml.java +++ b/java/org/apache/tomcat/util/descriptor/web/WebXml.java @@ -1895,7 +1895,7 @@ public class WebXml { String endpoint = endpointIter.next(); sb.append(" <port-component-ref>\n"); appendElement(sb, INDENT6, "service-endpoint-interface", endpoint); - appendElement(sb, INDENT6, "port-component-link", serviceRef.getProperty(endpoint)); + appendElement(sb, INDENT6, "port-component-link", serviceRef.getPortlink(endpoint)); sb.append(" </port-component-ref>\n"); } Iterator<String> handlerIter = serviceRef.getHandlers(); diff --git a/test/org/apache/tomcat/util/descriptor/web/TestWebXml.java b/test/org/apache/tomcat/util/descriptor/web/TestWebXml.java index e92235d87b..43bacfe8b1 100644 --- a/test/org/apache/tomcat/util/descriptor/web/TestWebXml.java +++ b/test/org/apache/tomcat/util/descriptor/web/TestWebXml.java @@ -19,7 +19,11 @@ package org.apache.tomcat.util.descriptor.web; import java.io.File; import java.io.IOException; import java.io.StringReader; +import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; +import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.Set; @@ -792,4 +796,77 @@ public class TestWebXml { Assert.assertEquals(input, output); } + + + @Test + public void testServiceRefMappedNameIsNotAnEndpoint() throws Exception { + // The <mapped-name> of a <service-ref> is stored in the generic + // properties of ResourceBase. It must not be confused with the + // port-component-ref entries which were historically stored in the + // same map. + String input = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + + "<web-app xmlns=\"https://jakarta.ee/xml/ns/jakartaee\"\n" + + " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" + + " xsi:schemaLocation=\"https://jakarta.ee/xml/ns/jakartaee" + + " https://jakarta.ee/xml/ns/jakartaee/web-app_6_2.xsd\"\n" + + " version=\"6.2\"\n" + + " metadata-complete=\"true\">\n" + + "\n" + + " <service-ref>\n" + + " <service-ref-name>service/Test</service-ref-name>\n" + + " <service-interface>com.example.TestService</service-interface>\n" + + " <service-qname>test</service-qname>\n" + + " <port-component-ref>\n" + + " <service-endpoint-interface>com.example.Hello</service-endpoint-interface>\n" + + " <port-component-link>HelloLink</port-component-link>\n" + + " </port-component-ref>\n" + + " <mapped-name>service/TestMapped</mapped-name>\n" + + " </service-ref>\n" + + "\n" + + "</web-app>"; + + Digester digester = DigesterFactory.newDigester(true, true, new WebRuleSet(), true); + XmlErrorHandler handler = new XmlErrorHandler(); + digester.setErrorHandler(handler); + + InputSource is = new InputSource(new StringReader(input)); + WebXml webxml = new WebXml(); + digester.push(webxml); + digester.parse(is); + + Assert.assertEquals(0, handler.getErrors().size()); + Assert.assertEquals(0, handler.getWarnings().size()); + + ContextService service = webxml.getServiceRefs().get("service/Test"); + Assert.assertNotNull(service); + + // Only the real endpoint must be reported. + List<String> endpoints = new ArrayList<>(); + Iterator<String> iter = service.getServiceendpoints(); + while (iter.hasNext()) { + endpoints.add(iter.next()); + } + Assert.assertEquals(Collections.singletonList("com.example.Hello"), endpoints); + Assert.assertEquals("HelloLink", service.getPortlink("com.example.Hello")); + + // The mapped-name remains available as the only generic property. + List<String> properties = new ArrayList<>(); + Iterator<String> propIter = service.listProperties(); + while (propIter.hasNext()) { + properties.add(propIter.next()); + } + Assert.assertEquals(Collections.singletonList("mappedName"), properties); + Assert.assertEquals("service/TestMapped", service.getProperty("mappedName")); + + // The effective web.xml serialization must contain exactly the real + // port-component-ref and the mapped-name. + String output = webxml.toXml(); + int firstPortComponentRef = output.indexOf("<port-component-ref>"); + Assert.assertTrue(output, firstPortComponentRef >= 0); + Assert.assertTrue(output, + output.indexOf("<port-component-ref>", firstPortComponentRef + 1) < 0); + Assert.assertFalse(output, output.contains("<service-endpoint-interface>mappedName</service-endpoint-interface>")); + Assert.assertTrue(output, output.contains("<port-component-link>HelloLink</port-component-link>")); + Assert.assertTrue(output, output.contains("<mapped-name>service/TestMapped</mapped-name>")); + } } --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
