This is an automated email from the ASF dual-hosted git repository.

coheigea pushed a commit to branch coheigea/CXF-7538
in repository https://gitbox.apache.org/repos/asf/cxf.git

commit af20d5b733310d7281e4baaa1077d448c372789f
Author: Colm O hEigeartaigh <[email protected]>
AuthorDate: Wed Oct 7 10:05:20 2026 +0100

    CXF-7538: Don't silently drop soap:header bindings when no serviceName is 
configured
---
 .../cxf/jaxws/implicitheader/EchoPortType.java     |  43 +++++++
 .../jaxws/implicitheader/ImplicitHeaderTest.java   | 132 +++++++++++++++++++++
 .../cxf/jaxws/implicitheader/implicit_header.wsdl  |  78 ++++++++++++
 .../cxf/wsdl/service/factory/Messages.properties   |   2 +
 .../factory/ReflectionServiceFactoryBean.java      |  93 ++++++++++++++-
 .../java/org/apache/cxf/wsdl11/Messages.properties |   3 +
 .../org/apache/cxf/wsdl11/WSDLServiceFactory.java  |  29 +++++
 7 files changed, 378 insertions(+), 2 deletions(-)

diff --git 
a/rt/frontend/jaxws/src/test/java/org/apache/cxf/jaxws/implicitheader/EchoPortType.java
 
b/rt/frontend/jaxws/src/test/java/org/apache/cxf/jaxws/implicitheader/EchoPortType.java
new file mode 100644
index 00000000000..077d3c988e6
--- /dev/null
+++ 
b/rt/frontend/jaxws/src/test/java/org/apache/cxf/jaxws/implicitheader/EchoPortType.java
@@ -0,0 +1,43 @@
+/**
+ * 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.cxf.jaxws.implicitheader;
+
+import jakarta.jws.WebMethod;
+import jakarta.jws.WebParam;
+import jakarta.jws.WebResult;
+import jakarta.jws.WebService;
+import jakarta.xml.ws.RequestWrapper;
+import jakarta.xml.ws.ResponseWrapper;
+
+/**
+ * Document/literal wrapped SEI (as generated by wsdl2java -exsh true) where 
the "auth" parameter
+ * maps to an implicit soap:header, i.e. one that is only declared on the WSDL 
binding.
+ */
+@WebService(name = "EchoPortType", targetNamespace = "urn:cxf:implicitheader")
+public interface EchoPortType {
+
+    @WebMethod(operationName = "echo")
+    @RequestWrapper(localName = "echo", targetNamespace = 
"urn:cxf:implicitheader")
+    @ResponseWrapper(localName = "echoResponse", targetNamespace = 
"urn:cxf:implicitheader")
+    @WebResult(name = "return", targetNamespace = "urn:cxf:implicitheader")
+    String echo(@WebParam(name = "in", targetNamespace = 
"urn:cxf:implicitheader") String in,
+                @WebParam(name = "auth", targetNamespace = 
"urn:cxf:implicitheader", partName = "auth",
+                          header = true)
+                String auth);
+}
diff --git 
a/rt/frontend/jaxws/src/test/java/org/apache/cxf/jaxws/implicitheader/ImplicitHeaderTest.java
 
b/rt/frontend/jaxws/src/test/java/org/apache/cxf/jaxws/implicitheader/ImplicitHeaderTest.java
new file mode 100644
index 00000000000..e1f08a50fb5
--- /dev/null
+++ 
b/rt/frontend/jaxws/src/test/java/org/apache/cxf/jaxws/implicitheader/ImplicitHeaderTest.java
@@ -0,0 +1,132 @@
+/**
+ * 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.cxf.jaxws.implicitheader;
+
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+
+import javax.xml.namespace.QName;
+
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.endpoint.Client;
+import org.apache.cxf.frontend.ClientProxy;
+import org.apache.cxf.jaxws.AbstractJaxWsTest;
+import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;
+import org.apache.cxf.jaxws.JaxWsServerFactoryBean;
+import org.apache.cxf.wsdl11.WSDLServiceFactory;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * A client created with a WSDL location but no service/endpoint name must use 
the binding defined in
+ * the WSDL, rather than silently generating a default one which drops the 
soap:header bindings.
+ */
+public class ImplicitHeaderTest extends AbstractJaxWsTest {
+
+    private static final String NS = "urn:cxf:implicitheader";
+    private static final String ADDRESS = "local://implicitheader";
+
+    @Test
+    public void testHeaderSentWithoutServiceName() throws Exception {
+        startServer();
+
+        JaxWsProxyFactoryBean factory = createClientFactory();
+        EchoPortType port = factory.create(EchoPortType.class);
+
+        Client client = ClientProxy.getClient(port);
+        assertEquals(new QName(NS, "ImplicitHeaderService"),
+                     client.getEndpoint().getService().getName());
+        assertEquals(new QName(NS, "ImplicitHeaderPort"),
+                     client.getEndpoint().getEndpointInfo().getName());
+        assertEquals("hello:secret", port.echo("hello", "secret"));
+    }
+
+    @Test
+    public void testHeaderSentWithServiceName() throws Exception {
+        startServer();
+
+        JaxWsProxyFactoryBean factory = createClientFactory();
+        factory.setServiceName(new QName(NS, "ImplicitHeaderService"));
+        factory.setEndpointName(new QName(NS, "ImplicitHeaderPort"));
+        EchoPortType port = factory.create(EchoPortType.class);
+
+        assertEquals("hello:secret", port.echo("hello", "secret"));
+    }
+
+    @Test
+    public void testWarningWhenWsdlBindingIgnored() throws Exception {
+        Logger logger = LogUtils.getL7dLogger(WSDLServiceFactory.class);
+        final StringBuilder warnings = new StringBuilder();
+        Handler handler = new Handler() {
+            @Override
+            public void publish(LogRecord record) {
+                if (record.getLevel() == Level.WARNING) {
+                    warnings.append(record.getMessage()).append(' ');
+                }
+            }
+
+            @Override
+            public void flush() {
+            }
+
+            @Override
+            public void close() {
+            }
+        };
+        logger.addHandler(handler);
+        try {
+            // an endpoint name that isn't in the WSDL, so the WSDL service 
can't be used
+            JaxWsProxyFactoryBean factory = createClientFactory();
+            factory.setEndpointName(new QName(NS, "UnknownPort"));
+            factory.create(EchoPortType.class);
+        } catch (RuntimeException ex) {
+            // expected, the port can't be found either
+        } finally {
+            logger.removeHandler(handler);
+        }
+        assertTrue(warnings.toString(), 
warnings.toString().contains("PARTIAL_WSDL_BINDING_IGNORED"));
+    }
+
+    private JaxWsProxyFactoryBean createClientFactory() {
+        JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
+        factory.setBus(getBus());
+        factory.setServiceClass(EchoPortType.class);
+        
factory.setWsdlURL(getClass().getResource("implicit_header.wsdl").toString());
+        factory.setAddress(ADDRESS);
+        return factory;
+    }
+
+    private void startServer() {
+        JaxWsServerFactoryBean svr = new JaxWsServerFactoryBean();
+        svr.setBus(getBus());
+        svr.setServiceClass(EchoPortType.class);
+        svr.setServiceBean(new EchoPortType() {
+            public String echo(String in, String auth) {
+                return in + ":" + auth;
+            }
+        });
+        svr.setAddress(ADDRESS);
+        svr.create();
+    }
+}
diff --git 
a/rt/frontend/jaxws/src/test/java/org/apache/cxf/jaxws/implicitheader/implicit_header.wsdl
 
b/rt/frontend/jaxws/src/test/java/org/apache/cxf/jaxws/implicitheader/implicit_header.wsdl
new file mode 100644
index 00000000000..f76fc491555
--- /dev/null
+++ 
b/rt/frontend/jaxws/src/test/java/org/apache/cxf/jaxws/implicitheader/implicit_header.wsdl
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  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.
+-->
+<!-- The service and port names deliberately differ from the defaults CXF 
derives from the SEI -->
+<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/";
+    xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/";
+    xmlns:xsd="http://www.w3.org/2001/XMLSchema";
+    xmlns:tns="urn:cxf:implicitheader"
+    name="ImplicitHeader" targetNamespace="urn:cxf:implicitheader">
+    <wsdl:types>
+        <xsd:schema targetNamespace="urn:cxf:implicitheader" 
elementFormDefault="qualified">
+            <xsd:element name="echo">
+                <xsd:complexType>
+                    <xsd:sequence>
+                        <xsd:element name="in" type="xsd:string"/>
+                    </xsd:sequence>
+                </xsd:complexType>
+            </xsd:element>
+            <xsd:element name="echoResponse">
+                <xsd:complexType>
+                    <xsd:sequence>
+                        <xsd:element name="return" type="xsd:string"/>
+                    </xsd:sequence>
+                </xsd:complexType>
+            </xsd:element>
+            <xsd:element name="auth" type="xsd:string"/>
+        </xsd:schema>
+    </wsdl:types>
+    <wsdl:message name="echoRequest">
+        <wsdl:part name="parameters" element="tns:echo"/>
+    </wsdl:message>
+    <wsdl:message name="echoResponse">
+        <wsdl:part name="parameters" element="tns:echoResponse"/>
+    </wsdl:message>
+    <wsdl:message name="requestHeader">
+        <wsdl:part name="auth" element="tns:auth"/>
+    </wsdl:message>
+    <wsdl:portType name="EchoPortType">
+        <wsdl:operation name="echo">
+            <wsdl:input message="tns:echoRequest"/>
+            <wsdl:output message="tns:echoResponse"/>
+        </wsdl:operation>
+    </wsdl:portType>
+    <wsdl:binding name="EchoBinding" type="tns:EchoPortType">
+        <soap:binding style="document" 
transport="http://schemas.xmlsoap.org/soap/http"/>
+        <wsdl:operation name="echo">
+            <soap:operation soapAction=""/>
+            <wsdl:input>
+                <soap:body parts="parameters" use="literal"/>
+                <soap:header message="tns:requestHeader" part="auth" 
use="literal"/>
+            </wsdl:input>
+            <wsdl:output>
+                <soap:body use="literal"/>
+            </wsdl:output>
+        </wsdl:operation>
+    </wsdl:binding>
+    <wsdl:service name="ImplicitHeaderService">
+        <wsdl:port name="ImplicitHeaderPort" binding="tns:EchoBinding">
+            <soap:address location="local://implicitheader"/>
+        </wsdl:port>
+    </wsdl:service>
+</wsdl:definitions>
diff --git 
a/rt/wsdl/src/main/java/org/apache/cxf/wsdl/service/factory/Messages.properties 
b/rt/wsdl/src/main/java/org/apache/cxf/wsdl/service/factory/Messages.properties
index 77e6105400f..21e162f8305 100644
--- 
a/rt/wsdl/src/main/java/org/apache/cxf/wsdl/service/factory/Messages.properties
+++ 
b/rt/wsdl/src/main/java/org/apache/cxf/wsdl/service/factory/Messages.properties
@@ -22,6 +22,8 @@ NO_METHOD_FOR_OP=No method was found for the WSDL operation 
{0}.
 INVALID_SCHEMA_URL=Could not load schema {0}
 ERROR_READING_SCHEMA=There was an error reading the schema {0}.
 COULD_NOT_FIND_PORTTYPE = Could not find portType named {0}
+USING_WSDL_SERVICE = Service {0} was not found in WSDL {1}, using service {2} 
and port {3} which implement portType {4}. \
+  Set the serviceName and endpointName explicitly to avoid this lookup.
 USING_PROXY_FOR_SERVICE = Service class: {0} is a java.lang.reflect.Proxy 
instance.  This is known not to work well as \
   annotations on the real instance are not available.  We suggest overriding 
the ServiceClass via spring config or \
   other configuration. (serviceClass/implementorClass attributes on the 
endpoint/server spring config entry)
diff --git 
a/rt/wsdl/src/main/java/org/apache/cxf/wsdl/service/factory/ReflectionServiceFactoryBean.java
 
b/rt/wsdl/src/main/java/org/apache/cxf/wsdl/service/factory/ReflectionServiceFactoryBean.java
index 7b32e2691ab..096f607829c 100644
--- 
a/rt/wsdl/src/main/java/org/apache/cxf/wsdl/service/factory/ReflectionServiceFactoryBean.java
+++ 
b/rt/wsdl/src/main/java/org/apache/cxf/wsdl/service/factory/ReflectionServiceFactoryBean.java
@@ -46,7 +46,10 @@ import java.util.concurrent.Executor;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
+import javax.wsdl.Binding;
+import javax.wsdl.Definition;
 import javax.wsdl.Operation;
+import javax.wsdl.Port;
 import javax.xml.namespace.QName;
 
 import org.w3c.dom.DOMError;
@@ -105,6 +108,7 @@ import org.apache.cxf.service.model.SchemaInfo;
 import org.apache.cxf.service.model.ServiceInfo;
 import org.apache.cxf.service.model.UnwrappedOperationInfo;
 import org.apache.cxf.wsdl.WSDLConstants;
+import org.apache.cxf.wsdl11.PartialWSDLProcessor;
 import org.apache.cxf.wsdl11.WSDLServiceBuilder;
 import org.apache.cxf.wsdl11.WSDLServiceFactory;
 import org.apache.ws.commons.schema.XmlSchema;
@@ -386,11 +390,15 @@ public class ReflectionServiceFactoryBean extends 
org.apache.cxf.service.factory
     protected void buildServiceFromWSDL(String url) {
         sendEvent(Event.CREATE_FROM_WSDL, url);
 
+        populateFromClass = false;
+        WSDLServiceFactory factory = new WSDLServiceFactory(getBus(), url);
+        if (getServiceQName(false) == null) {
+            selectServiceFromWSDL(factory.getDefinition(), url);
+        }
         if (LOG.isLoggable(Level.INFO)) {
             LOG.info("Creating Service " + getServiceQName() + " from WSDL: " 
+ url);
         }
-        populateFromClass = false;
-        WSDLServiceFactory factory = new WSDLServiceFactory(getBus(), url, 
getServiceQName());
+        factory.setServiceName(getServiceQName());
         boolean setEPName = true;
         if (features != null) {
             for (Feature f : features) {
@@ -425,6 +433,87 @@ public class ReflectionServiceFactoryBean extends 
org.apache.cxf.service.factory
         initializeDataBindings();
     }
 
+    /**
+     * No service name was configured, so the default one derived from the 
service class is used.
+     * If the WSDL doesn't contain a service with that name (so 
WSDLServiceFactory would treat it as a
+     * partial WSDL), but does contain exactly one service with port(s) bound 
to the portType of the
+     * service class, use that service (and port) rather than generating a 
default binding from the
+     * portType, which would silently lose everything the real binding defines 
(soap:header bindings,
+     * etc.).
+     */
+    private void selectServiceFromWSDL(Definition def, String url) {
+        QName defaultServiceName;
+        QName portTypeName;
+        try {
+            defaultServiceName = getServiceQName();
+            portTypeName = getInterfaceName();
+        } catch (RuntimeException ex) {
+            return;
+        }
+        if (def == null || portTypeName == null
+            || PartialWSDLProcessor.isServiceExisted(def, defaultServiceName)
+            || PartialWSDLProcessor.isBindingExisted(def, defaultServiceName)
+            || !PartialWSDLProcessor.isPortTypeExisted(def, 
defaultServiceName)) {
+            // only replace the "partial WSDL" handling in WSDLServiceFactory, 
where a default
+            // binding and service would otherwise be generated from the 
portType
+            return;
+        }
+
+        javax.wsdl.Service wsdlService = null;
+        List<Port> ports = null;
+        for (javax.wsdl.Service s : 
CastUtils.cast(def.getAllServices().values(), javax.wsdl.Service.class)) {
+            List<Port> matching = new ArrayList<>();
+            for (Port port : CastUtils.cast(s.getPorts().values(), 
Port.class)) {
+                Binding binding = port.getBinding();
+                if (binding != null && binding.getPortType() != null
+                    && portTypeName.equals(binding.getPortType().getQName())) {
+                    matching.add(port);
+                }
+            }
+            if (!matching.isEmpty()) {
+                if (wsdlService != null) {
+                    // more than one candidate service, we can't choose
+                    return;
+                }
+                wsdlService = s;
+                ports = matching;
+            }
+        }
+        if (wsdlService == null) {
+            return;
+        }
+
+        QName epName = getEndpointName(false);
+        boolean explicitEndpoint = epName != null;
+        if (!explicitEndpoint) {
+            try {
+                epName = getEndpointName();
+            } catch (RuntimeException ex) {
+                epName = null;
+            }
+        }
+        String epLocalName = epName == null ? null : epName.getLocalPart();
+        Port port = null;
+        for (Port p : ports) {
+            if (p.getName().equals(epLocalName)) {
+                port = p;
+            }
+        }
+        if (port == null) {
+            if (explicitEndpoint || ports.size() != 1) {
+                // the configured port isn't in this service, or the port to 
use is ambiguous
+                return;
+            }
+            port = ports.get(0);
+        }
+
+        QName serviceQName = wsdlService.getQName();
+        LOG.log(Level.INFO, "USING_WSDL_SERVICE",
+                new Object[] {defaultServiceName, url, serviceQName, 
port.getName(), portTypeName});
+        setServiceName(serviceQName);
+        setEndpointName(new QName(serviceQName.getNamespaceURI(), 
port.getName()));
+    }
+
     protected void buildServiceFromClass() {
         Object o = getBus().getProperty("requireExplicitContractLocation");
         if (o != null
diff --git a/rt/wsdl/src/main/java/org/apache/cxf/wsdl11/Messages.properties 
b/rt/wsdl/src/main/java/org/apache/cxf/wsdl11/Messages.properties
index c23e4aee848..12ce36691f1 100644
--- a/rt/wsdl/src/main/java/org/apache/cxf/wsdl11/Messages.properties
+++ b/rt/wsdl/src/main/java/org/apache/cxf/wsdl11/Messages.properties
@@ -27,6 +27,9 @@ EXTENSION_ADD_FAILED_MSG = Failed to add extension element.
 SERVICE_CREATION_MSG = Failed to create service.
 NO_SUCH_SERVICE_EXC = Could not find definition for service {0}.
 NO_SUCH_ENDPOINT_EXC = Could not find definition for port {0}.
+PARTIAL_WSDL_BINDING_IGNORED = Service {0} was not found in WSDL {1}, so a 
default SOAP binding is being generated \
+  for portType {2}. The existing binding(s) {3} for this portType, including 
any soap:header definitions, will be \
+  ignored. Set the serviceName and endpointName explicitly to use the binding 
from the WSDL.
 
 MISSING_DESTINATION_FACTORY = Cannot find the destination factory, check the 
port //wsdl:port[@name=''{0}'']
 MISSING_SERVICE= No definition of service {0} in the WSDL.
diff --git 
a/rt/wsdl/src/main/java/org/apache/cxf/wsdl11/WSDLServiceFactory.java 
b/rt/wsdl/src/main/java/org/apache/cxf/wsdl11/WSDLServiceFactory.java
index cd97791df83..2f857ccdacb 100644
--- a/rt/wsdl/src/main/java/org/apache/cxf/wsdl11/WSDLServiceFactory.java
+++ b/rt/wsdl/src/main/java/org/apache/cxf/wsdl11/WSDLServiceFactory.java
@@ -19,9 +19,11 @@
 
 package org.apache.cxf.wsdl11;
 
+import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.logging.Level;
 import java.util.logging.Logger;
 
 import javax.wsdl.Binding;
@@ -94,6 +96,10 @@ public class WSDLServiceFactory extends 
AbstractServiceFactoryBean {
         allowRefs = b;
     }
 
+    public void setServiceName(QName qn) {
+        serviceName = qn;
+    }
+
     public void setEndpointName(QName qn) {
         endpointName = qn;
     }
@@ -142,6 +148,7 @@ public class WSDLServiceFactory extends 
AbstractServiceFactoryBean {
                                 break;
                             }
                         }
+                        warnIfBindingIgnored(portType);
                         WSDLFactory factory = WSDLFactory.newInstance();
                         ExtensionRegistry extReg = 
factory.newPopulatedExtensionRegistry();
                         Binding binding = 
PartialWSDLProcessor.doAppendBinding(definition,
@@ -174,4 +181,26 @@ public class WSDLServiceFactory extends 
AbstractServiceFactoryBean {
         return service;
     }
 
+    /**
+     * The service could not be found, so a SOAP binding and service are about 
to be synthesized
+     * from the portType. If the WSDL already contains a binding for that 
portType, anything it
+     * declares beyond the soap:body (e.g. soap:header bindings) will be 
ignored, so warn about it.
+     */
+    private void warnIfBindingIgnored(PortType portType) {
+        if (portType == null || !LOG.isLoggable(Level.WARNING)) {
+            return;
+        }
+        List<QName> bindings = new ArrayList<>();
+        for (Binding b : CastUtils.cast(definition.getAllBindings().values(), 
Binding.class)) {
+            if (!b.isUndefined() && b.getPortType() != null
+                && portType.getQName().equals(b.getPortType().getQName())) {
+                bindings.add(b.getQName());
+            }
+        }
+        if (!bindings.isEmpty()) {
+            LOG.log(Level.WARNING, "PARTIAL_WSDL_BINDING_IGNORED",
+                    new Object[] {serviceName, wsdlUrl, portType.getQName(), 
bindings});
+        }
+    }
+
 }

Reply via email to