This is an automated email from the ASF dual-hosted git repository.
coheigea pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cxf.git
The following commit(s) were added to refs/heads/main by this push:
new 09d44eeb8b5 CXF-7538: Don't silently drop soap:header bindings when no
serviceName is configured (#3550)
09d44eeb8b5 is described below
commit 09d44eeb8b59b078a3ebd18cd846898a7fbefc51
Author: Colm O hEigeartaigh <[email protected]>
AuthorDate: Wed Oct 7 13:43:08 2026 +0100
CXF-7538: Don't silently drop soap:header bindings when no serviceName is
configured (#3550)
---
.../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});
+ }
+ }
+
}