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

gnodet pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new 784189ea8970 CAMEL-23909: Fix flaky CxfConsumerPayLoadFaultMessageTest 
(#24426)
784189ea8970 is described below

commit 784189ea8970b61cb543c22bae329199b3c91043
Author: Guillaume Nodet <[email protected]>
AuthorDate: Sun Jul 5 21:11:49 2026 +0200

    CAMEL-23909: Fix flaky CxfConsumerPayLoadFaultMessageTest (#24426)
    
    * 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-23909: Fix namespace lookup in parseFaultCode
    
    Look up the namespace prefix from the codeElement rather than
    faultElement so that namespace declarations on the <faultcode>/<Code>
    element itself are also visible. lookupNamespaceURI walks up the tree,
    so declarations on ancestor elements (including <Fault>) are still
    found.
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
    
    ---------
    
    Co-authored-by: Claude Opus 4.6 <[email protected]>
---
 .../camel/component/cxf/jaxws/CxfConsumer.java     | 84 ++++++++++++++++++++++
 1 file changed, 84 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..505e8bda9a01 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,86 @@ 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(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 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);
+                // Look up from codeElement (not faultElement) so that 
namespace declarations
+                // on the <faultcode>/<Code> element itself are also visible
+                String namespaceURI = codeElement.lookupNamespaceURI(prefix);
+                if (namespaceURI != null) {
+                    return new QName(namespaceURI, localPart, prefix);
+                }
+                return new QName("", localPart, prefix);
+            }
+            return new QName("", codeText);
+        }
+
     }
 }

Reply via email to