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}); + } + } + }
