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 177686ba2acc CAMEL-26225: camel-arangodb - SAVE_DOCUMENT inserts a
Collection body as several documents, and a producer without the operation
option fails to start (#29068)
177686ba2acc is described below
commit 177686ba2acc3cd01af203a73c9f5d0f48cf42be
Author: Andrea Cosentino <[email protected]>
AuthorDate: Tue Nov 3 12:12:33 2026 +0100
CAMEL-26225: camel-arangodb - SAVE_DOCUMENT inserts a Collection body as
several documents, and a producer without the operation option fails to start
(#29068)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
.../apache/camel/catalog/components/arangodb.json | 4 +-
.../camel/catalog/docs/arangodb-component.adoc | 6 +
.../apache/camel/component/arangodb/arangodb.json | 4 +-
.../src/main/docs/arangodb-component.adoc | 6 +
.../camel/component/arangodb/ArangoDbEndpoint.java | 18 +-
.../camel/component/arangodb/ArangoDbProducer.java | 6 +-
.../component/arangodb/ArangoDbProducerTest.java | 195 +++++++++++++++++++++
.../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc | 10 ++
8 files changed, 239 insertions(+), 10 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/arangodb.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/arangodb.json
index 95771e01e237..7b993b243d96 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/arangodb.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/arangodb.json
@@ -50,8 +50,8 @@
"CamelArangoDbAqlOptions": { "index": 7, "kind": "header", "displayName":
"", "group": "advanced", "label": "advanced", "required": false, "javaType":
"com.arangodb.model.AqlQueryOptions", "deprecated": false, "deprecationNote":
"", "autowired": false, "secret": false, "description": "The additional options
that will be passed to the query API.", "constantName":
"org.apache.camel.component.arangodb.ArangoDbConstants#AQL_QUERY_OPTIONS" }
},
"bodyTypes": {
- "producerIn": { "carriers": [ "map", "pojo", "list" ], "byOption": [ {
"option": "operation", "value": "FIND_DOCUMENT_BY_KEY", "carriers": [ "text" ]
}, { "option": "operation", "value": "DELETE_DOCUMENT", "carriers": [ "text",
"list" ] }, { "option": "operation", "value": "AQL_QUERY", "carriers": [ "text"
] }, { "option": "operation", "value": "SAVE_VERTEX", "carriers": [ "map",
"pojo" ] }, { "option": "operation", "value": "UPDATE_VERTEX", "carriers": [
"map", "pojo" ] }, { "option [...]
- "producerOut": { "carriers": [ "unknown" ], "byOption": [ { "option":
"operation", "value": "SAVE_DOCUMENT", "carriers": [ "pojo" ] }, { "option":
"operation", "value": "FIND_DOCUMENT_BY_KEY", "carriers": [ "pojo", "null" ] },
{ "option": "operation", "value": "UPDATE_DOCUMENT", "carriers": [ "pojo" ] },
{ "option": "operation", "value": "DELETE_DOCUMENT", "carriers": [ "pojo" ] },
{ "option": "operation", "value": "AQL_QUERY", "carriers": [ "list" ] }, {
"option": "operation", "valu [...]
+ "producerIn": { "carriers": [ "map", "pojo", "list" ], "byOption": [ {
"option": "operation", "value": "FIND_DOCUMENT_BY_KEY", "carriers": [ "text" ]
}, { "option": "operation", "value": "DELETE_DOCUMENT", "carriers": [ "text",
"list" ] }, { "option": "operation", "value": "AQL_QUERY", "carriers": [ "text"
] }, { "option": "operation", "value": "SAVE_VERTEX", "carriers": [ "map",
"pojo" ] }, { "option": "operation", "value": "UPDATE_VERTEX", "carriers": [
"map", "pojo" ] }, { "option [...]
+ "producerOut": { "carriers": [ "unknown" ], "byOption": [ { "option":
"operation", "value": "SAVE_DOCUMENT", "carriers": [ "pojo" ] }, { "option":
"operation", "value": "FIND_DOCUMENT_BY_KEY", "carriers": [ "pojo", "null" ] },
{ "option": "operation", "value": "UPDATE_DOCUMENT", "carriers": [ "pojo" ] },
{ "option": "operation", "value": "DELETE_DOCUMENT", "carriers": [ "pojo" ] },
{ "option": "operation", "value": "AQL_QUERY", "carriers": [ "list" ] }, {
"option": "operation", "valu [...]
},
"properties": {
"database": { "index": 0, "kind": "path", "displayName": "Database",
"group": "producer", "label": "", "required": true, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "database name" },
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/arangodb-component.adoc
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/arangodb-component.adoc
index 5c2f11e54c3a..9f5e4f6ec1d0 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/arangodb-component.adoc
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/arangodb-component.adoc
@@ -40,6 +40,9 @@ include::partial$component-endpoint-options.adoc[]
include::partial$component-endpoint-headers.adoc[]
// component options: END
+The `operation` option must be set, on the endpoint or on the component
configuration: there is no default
+operation, and a route that sends to an arangodb endpoint without it fails to
start.
+
== Examples
=== Producer Examples
@@ -90,6 +93,9 @@ myObject.addAttribute("a", "Foo");
myObject.addAttribute("b", 42);
----
+A `Collection` body, such as a `List` of documents, saves each of them as a
document, with or without the
+`CamelArangoDbMultiInsert` header, and the body after the operation is a
`MultiDocumentEntity`.
+
==== Query a collection
[tabs]
diff --git
a/components/camel-arangodb/src/generated/resources/META-INF/org/apache/camel/component/arangodb/arangodb.json
b/components/camel-arangodb/src/generated/resources/META-INF/org/apache/camel/component/arangodb/arangodb.json
index 95771e01e237..7b993b243d96 100644
---
a/components/camel-arangodb/src/generated/resources/META-INF/org/apache/camel/component/arangodb/arangodb.json
+++
b/components/camel-arangodb/src/generated/resources/META-INF/org/apache/camel/component/arangodb/arangodb.json
@@ -50,8 +50,8 @@
"CamelArangoDbAqlOptions": { "index": 7, "kind": "header", "displayName":
"", "group": "advanced", "label": "advanced", "required": false, "javaType":
"com.arangodb.model.AqlQueryOptions", "deprecated": false, "deprecationNote":
"", "autowired": false, "secret": false, "description": "The additional options
that will be passed to the query API.", "constantName":
"org.apache.camel.component.arangodb.ArangoDbConstants#AQL_QUERY_OPTIONS" }
},
"bodyTypes": {
- "producerIn": { "carriers": [ "map", "pojo", "list" ], "byOption": [ {
"option": "operation", "value": "FIND_DOCUMENT_BY_KEY", "carriers": [ "text" ]
}, { "option": "operation", "value": "DELETE_DOCUMENT", "carriers": [ "text",
"list" ] }, { "option": "operation", "value": "AQL_QUERY", "carriers": [ "text"
] }, { "option": "operation", "value": "SAVE_VERTEX", "carriers": [ "map",
"pojo" ] }, { "option": "operation", "value": "UPDATE_VERTEX", "carriers": [
"map", "pojo" ] }, { "option [...]
- "producerOut": { "carriers": [ "unknown" ], "byOption": [ { "option":
"operation", "value": "SAVE_DOCUMENT", "carriers": [ "pojo" ] }, { "option":
"operation", "value": "FIND_DOCUMENT_BY_KEY", "carriers": [ "pojo", "null" ] },
{ "option": "operation", "value": "UPDATE_DOCUMENT", "carriers": [ "pojo" ] },
{ "option": "operation", "value": "DELETE_DOCUMENT", "carriers": [ "pojo" ] },
{ "option": "operation", "value": "AQL_QUERY", "carriers": [ "list" ] }, {
"option": "operation", "valu [...]
+ "producerIn": { "carriers": [ "map", "pojo", "list" ], "byOption": [ {
"option": "operation", "value": "FIND_DOCUMENT_BY_KEY", "carriers": [ "text" ]
}, { "option": "operation", "value": "DELETE_DOCUMENT", "carriers": [ "text",
"list" ] }, { "option": "operation", "value": "AQL_QUERY", "carriers": [ "text"
] }, { "option": "operation", "value": "SAVE_VERTEX", "carriers": [ "map",
"pojo" ] }, { "option": "operation", "value": "UPDATE_VERTEX", "carriers": [
"map", "pojo" ] }, { "option [...]
+ "producerOut": { "carriers": [ "unknown" ], "byOption": [ { "option":
"operation", "value": "SAVE_DOCUMENT", "carriers": [ "pojo" ] }, { "option":
"operation", "value": "FIND_DOCUMENT_BY_KEY", "carriers": [ "pojo", "null" ] },
{ "option": "operation", "value": "UPDATE_DOCUMENT", "carriers": [ "pojo" ] },
{ "option": "operation", "value": "DELETE_DOCUMENT", "carriers": [ "pojo" ] },
{ "option": "operation", "value": "AQL_QUERY", "carriers": [ "list" ] }, {
"option": "operation", "valu [...]
},
"properties": {
"database": { "index": 0, "kind": "path", "displayName": "Database",
"group": "producer", "label": "", "required": true, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "database name" },
diff --git a/components/camel-arangodb/src/main/docs/arangodb-component.adoc
b/components/camel-arangodb/src/main/docs/arangodb-component.adoc
index 5c2f11e54c3a..9f5e4f6ec1d0 100644
--- a/components/camel-arangodb/src/main/docs/arangodb-component.adoc
+++ b/components/camel-arangodb/src/main/docs/arangodb-component.adoc
@@ -40,6 +40,9 @@ include::partial$component-endpoint-options.adoc[]
include::partial$component-endpoint-headers.adoc[]
// component options: END
+The `operation` option must be set, on the endpoint or on the component
configuration: there is no default
+operation, and a route that sends to an arangodb endpoint without it fails to
start.
+
== Examples
=== Producer Examples
@@ -90,6 +93,9 @@ myObject.addAttribute("a", "Foo");
myObject.addAttribute("b", 42);
----
+A `Collection` body, such as a `List` of documents, saves each of them as a
document, with or without the
+`CamelArangoDbMultiInsert` header, and the body after the operation is a
`MultiDocumentEntity`.
+
==== Query a collection
[tabs]
diff --git
a/components/camel-arangodb/src/main/java/org/apache/camel/component/arangodb/ArangoDbEndpoint.java
b/components/camel-arangodb/src/main/java/org/apache/camel/component/arangodb/ArangoDbEndpoint.java
index 937a9b7debdb..1a709db8b0eb 100644
---
a/components/camel-arangodb/src/main/java/org/apache/camel/component/arangodb/ArangoDbEndpoint.java
+++
b/components/camel-arangodb/src/main/java/org/apache/camel/component/arangodb/ArangoDbEndpoint.java
@@ -65,8 +65,10 @@ import org.apache.camel.util.ObjectHelper;
@BodyType.When(option = "operation",
value = "DELETE_EDGE",
carriers =
Carrier.TEXT) },
description = "By the operation: the
document to save or update as a Map or a POJO (a"
- + " List of them with the
CamelArangoDbMultiInsert or CamelArangoDbMultiUpdate"
- + " header), the key as text
to find or delete (a List of keys with"
+ + " Collection of them saves
several, with or without the"
+ + " CamelArangoDbMultiInsert
header, and a List of them with the"
+ + " CamelArangoDbMultiUpdate
header updates several), the key as text to find"
+ + " or delete (a List of keys
with"
+ " CamelArangoDbMultiDelete),
and the AQL query as text unless the"
+ " CamelArangoDbAqlQuery
header has it. JSON text, bytes or a stream is not a"
+ " document: unmarshal it to
a Map first. The key of an update is the"
@@ -99,9 +101,11 @@ import org.apache.camel.util.ObjectHelper;
javaType =
"com.arangodb.entity.EdgeUpdateEntity"),
@BodyType.When(option =
"operation", value = "DELETE_EDGE", carriers = Carrier.VALUE,
javaType =
"java.lang.Boolean") },
- description = "By the operation, which must
be set: the result entity of the ArangoDB"
+ description = "By the operation, which must
be set (the producer fails to start"
+ + " without it): the result
entity of the ArangoDB"
+ " driver
(DocumentCreateEntity, DocumentUpdateEntity,"
- + " DocumentDeleteEntity, or
a MultiDocumentEntity for the multi headers)"
+ + " DocumentDeleteEntity, or
a MultiDocumentEntity for the multi headers"
+ + " and a Collection saved)"
+ " for the document writes;
the document found as a BaseDocument (or"
+ " BaseEdgeDocument), or of
the class in the CamelArangoDbResultClassType"
+ " header, or null when
there is none; a List of the AQL results, empty for"
@@ -145,6 +149,12 @@ public class ArangoDbEndpoint extends DefaultEndpoint
implements EndpointService
}
public Producer createProducer() {
+ // there is no default operation, nor a header for it: fail when the
route starts, not on each message
+ if (configuration.getOperation() == null) {
+ throw new IllegalArgumentException(
+ "The operation option must be set on the arangodb
endpoint, or on the component configuration,"
+ + " for example
operation=SAVE_DOCUMENT: the producer has no default operation");
+ }
return new ArangoDbProducer(this);
}
diff --git
a/components/camel-arangodb/src/main/java/org/apache/camel/component/arangodb/ArangoDbProducer.java
b/components/camel-arangodb/src/main/java/org/apache/camel/component/arangodb/ArangoDbProducer.java
index 7e0e7a5e924d..26bc657dd8c9 100644
---
a/components/camel-arangodb/src/main/java/org/apache/camel/component/arangodb/ArangoDbProducer.java
+++
b/components/camel-arangodb/src/main/java/org/apache/camel/component/arangodb/ArangoDbProducer.java
@@ -120,8 +120,10 @@ public class ArangoDbProducer extends DefaultProducer {
ArangoCollection collection = calculateDocumentCollection();
boolean isMultiInsert =
exchange.getMessage().getHeader(MULTI_INSERT, false, Boolean.class);
- // save multiple document
- if (isMultiInsert) {
+ // save multiple documents: with the header, or for a
Collection body, which ArangoDB always inserts
+ // as several documents (insertDocument would insert them,
then fail to read the reply, so a retry
+ // would insert them again)
+ if (isMultiInsert || exchange.getMessage().getBody()
instanceof Collection) {
Collection<Object> objects =
exchange.getMessage().getMandatoryBody(Collection.class);
return collection.insertDocuments(objects);
}
diff --git
a/components/camel-arangodb/src/test/java/org/apache/camel/component/arangodb/ArangoDbProducerTest.java
b/components/camel-arangodb/src/test/java/org/apache/camel/component/arangodb/ArangoDbProducerTest.java
new file mode 100644
index 000000000000..1cb201da7825
--- /dev/null
+++
b/components/camel-arangodb/src/test/java/org/apache/camel/component/arangodb/ArangoDbProducerTest.java
@@ -0,0 +1,195 @@
+/*
+ * 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.arangodb;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import com.arangodb.ArangoDB;
+import com.arangodb.Protocol;
+import com.arangodb.entity.DocumentCreateEntity;
+import com.arangodb.entity.MultiDocumentEntity;
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpServer;
+import org.apache.camel.CamelContext;
+import org.apache.camel.Exchange;
+import org.apache.camel.ProducerTemplate;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.spi.Registry;
+import org.apache.camel.test.junit6.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * The producer against a fake of the document API of ArangoDB, which, like
the server, inserts a JSON array as several
+ * documents and replies with an array.
+ */
+class ArangoDbProducerTest extends CamelTestSupport {
+
+ private static final String DOCUMENT_API =
"/_db/testDb/_api/document/coll";
+
+ private final List<String> requests = new CopyOnWriteArrayList<>();
+ private HttpServer server;
+
+ @Override
+ protected void setupResources() throws Exception {
+ super.setupResources();
+ server = HttpServer.create(new InetSocketAddress("localhost", 0), 0);
+ server.createContext("/", this::reply);
+ server.start();
+ }
+
+ @Override
+ protected void cleanupResources() throws Exception {
+ if (server != null) {
+ server.stop(0);
+ }
+ super.cleanupResources();
+ }
+
+ @Override
+ protected void bindToRegistry(Registry registry) {
+ registry.bind("arangoDB", newClient());
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+
from("direct:save").to("arangodb:testDb?documentCollection=coll&operation=SAVE_DOCUMENT");
+ }
+ };
+ }
+
+ @Test
+ void saveDocumentOfAListInsertsTheDocumentsOnce() {
+ List<Map<String, Object>> documents = List.of(Map.of("name", "foo"),
Map.of("name", "bar"));
+
+ Exchange result = template.request("direct:save", exchange ->
exchange.getMessage().setBody(documents));
+
+ // the server inserted the documents, so the exchange must not fail,
or a retry would insert them again
+ assertNull(result.getException(), () -> "the exchange failed with " +
result.getException());
+ MultiDocumentEntity<?> created =
assertInstanceOf(MultiDocumentEntity.class, result.getMessage().getBody());
+ assertEquals(2, created.getDocuments().size());
+ assertEquals(1, requests.size(), requests::toString);
+ assertTrue(requests.get(0).startsWith("POST " + DOCUMENT_API + " ["),
requests.get(0));
+ }
+
+ @Test
+ void saveDocumentOfOneDocumentInsertsOne() {
+ Exchange result = template.request("direct:save", exchange ->
exchange.getMessage().setBody(Map.of("name", "foo")));
+
+ assertNull(result.getException(), () -> "the exchange failed with " +
result.getException());
+ assertInstanceOf(DocumentCreateEntity.class,
result.getMessage().getBody());
+ assertEquals(1, requests.size(), requests::toString);
+ assertTrue(requests.get(0).startsWith("POST " + DOCUMENT_API + " {"),
requests.get(0));
+ }
+
+ @Test
+ void routeWithoutOperationFailsToStart() throws Exception {
+ try (CamelContext camel = new DefaultCamelContext()) {
+ camel.getRegistry().bind("arangoDB", newClient());
+ camel.addRoutes(saveRoute());
+
+ Exception error = assertThrows(Exception.class, camel::start);
+
+ Throwable cause = error;
+ while (cause.getCause() != null) {
+ cause = cause.getCause();
+ }
+ assertInstanceOf(IllegalArgumentException.class, cause, () -> "the
context failed with " + error);
+ assertTrue(cause.getMessage().contains("The operation option must
be set"), cause.getMessage());
+ }
+ assertTrue(requests.isEmpty(), requests::toString);
+ }
+
+ @Test
+ void operationOfTheComponentConfigurationIsEnough() throws Exception {
+ try (CamelContext camel = new DefaultCamelContext()) {
+ camel.getRegistry().bind("arangoDB", newClient());
+ camel.getComponent("arangodb",
ArangoDbComponent.class).getConfiguration()
+ .setOperation(ArangoDbOperation.SAVE_DOCUMENT);
+ camel.addRoutes(saveRoute());
+ camel.start();
+
+ try (ProducerTemplate producer = camel.createProducerTemplate()) {
+ Object body = producer.requestBody("direct:save",
Map.of("name", "foo"));
+
+ assertInstanceOf(DocumentCreateEntity.class, body);
+ }
+ }
+ }
+
+ /**
+ * A route that sends to an arangodb endpoint without the operation option.
+ */
+ private static RouteBuilder saveRoute() {
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+
from("direct:save").to("arangodb:testDb?documentCollection=coll");
+ }
+ };
+ }
+
+ private ArangoDB newClient() {
+ // HTTP/1.1, which the JDK HTTP server speaks
+ return new ArangoDB.Builder()
+ .host("localhost", server.getAddress().getPort())
+ .protocol(Protocol.HTTP_JSON)
+ .build();
+ }
+
+ private void reply(HttpExchange http) throws IOException {
+ String body = new String(http.getRequestBody().readAllBytes(),
StandardCharsets.UTF_8).trim();
+ String path = http.getRequestURI().getPath();
+ requests.add(http.getRequestMethod() + " " + path + " " + body);
+
+ int status;
+ String reply;
+ if ("POST".equals(http.getRequestMethod()) &&
DOCUMENT_API.equals(path)) {
+ status = 202;
+ // the tests send one document, or an array of two
+ reply = body.startsWith("[") ? "[" + created(1) + "," + created(2)
+ "]" : created(1);
+ } else {
+ status = 404;
+ reply =
"{\"error\":true,\"code\":404,\"errorNum\":1203,\"errorMessage\":\"unknown path
" + path + "\"}";
+ }
+ byte[] bytes = reply.getBytes(StandardCharsets.UTF_8);
+ http.getResponseHeaders().add("Content-Type", "application/json;
charset=utf-8");
+ http.sendResponseHeaders(status, bytes.length);
+ try (OutputStream out = http.getResponseBody()) {
+ out.write(bytes);
+ }
+ }
+
+ private static String created(int key) {
+ return "{\"_id\":\"coll/" + key + "\",\"_key\":\"" + key +
"\",\"_rev\":\"_r" + key + "\"}";
+ }
+}
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 b75ab007c613..fca9a0dfcd2f 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
@@ -4593,6 +4593,16 @@ credentials. A deployment that counted on the inherited
list as a restriction ha
A consumer whose `requestUriPattern` is `/` now also uses its own keys,
certificates and asynchronous MDN settings when
another consumer created the shared server connection; before, it used the
first consumer's.
+=== camel-arangodb - the operation option is required, and a Collection body
is saved as several documents
+
+A route that sends to an `arangodb` endpoint without the `operation` option,
set on the endpoint or on the component
+configuration, now fails to start with a message that names the option.
Before, the route started and each message
+failed with `Operation not supported. Value: null`.
+
+The `SAVE_DOCUMENT` operation now inserts a `Collection` body, such as a
`List` of documents, as several documents, as
+it does with the `CamelArangoDbMultiInsert` header, and the body after it is a
`MultiDocumentEntity`. Before, without
+the header, ArangoDB inserted the documents and the exchange then failed, so a
redelivery inserted them again.
+
=== camel-ftp, camel-sftp, camel-ftps, camel-mina-sftp - sendNoop also applies
to the consumer
The `sendNoop` option now also applies to the check a consumer makes before
each poll, not only to the pre-write check