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

davsclaus 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 1ec55622dbb4 CAMEL-26142: camel-spring-ws - null reply body, non-XML 
reply body, and File/InputStream producer bodies (#28915)
1ec55622dbb4 is described below

commit 1ec55622dbb482a6bdd1ccbf7f011f36ba7232fe
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Nov 2 20:44:03 2026 +0100

    CAMEL-26142: camel-spring-ws - null reply body, non-XML reply body, and 
File/InputStream producer bodies (#28915)
    
    * CAMEL-26142: camel-spring-ws - null reply body, non-XML reply body, a…
    
    …nd File/InputStream producer bodies
    
    A consumer route that set the body to null in a processor replied with the 
request
    payload, as SpringWebserviceMessage created the body again from the 
request. The body
    is now only created from the request until it has been set.
    
    A consumer reply body that did not convert to Source gave an empty SOAP 
body; it now
    gives a SOAP fault with the conversion error. The producer and the consumer 
now send a
    File or a raw InputStream body as a StreamSource, as no type converter 
covers these.
    
    * CAMEL-26142: camel-spring-ws - file consumer bodies, and an OUT messa…
    
    …ge without the request payload
    
    The producer failed for the GenericFile body of a file consumer, as there 
is no File to
    Source converter; it now uses the content of a WrappedFile. A new message 
from
    SpringWebserviceMessage.newInstance(), such as the OUT message of 
exchange.getOut(),
    no longer creates its body from the request payload.
    
    * CAMEL-26142: camel-spring-ws - end the test Spring XML files with a n…
    
    …ewline
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../camel/catalog/docs/spring-ws-component.adoc    |   8 ++
 .../src/main/docs/spring-ws-component.adoc         |   8 ++
 .../spring/ws/SpringWebserviceConsumer.java        |   4 +-
 .../spring/ws/SpringWebserviceHelper.java          |  27 ++++
 .../spring/ws/SpringWebserviceMessage.java         |  24 +++-
 .../spring/ws/SpringWebserviceProducer.java        |   3 +-
 .../component/spring/ws/ConsumerReplyBodyTest.java | 137 +++++++++++++++++++++
 .../spring/ws/ProducerLocalRouteTest.java          |  45 +++++++
 .../spring/ws/ConsumerReplyBodyTest-context.xml    |  29 +++++
 .../spring/ws/ProducerLocalRouteTest-context.xml   |  13 +-
 .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc    |  13 ++
 11 files changed, 306 insertions(+), 5 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/spring-ws-component.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/spring-ws-component.adoc
index 8a54d2036e31..22eae0bb9d7f 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/spring-ws-component.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/spring-ws-component.adoc
@@ -134,6 +134,10 @@ template.requestBody("direct:example", "<foobar 
xmlns=\"http://foo.com\";><msg>te
 Remember if it's a SOAP service you're calling, you don't have to include
 SOAP tags. Spring-WS will perform the XML-to-SOAP marshaling.
 
+The body is sent as the payload of the request. It can be anything that 
converts to a `javax.xml.transform.Source`,
+such as XML in a `String` or `byte[]`, a DOM `Document`, a `File` (also from 
the `file` or `ftp` consumers) or an
+`InputStream`.
+
 === Sending SOAP and WS-Addressing action headers
 
 When a remote web service requires a SOAP action or use of the
@@ -461,6 +465,10 @@ comes in). Also remember to check out the
 Spring Web Services Example included in the
 Camel distribution.
 
+The body of the message at the end of the route is the payload of the reply, 
and it is converted to a
+`javax.xml.transform.Source` as for the producer. A `null` body gives an empty 
SOAP body. A body that does not convert,
+such as a `Map`, gives a SOAP fault with the conversion error.
+
 === Endpoint mapping in routes
 
 With the XML configuration in place, you can now use Camel's DSL to
diff --git 
a/components/camel-spring-parent/camel-spring-ws/src/main/docs/spring-ws-component.adoc
 
b/components/camel-spring-parent/camel-spring-ws/src/main/docs/spring-ws-component.adoc
index 8a54d2036e31..22eae0bb9d7f 100644
--- 
a/components/camel-spring-parent/camel-spring-ws/src/main/docs/spring-ws-component.adoc
+++ 
b/components/camel-spring-parent/camel-spring-ws/src/main/docs/spring-ws-component.adoc
@@ -134,6 +134,10 @@ template.requestBody("direct:example", "<foobar 
xmlns=\"http://foo.com\";><msg>te
 Remember if it's a SOAP service you're calling, you don't have to include
 SOAP tags. Spring-WS will perform the XML-to-SOAP marshaling.
 
+The body is sent as the payload of the request. It can be anything that 
converts to a `javax.xml.transform.Source`,
+such as XML in a `String` or `byte[]`, a DOM `Document`, a `File` (also from 
the `file` or `ftp` consumers) or an
+`InputStream`.
+
 === Sending SOAP and WS-Addressing action headers
 
 When a remote web service requires a SOAP action or use of the
@@ -461,6 +465,10 @@ comes in). Also remember to check out the
 Spring Web Services Example included in the
 Camel distribution.
 
+The body of the message at the end of the route is the payload of the reply, 
and it is converted to a
+`javax.xml.transform.Source` as for the producer. A `null` body gives an empty 
SOAP body. A body that does not convert,
+such as a `Map`, gives a SOAP fault with the conversion error.
+
 === Endpoint mapping in routes
 
 With the XML configuration in place, you can now use Camel's DSL to
diff --git 
a/components/camel-spring-parent/camel-spring-ws/src/main/java/org/apache/camel/component/spring/ws/SpringWebserviceConsumer.java
 
b/components/camel-spring-parent/camel-spring-ws/src/main/java/org/apache/camel/component/spring/ws/SpringWebserviceConsumer.java
index fe99670941d9..97a0c64d0347 100644
--- 
a/components/camel-spring-parent/camel-spring-ws/src/main/java/org/apache/camel/component/spring/ws/SpringWebserviceConsumer.java
+++ 
b/components/camel-spring-parent/camel-spring-ws/src/main/java/org/apache/camel/component/spring/ws/SpringWebserviceConsumer.java
@@ -44,6 +44,7 @@ import org.springframework.ws.soap.SoapMessage;
 import org.springframework.ws.soap.saaj.SaajSoapMessage;
 
 import static 
org.apache.camel.component.spring.ws.SpringWebserviceHelper.toResult;
+import static 
org.apache.camel.component.spring.ws.SpringWebserviceHelper.toSource;
 
 public class SpringWebserviceConsumer extends DefaultConsumer implements 
MessageEndpoint {
 
@@ -83,7 +84,8 @@ public class SpringWebserviceConsumer extends DefaultConsumer 
implements Message
             } else if (exchange.getPattern().isOutCapable()) {
                 Message responseMessage = exchange.getMessage(Message.class);
                 if (responseMessage != null) {
-                    Source responseBody = 
responseMessage.getBody(Source.class);
+                    // a null body gives an empty SOAP body, a body that is 
not XML fails with a fault
+                    Source responseBody = responseMessage.getBody() != null ? 
toSource(responseMessage) : null;
                     WebServiceMessage response = messageContext.getResponse();
                     configuration.getMessageFilter().filterConsumer(exchange, 
response);
                     toResult(responseBody, response.getPayloadResult());
diff --git 
a/components/camel-spring-parent/camel-spring-ws/src/main/java/org/apache/camel/component/spring/ws/SpringWebserviceHelper.java
 
b/components/camel-spring-parent/camel-spring-ws/src/main/java/org/apache/camel/component/spring/ws/SpringWebserviceHelper.java
index 394a1cb51cef..49f51176812f 100644
--- 
a/components/camel-spring-parent/camel-spring-ws/src/main/java/org/apache/camel/component/spring/ws/SpringWebserviceHelper.java
+++ 
b/components/camel-spring-parent/camel-spring-ws/src/main/java/org/apache/camel/component/spring/ws/SpringWebserviceHelper.java
@@ -16,6 +16,8 @@
  */
 package org.apache.camel.component.spring.ws;
 
+import java.io.File;
+import java.io.InputStream;
 import java.util.Properties;
 
 import javax.xml.transform.Result;
@@ -23,7 +25,12 @@ import javax.xml.transform.Source;
 import javax.xml.transform.Transformer;
 import javax.xml.transform.TransformerException;
 import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.stream.StreamSource;
 
+import org.apache.camel.InvalidPayloadException;
+import org.apache.camel.Message;
+import org.apache.camel.StreamCache;
+import org.apache.camel.WrappedFile;
 import org.apache.camel.support.builder.xml.XMLConverterHelper;
 import org.apache.camel.util.ObjectHelper;
 
@@ -34,6 +41,26 @@ public final class SpringWebserviceHelper {
     private SpringWebserviceHelper() {
     }
 
+    /**
+     * Returns the message body as a {@link Source}. A {@link File} or an 
{@link InputStream} (that is not stream
+     * cached) is wrapped in a {@link StreamSource}, as there is no type 
converter from these to {@link Source}. For a
+     * {@link WrappedFile} (such as from the file or ftp consumers) its 
content is used.
+     *
+     * @throws InvalidPayloadException if the body cannot be converted to 
{@link Source}
+     */
+    public static Source toSource(Message message) throws 
InvalidPayloadException {
+        Object body = message.getBody();
+        if (body instanceof WrappedFile<?> wrappedFile) {
+            body = wrappedFile.getBody();
+        }
+        if (body instanceof File file) {
+            return new StreamSource(file);
+        } else if (body instanceof InputStream is && !(body instanceof 
StreamCache)) {
+            return new StreamSource(is);
+        }
+        return message.getMandatoryBody(Source.class);
+    }
+
     public static void toResult(Source source, Result result) throws 
TransformerException {
         if (source != null) {
             XMLConverterHelper xml = new XMLConverterHelper();
diff --git 
a/components/camel-spring-parent/camel-spring-ws/src/main/java/org/apache/camel/component/spring/ws/SpringWebserviceMessage.java
 
b/components/camel-spring-parent/camel-spring-ws/src/main/java/org/apache/camel/component/spring/ws/SpringWebserviceMessage.java
index 49274a423268..854cda4fe8c6 100644
--- 
a/components/camel-spring-parent/camel-spring-ws/src/main/java/org/apache/camel/component/spring/ws/SpringWebserviceMessage.java
+++ 
b/components/camel-spring-parent/camel-spring-ws/src/main/java/org/apache/camel/component/spring/ws/SpringWebserviceMessage.java
@@ -17,11 +17,14 @@
 package org.apache.camel.component.spring.ws;
 
 import org.apache.camel.CamelContext;
+import org.apache.camel.spi.DataType;
 import org.apache.camel.support.DefaultMessage;
 import org.springframework.ws.WebServiceMessage;
 
 public class SpringWebserviceMessage extends DefaultMessage {
     private WebServiceMessage webServiceMessage;
+    // the body is created from the web service payload only until the body 
has been set (also to null)
+    private boolean bodySet;
 
     public SpringWebserviceMessage(CamelContext camelContext, 
WebServiceMessage webServiceMessage) {
         super(camelContext);
@@ -32,11 +35,24 @@ public class SpringWebserviceMessage extends DefaultMessage 
{
     public void reset() {
         super.reset();
         webServiceMessage = null;
+        bodySet = false;
+    }
+
+    @Override
+    public void setBody(Object body) {
+        super.setBody(body);
+        bodySet = true;
+    }
+
+    @Override
+    public void setBody(Object body, DataType type) {
+        super.setBody(body, type);
+        bodySet = true;
     }
 
     @Override
     protected Object createBody() {
-        if (webServiceMessage != null) {
+        if (webServiceMessage != null && !bodySet) {
             return webServiceMessage.getPayloadSource();
         }
         return null;
@@ -48,11 +64,15 @@ public class SpringWebserviceMessage extends DefaultMessage 
{
 
     public void setWebServiceMessage(WebServiceMessage webServiceMessage) {
         this.webServiceMessage = webServiceMessage;
+        this.bodySet = false;
     }
 
     @Override
     public DefaultMessage newInstance() {
-        return new SpringWebserviceMessage(getCamelContext(), 
webServiceMessage);
+        SpringWebserviceMessage answer = new 
SpringWebserviceMessage(getCamelContext(), webServiceMessage);
+        // a new message (such as the OUT message) starts without a body, and 
does not take the request payload
+        answer.bodySet = true;
+        return answer;
     }
 
     @Override
diff --git 
a/components/camel-spring-parent/camel-spring-ws/src/main/java/org/apache/camel/component/spring/ws/SpringWebserviceProducer.java
 
b/components/camel-spring-parent/camel-spring-ws/src/main/java/org/apache/camel/component/spring/ws/SpringWebserviceProducer.java
index f31558346062..6db5de51fe36 100644
--- 
a/components/camel-spring-parent/camel-spring-ws/src/main/java/org/apache/camel/component/spring/ws/SpringWebserviceProducer.java
+++ 
b/components/camel-spring-parent/camel-spring-ws/src/main/java/org/apache/camel/component/spring/ws/SpringWebserviceProducer.java
@@ -59,6 +59,7 @@ import 
org.springframework.ws.transport.http.HttpUrlConnection;
 import org.springframework.ws.transport.http.HttpUrlConnectionMessageSender;
 
 import static 
org.apache.camel.component.spring.ws.SpringWebserviceHelper.toResult;
+import static 
org.apache.camel.component.spring.ws.SpringWebserviceHelper.toSource;
 
 public class SpringWebserviceProducer extends DefaultProducer {
 
@@ -77,7 +78,7 @@ public class SpringWebserviceProducer extends DefaultProducer 
{
     @Override
     public void process(Exchange exchange) throws Exception {
         // Let Camel TypeConverter hierarchy handle the conversion of XML 
messages to Source objects
-        Source sourcePayload = exchange.getIn().getMandatoryBody(Source.class);
+        Source sourcePayload = toSource(exchange.getIn());
 
         // Extract optional headers
         String endpointUriHeader = 
exchange.getIn().getHeader(SpringWebserviceConstants.SPRING_WS_ENDPOINT_URI, 
String.class);
diff --git 
a/components/camel-spring-parent/camel-spring-ws/src/test/java/org/apache/camel/component/spring/ws/ConsumerReplyBodyTest.java
 
b/components/camel-spring-parent/camel-spring-ws/src/test/java/org/apache/camel/component/spring/ws/ConsumerReplyBodyTest.java
new file mode 100644
index 000000000000..dfe01296f0e4
--- /dev/null
+++ 
b/components/camel-spring-parent/camel-spring-ws/src/test/java/org/apache/camel/component/spring/ws/ConsumerReplyBodyTest.java
@@ -0,0 +1,137 @@
+/*
+ * 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.camel.component.spring.ws;
+
+import java.io.ByteArrayInputStream;
+import java.io.StringReader;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+
+import javax.xml.transform.stream.StreamSource;
+
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.spring.ws.bean.CamelEndpointMapping;
+import org.apache.camel.spi.Registry;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.apache.camel.test.spring.junit6.CamelSpringTest;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.ws.test.server.MockWebServiceClient;
+
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.springframework.ws.test.server.RequestCreators.withPayload;
+import static org.springframework.ws.test.server.ResponseMatchers.noFault;
+import static org.springframework.ws.test.server.ResponseMatchers.payload;
+import static 
org.springframework.ws.test.server.ResponseMatchers.serverOrReceiverFault;
+
+/**
+ * The reply of a spring-ws consumer route: a null body, a body that is not 
XML and an InputStream body.
+ */
+@ContextConfiguration
+@CamelSpringTest
+public class ConsumerReplyBodyTest extends CamelTestSupport {
+
+    private static final String REPLY = "<Reply 
xmlns=\"http://www.webserviceX.NET/\";><text>Hello</text></Reply>";
+
+    @Autowired
+    private CamelEndpointMapping endpointMapping;
+
+    @Autowired
+    private ApplicationContext applicationContext;
+
+    private MockWebServiceClient mockClient;
+
+    @BeforeEach
+    public void createClient() {
+        mockClient = MockWebServiceClient.createClient(applicationContext);
+    }
+
+    @Override
+    protected void bindToRegistry(Registry registry) throws Exception {
+        registry.bind("endpointMapping", this.endpointMapping);
+    }
+
+    @Test
+    public void nullBodyGivesEmptyResponse() throws Exception {
+        mockClient.sendRequest(withPayload(request("NullReply")))
+                .andExpect(noFault())
+                .andExpect((request, response) -> 
assertNull(response.getPayloadSource(),
+                        "A null reply body should give an empty SOAP body, not 
the request payload"));
+    }
+
+    @Test
+    public void nullBodyFromSetBodyGivesEmptyResponse() throws Exception {
+        mockClient.sendRequest(withPayload(request("SetBodyNullReply")))
+                .andExpect(noFault())
+                .andExpect((request, response) -> 
assertNull(response.getPayloadSource(),
+                        "A null reply body should give an empty SOAP body, not 
the request payload"));
+    }
+
+    @Test
+    public void outMessageWithoutBodyGivesEmptyResponse() throws Exception {
+        mockClient.sendRequest(withPayload(request("OutNoBodyReply")))
+                .andExpect(noFault())
+                .andExpect((request, response) -> 
assertNull(response.getPayloadSource(),
+                        "An OUT message without a body should give an empty 
SOAP body, not the request payload"));
+    }
+
+    @Test
+    public void bodyNotConvertibleToSourceGivesFault() throws Exception {
+        mockClient.sendRequest(withPayload(request("MapReply")))
+                .andExpect(serverOrReceiverFault());
+    }
+
+    @Test
+    public void inputStreamBodyIsSentAsReply() throws Exception {
+        mockClient.sendRequest(withPayload(request("StreamReply")))
+                .andExpect(noFault())
+                .andExpect(payload(new StreamSource(new StringReader(REPLY))));
+    }
+
+    private static StreamSource request(String name) {
+        return new StreamSource(
+                new StringReader("<" + name + " 
xmlns=\"http://www.webserviceX.NET/\";><symbol>GOOG</symbol></" + name + ">"));
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() throws Exception {
+        return new RouteBuilder() {
+
+            @Override
+            public void configure() throws Exception {
+                
from("spring-ws:rootqname:{http://www.webserviceX.NET/}NullReply?endpointMapping=#endpointMapping";)
+                        .process(e -> e.getMessage().setBody(null));
+
+                
from("spring-ws:rootqname:{http://www.webserviceX.NET/}SetBodyNullReply?endpointMapping=#endpointMapping";)
+                        .setBody(constant(null));
+
+                
from("spring-ws:rootqname:{http://www.webserviceX.NET/}OutNoBodyReply?endpointMapping=#endpointMapping";)
+                        .process(e -> e.getOut().setHeader("foo", "bar"));
+
+                
from("spring-ws:rootqname:{http://www.webserviceX.NET/}MapReply?endpointMapping=#endpointMapping";)
+                        .setBody(constant(Map.of("text", "Hello")));
+
+                
from("spring-ws:rootqname:{http://www.webserviceX.NET/}StreamReply?endpointMapping=#endpointMapping";)
+                        .process(e -> e.getMessage()
+                                .setBody(new 
ByteArrayInputStream(REPLY.getBytes(StandardCharsets.UTF_8))));
+            }
+        };
+    }
+}
diff --git 
a/components/camel-spring-parent/camel-spring-ws/src/test/java/org/apache/camel/component/spring/ws/ProducerLocalRouteTest.java
 
b/components/camel-spring-parent/camel-spring-ws/src/test/java/org/apache/camel/component/spring/ws/ProducerLocalRouteTest.java
index 64d06f468765..3a90d113985e 100644
--- 
a/components/camel-spring-parent/camel-spring-ws/src/test/java/org/apache/camel/component/spring/ws/ProducerLocalRouteTest.java
+++ 
b/components/camel-spring-parent/camel-spring-ws/src/test/java/org/apache/camel/component/spring/ws/ProducerLocalRouteTest.java
@@ -16,6 +16,12 @@
  */
 package org.apache.camel.component.spring.ws;
 
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
 import javax.xml.transform.Source;
 
 import org.apache.camel.EndpointInject;
@@ -27,6 +33,7 @@ import org.apache.camel.component.mock.MockEndpoint;
 import org.apache.camel.test.spring.junit6.CamelSpringTest;
 import org.apache.camel.util.xml.StringSource;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
 import org.springframework.test.context.ContextConfiguration;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -50,6 +57,12 @@ public class ProducerLocalRouteTest {
     @EndpointInject("mock:inOnly")
     private MockEndpoint inOnlyEndpoint;
 
+    @EndpointInject("mock:fileResult")
+    private MockEndpoint fileResultEndpoint;
+
+    @TempDir
+    private Path tempDir;
+
     @Test
     public void consumeStockQuoteWebserviceWithDefaultTemplate() throws 
Exception {
         Object result = 
template.requestBody("direct:stockQuoteWebserviceWithDefaultTemplate", 
xmlRequestForGoogleStockQuote);
@@ -85,6 +98,38 @@ public class ProducerLocalRouteTest {
         assertTrue(result instanceof Source);
     }
 
+    @Test
+    public void consumeStockQuoteWebserviceWithFileInput() throws Exception {
+        File file = tempDir.resolve("request.xml").toFile();
+        Files.writeString(file.toPath(), xmlRequestForGoogleStockQuote);
+
+        Object result = 
template.requestBody("direct:stockQuoteWebserviceAsString", file);
+
+        assertTrue(result instanceof String);
+        assertTrue(((String) result).contains("Google Inc."));
+    }
+
+    @Test
+    public void consumeStockQuoteWebserviceFromFileConsumer() throws Exception 
{
+        fileResultEndpoint.expectedMessageCount(1);
+        fileResultEndpoint.message(0).body().contains("Google Inc.");
+
+        Path dir = Path.of("target/data/spring-ws-file-input");
+        Files.createDirectories(dir);
+        Files.writeString(dir.resolve("request.xml"), 
xmlRequestForGoogleStockQuote);
+
+        fileResultEndpoint.assertIsSatisfied();
+    }
+
+    @Test
+    public void consumeStockQuoteWebserviceWithInputStreamInputNoStreamCache() 
throws Exception {
+        Object result = 
template.requestBody("direct:stockQuoteWebserviceNoStreamCache",
+                new 
ByteArrayInputStream(xmlRequestForGoogleStockQuote.getBytes(StandardCharsets.UTF_8)));
+
+        assertTrue(result instanceof String);
+        assertTrue(((String) result).contains("Google Inc."));
+    }
+
     @Test
     public void consumeStockQuoteWebserviceWithNonDefaultMessageFactory() 
throws Exception {
         Object result = 
template.requestBody("direct:stockQuoteWebserviceWithNonDefaultMessageFactory",
diff --git 
a/components/camel-spring-parent/camel-spring-ws/src/test/resources/org/apache/camel/component/spring/ws/ConsumerReplyBodyTest-context.xml
 
b/components/camel-spring-parent/camel-spring-ws/src/test/resources/org/apache/camel/component/spring/ws/ConsumerReplyBodyTest-context.xml
new file mode 100644
index 000000000000..84a8c610a205
--- /dev/null
+++ 
b/components/camel-spring-parent/camel-spring-ws/src/test/resources/org/apache/camel/component/spring/ws/ConsumerReplyBodyTest-context.xml
@@ -0,0 +1,29 @@
+<?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.
+
+-->
+<beans xmlns="http://www.springframework.org/schema/beans";
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+       xsi:schemaLocation="
+         http://www.springframework.org/schema/beans 
http://www.springframework.org/schema/beans/spring-beans.xsd
+         http://camel.apache.org/schema/spring 
http://camel.apache.org/schema/spring/camel-spring.xsd";>
+
+    <bean id="endpointMapping"
+          
class="org.apache.camel.component.spring.ws.bean.CamelEndpointMapping"/>
+
+</beans>
diff --git 
a/components/camel-spring-parent/camel-spring-ws/src/test/resources/org/apache/camel/component/spring/ws/ProducerLocalRouteTest-context.xml
 
b/components/camel-spring-parent/camel-spring-ws/src/test/resources/org/apache/camel/component/spring/ws/ProducerLocalRouteTest-context.xml
index 893cbae41e9d..95dc9e70c667 100644
--- 
a/components/camel-spring-parent/camel-spring-ws/src/test/resources/org/apache/camel/component/spring/ws/ProducerLocalRouteTest-context.xml
+++ 
b/components/camel-spring-parent/camel-spring-ws/src/test/resources/org/apache/camel/component/spring/ws/ProducerLocalRouteTest-context.xml
@@ -61,6 +61,17 @@
             <convertBodyTo type="java.lang.String"/>
             <to uri="mock:inOnly" />
         </route>
+        <route streamCache="false">
+            <from uri="direct:stockQuoteWebserviceNoStreamCache"/>
+            <to 
uri="spring-ws:http://localhost?webServiceTemplate=#webServiceTemplate&amp;soapAction=http://www.stockquotes.edu/GetQuote"/>
+            <convertBodyTo type="java.lang.String"/>
+        </route>
+        <route>
+            <from 
uri="file:target/data/spring-ws-file-input?initialDelay=0&amp;delay=100"/>
+            <to 
uri="spring-ws:http://localhost?webServiceTemplate=#webServiceTemplate&amp;soapAction=http://www.stockquotes.edu/GetQuote"/>
+            <convertBodyTo type="java.lang.String"/>
+            <to uri="mock:fileResult"/>
+        </route>
         <!-- consumer route (providing the actual web service) -->
         <route>
             <from 
uri="spring-ws:soapaction:http://www.stockquotes.edu/GetQuote?endpointMapping=#endpointMapping"/>
@@ -83,4 +94,4 @@
         </property>
     </bean>
 
-</beans>
\ No newline at end of file
+</beans>
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index a5bf98a4b023..96c851592919 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -3951,6 +3951,19 @@ The consumer now gives a message that its serializer 
cannot deserialize, such as
 route as a `byte[]`. Previously the message was dropped with a WARN. A failed 
exchange of the consumer is now handed to
 its exception handler.
 
+=== camel-spring-ws - the reply of a consumer, and File and stream bodies of 
the producer
+
+A `spring-ws` consumer route that sets the body to `null` in a processor, such 
as with
+`exchange.getMessage().setBody(null)`, now answers with an empty SOAP body. 
Previously the message created its body
+again from the request, so the request payload was sent back. Likewise, an OUT 
message that the route creates with
+`exchange.getOut()` no longer starts with the request payload as its body.
+
+A reply body that does not convert to a `javax.xml.transform.Source`, such as 
a `Map`, now gives a SOAP fault with the
+conversion error. Previously it gave an empty SOAP body. A reply body that is 
an `InputStream` is now sent as XML.
+
+The `spring-ws` producer now sends a `File` body, the file of a `file` or 
`ftp` consumer, and an `InputStream` body
+(also when stream caching is off) as XML. Previously these failed with an 
`InvalidPayloadException`.
+
 === camel-couchbase - the document id, the operation option and the bodies of 
CCB_PUT
 
 The `couchbase` producer now uses the `key` option as the document id when the 
message has no `CamelCouchbaseId`

Reply via email to