This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch CAMEL-26142-spring-ws-null-body in repository https://gitbox.apache.org/repos/asf/camel.git
commit 34b63f7e7cb1182017b1a05273f170f225a08daa Author: Claus Ibsen <[email protected]> AuthorDate: Mon Nov 2 17:18:22 2026 +0100 CAMEL-26142: camel-spring-ws - null reply body, non-XML reply body, and 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. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Claus Ibsen <[email protected]> --- .../camel/catalog/docs/spring-ws-component.adoc | 7 ++ .../src/main/docs/spring-ws-component.adoc | 7 ++ .../spring/ws/SpringWebserviceConsumer.java | 4 +- .../spring/ws/SpringWebserviceHelper.java | 22 ++++ .../spring/ws/SpringWebserviceMessage.java | 19 +++- .../spring/ws/SpringWebserviceProducer.java | 3 +- .../component/spring/ws/ConsumerReplyBodyTest.java | 126 +++++++++++++++++++++ .../spring/ws/ProducerLocalRouteTest.java | 30 +++++ .../spring/ws/ConsumerReplyBodyTest-context.xml | 29 +++++ .../spring/ws/ProducerLocalRouteTest-context.xml | 5 + .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc | 12 ++ 11 files changed, 261 insertions(+), 3 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..07a579ec9c9f 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,9 @@ 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` 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 +464,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..07a579ec9c9f 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,9 @@ 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` 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 +464,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..bcdddb115a36 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,11 @@ 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.support.builder.xml.XMLConverterHelper; import org.apache.camel.util.ObjectHelper; @@ -34,6 +40,22 @@ 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}. + * + * @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 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..5a0616899580 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,6 +64,7 @@ public class SpringWebserviceMessage extends DefaultMessage { public void setWebServiceMessage(WebServiceMessage webServiceMessage) { this.webServiceMessage = webServiceMessage; + this.bodySet = false; } @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..405b9f4e06e5 --- /dev/null +++ b/components/camel-spring-parent/camel-spring-ws/src/test/java/org/apache/camel/component/spring/ws/ConsumerReplyBodyTest.java @@ -0,0 +1,126 @@ +/* + * 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 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/}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..d0b763486679 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,9 @@ public class ProducerLocalRouteTest { @EndpointInject("mock:inOnly") private MockEndpoint inOnlyEndpoint; + @TempDir + private Path tempDir; + @Test public void consumeStockQuoteWebserviceWithDefaultTemplate() throws Exception { Object result = template.requestBody("direct:stockQuoteWebserviceWithDefaultTemplate", xmlRequestForGoogleStockQuote); @@ -85,6 +95,26 @@ 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 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..af4df97b7279 --- /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> \ No newline at end of file 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..a7559730977a 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,11 @@ <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&soapAction=http://www.stockquotes.edu/GetQuote"/> + <convertBodyTo type="java.lang.String"/> + </route> <!-- consumer route (providing the actual web service) --> <route> <from uri="spring-ws:soapaction:http://www.stockquotes.edu/GetQuote?endpointMapping=#endpointMapping"/> 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 1bc188419d61..3313b702ec84 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 @@ -3808,6 +3808,18 @@ 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. + +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 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`
