Skip to content

Commit 784189e

Browse files
gnodetclaude
andauthored
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 <noreply@anthropic.com> * 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 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 105f362 commit 784189e

1 file changed

Lines changed: 84 additions & 0 deletions

File tree

  • components/camel-cxf/camel-cxf-soap/src/main/java/org/apache/camel/component/cxf/jaxws

components/camel-cxf/camel-cxf-soap/src/main/java/org/apache/camel/component/cxf/jaxws/CxfConsumer.java

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,23 +18,30 @@
1818

1919
import java.lang.reflect.Method;
2020
import java.util.HashMap;
21+
import java.util.List;
2122
import java.util.Map;
2223

2324
import jakarta.xml.ws.WebFault;
2425

26+
import javax.xml.namespace.QName;
27+
2528
import org.w3c.dom.Element;
29+
import org.w3c.dom.Node;
30+
import org.w3c.dom.NodeList;
2631

2732
import org.apache.camel.AsyncCallback;
2833
import org.apache.camel.ExchangePattern;
2934
import org.apache.camel.ExchangeTimedOutException;
3035
import org.apache.camel.Processor;
3136
import org.apache.camel.Suspendable;
3237
import org.apache.camel.component.cxf.common.CxfBinding;
38+
import org.apache.camel.component.cxf.common.CxfPayload;
3339
import org.apache.camel.component.cxf.common.DataFormat;
3440
import org.apache.camel.component.cxf.common.UnitOfWorkCloserInterceptor;
3541
import org.apache.camel.component.cxf.common.message.CxfConstants;
3642
import org.apache.camel.support.DefaultConsumer;
3743
import org.apache.camel.util.ObjectHelper;
44+
import org.apache.cxf.binding.soap.SoapFault;
3845
import org.apache.cxf.continuations.Continuation;
3946
import org.apache.cxf.continuations.ContinuationProvider;
4047
import org.apache.cxf.endpoint.Server;
@@ -402,9 +409,86 @@ private static Throwable extractFromBody(org.apache.camel.Exchange camelExchange
402409
Object body = camelExchange.getMessage().getBody();
403410
if (body instanceof Throwable throwable) {
404411
t = throwable;
412+
} else if (body instanceof CxfPayload<?> payload) {
413+
// Check if the CxfPayload contains a SOAP Fault element set directly as body
414+
t = extractFaultFromPayload(payload);
405415
}
406416
return t;
407417
}
408418

419+
/**
420+
* Detects a SOAP Fault element inside a CxfPayload body and converts it to a proper SoapFault. This handles the
421+
* case where a route sets a CxfPayload containing a raw {@code <soap:Fault>} XML element directly on the
422+
* message body, ensuring it is routed through CXF's standard fault handling path rather than the normal
423+
* response path.
424+
*/
425+
private static SoapFault extractFaultFromPayload(CxfPayload<?> payload) {
426+
List<Element> elements = payload.getBody();
427+
if (elements == null || elements.size() != 1) {
428+
return null;
429+
}
430+
Element element = elements.get(0);
431+
if (!"Fault".equals(element.getLocalName())) {
432+
return null;
433+
}
434+
String nsUri = element.getNamespaceURI();
435+
if (!"http://schemas.xmlsoap.org/soap/envelope/".equals(nsUri)
436+
&& !"http://www.w3.org/2003/05/soap-envelope".equals(nsUri)) {
437+
return null;
438+
}
439+
440+
// Extract faultcode, faultstring, and detail from the Fault element
441+
String faultString = null;
442+
QName faultCode = null;
443+
Element detail = null;
444+
445+
NodeList children = element.getChildNodes();
446+
for (int i = 0; i < children.getLength(); i++) {
447+
Node child = children.item(i);
448+
if (child.getNodeType() != Node.ELEMENT_NODE) {
449+
continue;
450+
}
451+
Element childElem = (Element) child;
452+
String localName = childElem.getLocalName();
453+
if ("faultcode".equals(localName) || "Code".equals(localName)) {
454+
faultCode = parseFaultCode(childElem);
455+
} else if ("faultstring".equals(localName) || "Reason".equals(localName)) {
456+
faultString = childElem.getTextContent();
457+
} else if ("detail".equals(localName) || "Detail".equals(localName)) {
458+
detail = childElem;
459+
}
460+
}
461+
462+
if (faultCode == null) {
463+
faultCode = new QName(nsUri, "Server");
464+
}
465+
SoapFault fault = new SoapFault(faultString != null ? faultString : "", faultCode);
466+
if (detail != null) {
467+
fault.setDetail(detail);
468+
}
469+
return fault;
470+
}
471+
472+
private static QName parseFaultCode(Element codeElement) {
473+
String codeText = codeElement.getTextContent();
474+
if (codeText == null || codeText.isBlank()) {
475+
return null;
476+
}
477+
codeText = codeText.trim();
478+
int colonIndex = codeText.indexOf(':');
479+
if (colonIndex > 0) {
480+
String prefix = codeText.substring(0, colonIndex);
481+
String localPart = codeText.substring(colonIndex + 1);
482+
// Look up from codeElement (not faultElement) so that namespace declarations
483+
// on the <faultcode>/<Code> element itself are also visible
484+
String namespaceURI = codeElement.lookupNamespaceURI(prefix);
485+
if (namespaceURI != null) {
486+
return new QName(namespaceURI, localPart, prefix);
487+
}
488+
return new QName("", localPart, prefix);
489+
}
490+
return new QName("", codeText);
491+
}
492+
409493
}
410494
}

0 commit comments

Comments
 (0)