This is an automated email from the ASF dual-hosted git repository. gnodet pushed a commit to branch fix/CAMEL-23909 in repository https://gitbox.apache.org/repos/asf/camel.git
commit 4cc7369f62e3fcf13f70ceb5442923ef60c878b4 Author: Guillaume Nodet <[email protected]> AuthorDate: Sat Jul 4 17:47:46 2026 +0000 CAMEL-23909: Fix flaky CxfConsumerPayLoadFaultMessageTest Enhance CxfConsumer.extractFromBody() to detect CxfPayload bodies containing raw SOAP Fault XML elements and convert them to proper SoapFault exceptions. Previously, only bodies that were direct Throwable instances were recognized as faults. When a route set a CxfPayload containing a <soap:Fault> element directly on the message body (as in CxfConsumerPayLoadFaultMessageTest), the fault was not detected and went through CXF's normal response serialization path instead of the fault handling path, leading to intermittent failures. The fix adds extractFaultFromPayload() which checks for a single Element body with localName "Fault" in SOAP 1.1 or 1.2 namespace, extracts faultcode/faultstring/detail, and constructs a SoapFault that CXF can process through its standard fault handling pipeline. Co-Authored-By: Claude Opus 4.6 <[email protected]> --- .../camel/component/cxf/jaxws/CxfConsumer.java | 82 ++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/components/camel-cxf/camel-cxf-soap/src/main/java/org/apache/camel/component/cxf/jaxws/CxfConsumer.java b/components/camel-cxf/camel-cxf-soap/src/main/java/org/apache/camel/component/cxf/jaxws/CxfConsumer.java index 73c3d199da52..923a5553ce73 100644 --- a/components/camel-cxf/camel-cxf-soap/src/main/java/org/apache/camel/component/cxf/jaxws/CxfConsumer.java +++ b/components/camel-cxf/camel-cxf-soap/src/main/java/org/apache/camel/component/cxf/jaxws/CxfConsumer.java @@ -18,11 +18,16 @@ package org.apache.camel.component.cxf.jaxws; import java.lang.reflect.Method; import java.util.HashMap; +import java.util.List; import java.util.Map; import jakarta.xml.ws.WebFault; +import javax.xml.namespace.QName; + import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; import org.apache.camel.AsyncCallback; import org.apache.camel.ExchangePattern; @@ -30,11 +35,13 @@ import org.apache.camel.ExchangeTimedOutException; import org.apache.camel.Processor; import org.apache.camel.Suspendable; import org.apache.camel.component.cxf.common.CxfBinding; +import org.apache.camel.component.cxf.common.CxfPayload; import org.apache.camel.component.cxf.common.DataFormat; import org.apache.camel.component.cxf.common.UnitOfWorkCloserInterceptor; import org.apache.camel.component.cxf.common.message.CxfConstants; import org.apache.camel.support.DefaultConsumer; import org.apache.camel.util.ObjectHelper; +import org.apache.cxf.binding.soap.SoapFault; import org.apache.cxf.continuations.Continuation; import org.apache.cxf.continuations.ContinuationProvider; import org.apache.cxf.endpoint.Server; @@ -402,9 +409,84 @@ public class CxfConsumer extends DefaultConsumer implements Suspendable { Object body = camelExchange.getMessage().getBody(); if (body instanceof Throwable throwable) { t = throwable; + } else if (body instanceof CxfPayload<?> payload) { + // Check if the CxfPayload contains a SOAP Fault element set directly as body + t = extractFaultFromPayload(payload); } return t; } + /** + * Detects a SOAP Fault element inside a CxfPayload body and converts it to a proper SoapFault. This handles the + * case where a route sets a CxfPayload containing a raw {@code <soap:Fault>} XML element directly on the + * message body, ensuring it is routed through CXF's standard fault handling path rather than the normal + * response path. + */ + private static SoapFault extractFaultFromPayload(CxfPayload<?> payload) { + List<Element> elements = payload.getBody(); + if (elements == null || elements.size() != 1) { + return null; + } + Element element = elements.get(0); + if (!"Fault".equals(element.getLocalName())) { + return null; + } + String nsUri = element.getNamespaceURI(); + if (!"http://schemas.xmlsoap.org/soap/envelope/".equals(nsUri) + && !"http://www.w3.org/2003/05/soap-envelope".equals(nsUri)) { + return null; + } + + // Extract faultcode, faultstring, and detail from the Fault element + String faultString = null; + QName faultCode = null; + Element detail = null; + + NodeList children = element.getChildNodes(); + for (int i = 0; i < children.getLength(); i++) { + Node child = children.item(i); + if (child.getNodeType() != Node.ELEMENT_NODE) { + continue; + } + Element childElem = (Element) child; + String localName = childElem.getLocalName(); + if ("faultcode".equals(localName) || "Code".equals(localName)) { + faultCode = parseFaultCode(element, childElem); + } else if ("faultstring".equals(localName) || "Reason".equals(localName)) { + faultString = childElem.getTextContent(); + } else if ("detail".equals(localName) || "Detail".equals(localName)) { + detail = childElem; + } + } + + if (faultCode == null) { + faultCode = new QName(nsUri, "Server"); + } + SoapFault fault = new SoapFault(faultString != null ? faultString : "", faultCode); + if (detail != null) { + fault.setDetail(detail); + } + return fault; + } + + private static QName parseFaultCode(Element faultElement, Element codeElement) { + String codeText = codeElement.getTextContent(); + if (codeText == null || codeText.isBlank()) { + return null; + } + codeText = codeText.trim(); + int colonIndex = codeText.indexOf(':'); + if (colonIndex > 0) { + String prefix = codeText.substring(0, colonIndex); + String localPart = codeText.substring(colonIndex + 1); + String namespaceURI = faultElement.lookupNamespaceURI(prefix); + if (namespaceURI != null) { + return new QName(namespaceURI, localPart, prefix); + } + return new QName("", localPart, prefix); + } + return new QName("", codeText); + } + } }
