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 4385357bc2b0 CAMEL-26234: camel-elasticsearch-rest-client - SEARCH
sends a JSON body as the query, GET_BY_ID and DELETE prefer the
CamelElasticsearchId header to the body, and the doc names the real headers
(#29076)
4385357bc2b0 is described below
commit 4385357bc2b0d8034d1ed69fec24b1d134e19a88
Author: Andrea Cosentino <[email protected]>
AuthorDate: Tue Nov 3 12:01:47 2026 +0100
CAMEL-26234: camel-elasticsearch-rest-client - SEARCH sends a JSON body as
the query, GET_BY_ID and DELETE prefer the CamelElasticsearchId header to the
body, and the doc names the real headers (#29076)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
.../components/elasticsearch-rest-client.json | 2 +-
.../docs/elasticsearch-rest-client-component.adoc | 31 ++--
.../rest/client/elasticsearch-rest-client.json | 2 +-
.../docs/elasticsearch-rest-client-component.adoc | 31 ++--
.../client/ElasticsearchRestClientEndpoint.java | 14 +-
.../client/ElasticsearchRestClientProducer.java | 23 +--
.../ElasticsearchRestClientProducerBodyTest.java | 189 +++++++++++++++++++++
.../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc | 11 ++
8 files changed, 265 insertions(+), 38 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/elasticsearch-rest-client.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/elasticsearch-rest-client.json
index c5f30ddb187a..2ee6f4690977 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/elasticsearch-rest-client.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/elasticsearch-rest-client.json
@@ -47,7 +47,7 @@
"CamelElasticsearchOperation": { "index": 4, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType":
"org.apache.camel.component.elasticsearch.rest.client.ElasticsearchRestClientOperation",
"enum": [ "INDEX_OR_UPDATE", "GET_BY_ID", "DELETE", "CREATE_INDEX",
"DELETE_INDEX", "SEARCH" ], "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "The operation to perform",
"constantName": "org.apache.came [...]
},
"bodyTypes": {
- "producerIn": { "carriers": [ "text", "bytes", "stream", "file", "value",
"map", "null" ], "byOption": [ { "option": "operation", "value":
"INDEX_OR_UPDATE", "carriers": [ "text", "bytes", "stream", "file" ] }, {
"option": "operation", "value": "GET_BY_ID", "carriers": [ "text", "value",
"null" ] }, { "option": "operation", "value": "DELETE", "carriers": [ "text",
"value", "null" ] }, { "option": "operation", "value": "SEARCH", "carriers": [
"map", "null" ] }, { "option": "operation" [...]
+ "producerIn": { "carriers": [ "text", "bytes", "stream", "file", "value",
"map", "null" ], "byOption": [ { "option": "operation", "value":
"INDEX_OR_UPDATE", "carriers": [ "text", "bytes", "stream", "file" ] }, {
"option": "operation", "value": "GET_BY_ID", "carriers": [ "text", "value",
"null" ] }, { "option": "operation", "value": "DELETE", "carriers": [ "text",
"value", "null" ] }, { "option": "operation", "value": "SEARCH", "carriers": [
"text", "bytes", "stream", "file", "map", [...]
"producerOut": { "carriers": [ "text", "value" ], "byOption": [ {
"option": "operation", "value": "INDEX_OR_UPDATE", "carriers": [ "text" ],
"javaType": "java.lang.String" }, { "option": "operation", "value":
"GET_BY_ID", "carriers": [ "text" ], "javaType": "java.lang.String" }, {
"option": "operation", "value": "SEARCH", "carriers": [ "text" ], "javaType":
"java.lang.String" }, { "option": "operation", "value": "DELETE", "carriers": [
"value" ], "javaType": "java.lang.Boolean" }, { [...]
},
"properties": {
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/elasticsearch-rest-client-component.adoc
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/elasticsearch-rest-client-component.adoc
index 50d794edd785..2f920a8bce15 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/elasticsearch-rest-client-component.adoc
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/elasticsearch-rest-client-component.adoc
@@ -49,16 +49,17 @@ Set an endpoint URI option or exchange header with a name
of `operation` and a v
|operation |message body |description
|`INDEX_OR_UPDATE` |`String`, `byte[]`, `Reader` or `InputStream` content to
index or update |Adds or updates content to an index and returns the resulting
`id` in the message body.
-You can set the name of the target index from the `indexName` URI parameter
option, or by providing a message header with the key `INDEX_NAME`.
-When updating indexed content, you must provide its id via a message header
with the key `ID` .
+You can set the name of the target index from the `indexName` URI parameter
option, or by providing a message header with the key
`CamelElasticsearchIndexName`.
+When updating indexed content, you must provide its id via a message header
with the key `CamelElasticsearchId`.
|`GET_BY_ID` |`String` id of content to retrieve |Retrieves a JSON String
representation of the indexed document, corresponding to the given index id and
sets it as the message exchange body.
-You can set the name of the target index from the `indexName` URI parameter
option, or by providing a message header with the key `INDEX_NAME`.
-You must provide the index id of the content to retrieve either in the message
body, or via a message header with the key `ID` .
+You can set the name of the target index from the `indexName` URI parameter
option, or by providing a message header with the key
`CamelElasticsearchIndexName`.
+You must provide the index id of the content to retrieve either via a message
header with the key `CamelElasticsearchId`, or in the message body: the header
wins over the body.
+A missing id fails with the error of the server (404).
|`DELETE` |`String` id of content to delete |Deletes the specified `indexName`
and returns a `boolean` value as the message exchange body, indicating whether
the operation was successful.
-You can set the name of the target index from the `indexName` URI parameter
option, or by providing a message header with the key `INDEX_NAME`.
-You must provide the index id of the content to delete either in the message
body, or via a message header with the key `ID` .
+You can set the name of the target index from the `indexName` URI parameter
option, or by providing a message header with the key
`CamelElasticsearchIndexName`.
+You must provide the index id of the content to delete either via a message
header with the key `CamelElasticsearchId`, or in the message body: the header
wins over the body.
|`CREATE_INDEX` | |Creates the specified `indexName` and returns a `boolean`
value as the message exchange body, indicating whether the operation was
successful.
You can set the name of the target index to create from the `indexName` URI
parameter option, or by providing a message header with the key
`CamelElasticsearchIndexName`.
@@ -67,8 +68,8 @@ You may also provide a header with the key
`CamelElasticsearchIndexSettings` whe
|`DELETE_INDEX` | |Deletes the specified `indexName` and returns a `boolean`
value as the message exchange body, indicating whether the operation was
successful.
You can set the name of the target index to create from the `indexName` URI
parameter option, or by providing a message header with the key
`CamelElasticsearchIndexName`.
-|`SEARCH` |`Map` (optional) |Search for content with either a `Map` of
`String` keys & values of query criteria in the body, or a JSON string
representation of the query in the `CamelElasticsearchSearchQuery` header,
which wins over the body. A JSON string in the body is not used: with no `Map`
body and no header, all documents match. Matching documents are returned as a
JSON array string of their sources set on the message exchange body.
-You can set the JSON query String by providing a message header with the key
`CamelElasticsearchSearchQuery`.
+|`SEARCH` |`Map` or JSON `String` (optional) |Search for content with either a
`Map` of `String` keys & values of query criteria in the body, a JSON string
representation of the query in the body, or one in the
`CamelElasticsearchSearchQuery` header, which wins over the body. With no body
and no header, all documents match. Matching documents are returned as a JSON
array string of their sources set on the message exchange body.
+You can set the JSON query String by providing a message header with the key
`CamelElasticsearchSearchQuery`, or as the message body.
You can set the message exchange body to a `Map` of `String` keys & values for
the query criteria.
|===
@@ -121,7 +122,7 @@ YAML::
----
====
-To update existing indexed content, provide the `ID` message header and the
message body with the updated content.
+To update existing indexed content, provide the `CamelElasticsearchId` message
header and the message body with the updated content.
[tabs]
====
@@ -219,7 +220,7 @@ YAML::
=== Delete Example
-To delete indexed content, provide the `ID` message header.
+To delete indexed content, provide the `CamelElasticsearchId` message header.
[tabs]
====
@@ -407,6 +408,16 @@ YAML::
----
====
+The JSON query can also be the message body, when the
`CamelElasticsearchSearchQuery` header is not set.
+
+._Java-only: ProducerTemplate with the JSON query as the body_
+[source,java]
+----
+String query = "{\"query\":{\"match\":{\"content\":\"ElasticSearch With
Camel\"}}}";
+String hits =
template.requestBody("elasticsearch-rest-client://myCluster?operation=SEARCH&indexName=myIndex",
query,
+ String.class);
+----
+
Search on specific field(s) using `Map`.
._Java-only: creating a search criteria Map_
diff --git
a/components/camel-elasticsearch-rest-client/src/generated/resources/META-INF/org/apache/camel/component/elasticsearch/rest/client/elasticsearch-rest-client.json
b/components/camel-elasticsearch-rest-client/src/generated/resources/META-INF/org/apache/camel/component/elasticsearch/rest/client/elasticsearch-rest-client.json
index c5f30ddb187a..2ee6f4690977 100644
---
a/components/camel-elasticsearch-rest-client/src/generated/resources/META-INF/org/apache/camel/component/elasticsearch/rest/client/elasticsearch-rest-client.json
+++
b/components/camel-elasticsearch-rest-client/src/generated/resources/META-INF/org/apache/camel/component/elasticsearch/rest/client/elasticsearch-rest-client.json
@@ -47,7 +47,7 @@
"CamelElasticsearchOperation": { "index": 4, "kind": "header",
"displayName": "", "group": "producer", "label": "", "required": false,
"javaType":
"org.apache.camel.component.elasticsearch.rest.client.ElasticsearchRestClientOperation",
"enum": [ "INDEX_OR_UPDATE", "GET_BY_ID", "DELETE", "CREATE_INDEX",
"DELETE_INDEX", "SEARCH" ], "deprecated": false, "deprecationNote": "",
"autowired": false, "secret": false, "description": "The operation to perform",
"constantName": "org.apache.came [...]
},
"bodyTypes": {
- "producerIn": { "carriers": [ "text", "bytes", "stream", "file", "value",
"map", "null" ], "byOption": [ { "option": "operation", "value":
"INDEX_OR_UPDATE", "carriers": [ "text", "bytes", "stream", "file" ] }, {
"option": "operation", "value": "GET_BY_ID", "carriers": [ "text", "value",
"null" ] }, { "option": "operation", "value": "DELETE", "carriers": [ "text",
"value", "null" ] }, { "option": "operation", "value": "SEARCH", "carriers": [
"map", "null" ] }, { "option": "operation" [...]
+ "producerIn": { "carriers": [ "text", "bytes", "stream", "file", "value",
"map", "null" ], "byOption": [ { "option": "operation", "value":
"INDEX_OR_UPDATE", "carriers": [ "text", "bytes", "stream", "file" ] }, {
"option": "operation", "value": "GET_BY_ID", "carriers": [ "text", "value",
"null" ] }, { "option": "operation", "value": "DELETE", "carriers": [ "text",
"value", "null" ] }, { "option": "operation", "value": "SEARCH", "carriers": [
"text", "bytes", "stream", "file", "map", [...]
"producerOut": { "carriers": [ "text", "value" ], "byOption": [ {
"option": "operation", "value": "INDEX_OR_UPDATE", "carriers": [ "text" ],
"javaType": "java.lang.String" }, { "option": "operation", "value":
"GET_BY_ID", "carriers": [ "text" ], "javaType": "java.lang.String" }, {
"option": "operation", "value": "SEARCH", "carriers": [ "text" ], "javaType":
"java.lang.String" }, { "option": "operation", "value": "DELETE", "carriers": [
"value" ], "javaType": "java.lang.Boolean" }, { [...]
},
"properties": {
diff --git
a/components/camel-elasticsearch-rest-client/src/main/docs/elasticsearch-rest-client-component.adoc
b/components/camel-elasticsearch-rest-client/src/main/docs/elasticsearch-rest-client-component.adoc
index 50d794edd785..2f920a8bce15 100644
---
a/components/camel-elasticsearch-rest-client/src/main/docs/elasticsearch-rest-client-component.adoc
+++
b/components/camel-elasticsearch-rest-client/src/main/docs/elasticsearch-rest-client-component.adoc
@@ -49,16 +49,17 @@ Set an endpoint URI option or exchange header with a name
of `operation` and a v
|operation |message body |description
|`INDEX_OR_UPDATE` |`String`, `byte[]`, `Reader` or `InputStream` content to
index or update |Adds or updates content to an index and returns the resulting
`id` in the message body.
-You can set the name of the target index from the `indexName` URI parameter
option, or by providing a message header with the key `INDEX_NAME`.
-When updating indexed content, you must provide its id via a message header
with the key `ID` .
+You can set the name of the target index from the `indexName` URI parameter
option, or by providing a message header with the key
`CamelElasticsearchIndexName`.
+When updating indexed content, you must provide its id via a message header
with the key `CamelElasticsearchId`.
|`GET_BY_ID` |`String` id of content to retrieve |Retrieves a JSON String
representation of the indexed document, corresponding to the given index id and
sets it as the message exchange body.
-You can set the name of the target index from the `indexName` URI parameter
option, or by providing a message header with the key `INDEX_NAME`.
-You must provide the index id of the content to retrieve either in the message
body, or via a message header with the key `ID` .
+You can set the name of the target index from the `indexName` URI parameter
option, or by providing a message header with the key
`CamelElasticsearchIndexName`.
+You must provide the index id of the content to retrieve either via a message
header with the key `CamelElasticsearchId`, or in the message body: the header
wins over the body.
+A missing id fails with the error of the server (404).
|`DELETE` |`String` id of content to delete |Deletes the specified `indexName`
and returns a `boolean` value as the message exchange body, indicating whether
the operation was successful.
-You can set the name of the target index from the `indexName` URI parameter
option, or by providing a message header with the key `INDEX_NAME`.
-You must provide the index id of the content to delete either in the message
body, or via a message header with the key `ID` .
+You can set the name of the target index from the `indexName` URI parameter
option, or by providing a message header with the key
`CamelElasticsearchIndexName`.
+You must provide the index id of the content to delete either via a message
header with the key `CamelElasticsearchId`, or in the message body: the header
wins over the body.
|`CREATE_INDEX` | |Creates the specified `indexName` and returns a `boolean`
value as the message exchange body, indicating whether the operation was
successful.
You can set the name of the target index to create from the `indexName` URI
parameter option, or by providing a message header with the key
`CamelElasticsearchIndexName`.
@@ -67,8 +68,8 @@ You may also provide a header with the key
`CamelElasticsearchIndexSettings` whe
|`DELETE_INDEX` | |Deletes the specified `indexName` and returns a `boolean`
value as the message exchange body, indicating whether the operation was
successful.
You can set the name of the target index to create from the `indexName` URI
parameter option, or by providing a message header with the key
`CamelElasticsearchIndexName`.
-|`SEARCH` |`Map` (optional) |Search for content with either a `Map` of
`String` keys & values of query criteria in the body, or a JSON string
representation of the query in the `CamelElasticsearchSearchQuery` header,
which wins over the body. A JSON string in the body is not used: with no `Map`
body and no header, all documents match. Matching documents are returned as a
JSON array string of their sources set on the message exchange body.
-You can set the JSON query String by providing a message header with the key
`CamelElasticsearchSearchQuery`.
+|`SEARCH` |`Map` or JSON `String` (optional) |Search for content with either a
`Map` of `String` keys & values of query criteria in the body, a JSON string
representation of the query in the body, or one in the
`CamelElasticsearchSearchQuery` header, which wins over the body. With no body
and no header, all documents match. Matching documents are returned as a JSON
array string of their sources set on the message exchange body.
+You can set the JSON query String by providing a message header with the key
`CamelElasticsearchSearchQuery`, or as the message body.
You can set the message exchange body to a `Map` of `String` keys & values for
the query criteria.
|===
@@ -121,7 +122,7 @@ YAML::
----
====
-To update existing indexed content, provide the `ID` message header and the
message body with the updated content.
+To update existing indexed content, provide the `CamelElasticsearchId` message
header and the message body with the updated content.
[tabs]
====
@@ -219,7 +220,7 @@ YAML::
=== Delete Example
-To delete indexed content, provide the `ID` message header.
+To delete indexed content, provide the `CamelElasticsearchId` message header.
[tabs]
====
@@ -407,6 +408,16 @@ YAML::
----
====
+The JSON query can also be the message body, when the
`CamelElasticsearchSearchQuery` header is not set.
+
+._Java-only: ProducerTemplate with the JSON query as the body_
+[source,java]
+----
+String query = "{\"query\":{\"match\":{\"content\":\"ElasticSearch With
Camel\"}}}";
+String hits =
template.requestBody("elasticsearch-rest-client://myCluster?operation=SEARCH&indexName=myIndex",
query,
+ String.class);
+----
+
Search on specific field(s) using `Map`.
._Java-only: creating a search criteria Map_
diff --git
a/components/camel-elasticsearch-rest-client/src/main/java/org/apache/camel/component/elasticsearch/rest/client/ElasticsearchRestClientEndpoint.java
b/components/camel-elasticsearch-rest-client/src/main/java/org/apache/camel/component/elasticsearch/rest/client/ElasticsearchRestClientEndpoint.java
index 1a839cb9791a..9d1fc4125539 100644
---
a/components/camel-elasticsearch-rest-client/src/main/java/org/apache/camel/component/elasticsearch/rest/client/ElasticsearchRestClientEndpoint.java
+++
b/components/camel-elasticsearch-rest-client/src/main/java/org/apache/camel/component/elasticsearch/rest/client/ElasticsearchRestClientEndpoint.java
@@ -50,18 +50,20 @@ import org.elasticsearch.client.RestClient;
@BodyType.When(option = "operation",
value = "DELETE",
carriers = {
Carrier.TEXT, Carrier.VALUE, Carrier.NULL }),
@BodyType.When(option = "operation",
value = "SEARCH",
- carriers = {
Carrier.MAP, Carrier.NULL }),
+ carriers = {
+ Carrier.TEXT,
Carrier.BYTES, Carrier.STREAM, Carrier.FILE,
+ Carrier.MAP,
Carrier.NULL }),
@BodyType.When(option = "operation",
value = "CREATE_INDEX", carriers = Carrier.ANY),
@BodyType.When(option = "operation",
value = "DELETE_INDEX",
carriers =
Carrier.ANY) },
description = "By the operation, set by the
operation option or the CamelElasticsearchOperation"
+ " header. INDEX_OR_UPDATE
sends the body as the JSON document: text, bytes, a"
+ " stream or a file of it; a
Map or a POJO is sent as its toString() and"
- + " fails, so marshal it to
JSON first. GET_BY_ID and DELETE take the id as"
- + " the body, or from the
CamelElasticsearchId header when the body is empty."
- + " SEARCH takes a Map of
field names and values to match, or else the JSON"
- + " query of the
CamelElasticsearchSearchQuery header; a JSON text body is"
- + " not used, and with neither
all documents match. CREATE_INDEX and"
+ + " fails, so marshal it to
JSON first. GET_BY_ID and DELETE take the id of"
+ + " the CamelElasticsearchId
header, or else the body. SEARCH takes the JSON"
+ + " query of the
CamelElasticsearchSearchQuery header, or else the body: a Map"
+ + " of field names and values
to match, or the JSON query as text, bytes, a"
+ + " stream or a file; with no
body all documents match. CREATE_INDEX and"
+ " DELETE_INDEX do not use
the body: the settings of a new index are in the"
+ "
CamelElasticsearchIndexSettings header"),
producerOut = @BodyType(value = { Carrier.TEXT, Carrier.VALUE },
diff --git
a/components/camel-elasticsearch-rest-client/src/main/java/org/apache/camel/component/elasticsearch/rest/client/ElasticsearchRestClientProducer.java
b/components/camel-elasticsearch-rest-client/src/main/java/org/apache/camel/component/elasticsearch/rest/client/ElasticsearchRestClientProducer.java
index 401e78593f90..3f20a5b369f5 100644
---
a/components/camel-elasticsearch-rest-client/src/main/java/org/apache/camel/component/elasticsearch/rest/client/ElasticsearchRestClientProducer.java
+++
b/components/camel-elasticsearch-rest-client/src/main/java/org/apache/camel/component/elasticsearch/rest/client/ElasticsearchRestClientProducer.java
@@ -247,9 +247,10 @@ public class ElasticsearchRestClientProducer extends
DefaultAsyncProducer {
* Generate the Request for operation GET_BY_ID
*/
private Request getById(String indexName, Exchange exchange) {
- var id = exchange.getMessage().getBody(String.class);
+ // the id of the header, as for the other operations, else the one of
the body
+ var id =
exchange.getMessage().getHeader(ElasticSearchRestClientConstant.ID,
String.class);
if (ObjectHelper.isEmpty(id)) {
- id =
exchange.getMessage().getHeader(ElasticSearchRestClientConstant.ID,
String.class);
+ id = exchange.getMessage().getBody(String.class);
if (ObjectHelper.isEmpty(id)) {
throw new IllegalArgumentException(
"id value is mandatory when performing GET_BY_ID
operation");
@@ -263,9 +264,10 @@ public class ElasticsearchRestClientProducer extends
DefaultAsyncProducer {
* Generate the Request for DELETE
*/
private Request delete(String indexName, Exchange exchange) {
- var id = exchange.getMessage().getBody(String.class);
+ // the id of the header, as for the other operations, else the one of
the body
+ var id =
exchange.getMessage().getHeader(ElasticSearchRestClientConstant.ID,
String.class);
if (ObjectHelper.isEmpty(id)) {
- id =
exchange.getMessage().getHeader(ElasticSearchRestClientConstant.ID,
String.class);
+ id = exchange.getMessage().getBody(String.class);
if (ObjectHelper.isEmpty(id)) {
throw new IllegalArgumentException(
"id value is mandatory when performing DELETE
operation");
@@ -293,6 +295,12 @@ public class ElasticsearchRestClientProducer extends
DefaultAsyncProducer {
if (ObjectHelper.isNotEmpty(queryParameters)) {
var jsonRequest = createQueryFromMap(queryParameters);
request.setJsonEntity(jsonRequest);
+ } else if (queryParameters == null) {
+ // else the body is the JSON query, as the header would be
+ var jsonQuery = exchange.getMessage().getBody(String.class);
+ if (ObjectHelper.isNotEmpty(jsonQuery)) {
+ request.setJsonEntity(jsonQuery);
+ }
}
}
@@ -347,12 +355,7 @@ public class ElasticsearchRestClientProducer extends
DefaultAsyncProducer {
* Extract Document from response
*/
String extractDocument(JsonObject doc) {
- // check if document exists
- Boolean found = doc.getBoolean("found");
- if (!found) {
- return null;
- }
- // if it exists extract document
+ // the document exists: for a missing document the server answers 404,
which fails the request before
Map<String, Object> map = doc.getMap("_source");
JsonObject jsonObject = new JsonObject(map);
diff --git
a/components/camel-elasticsearch-rest-client/src/test/java/org/apache/camel/component/elasticsearch/rest/client/ElasticsearchRestClientProducerBodyTest.java
b/components/camel-elasticsearch-rest-client/src/test/java/org/apache/camel/component/elasticsearch/rest/client/ElasticsearchRestClientProducerBodyTest.java
new file mode 100644
index 000000000000..bfa0d3f6f8d5
--- /dev/null
+++
b/components/camel-elasticsearch-rest-client/src/test/java/org/apache/camel/component/elasticsearch/rest/client/ElasticsearchRestClientProducerBodyTest.java
@@ -0,0 +1,189 @@
+/*
+ * 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.elasticsearch.rest.client;
+
+import java.io.ByteArrayInputStream;
+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.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+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.impl.DefaultCamelContext;
+import org.elasticsearch.client.ResponseException;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+
+/**
+ * The request the producer sends for the bodies and headers it takes, checked
against a node that records the requests
+ * and answers them as Elasticsearch does.
+ */
+class ElasticsearchRestClientProducerBodyTest {
+
+ private static final ObjectMapper MAPPER = new ObjectMapper();
+ private static final String QUERY =
"{\"query\":{\"match\":{\"title\":\"camel\"}}}";
+
+ private final List<Recorded> requests = new CopyOnWriteArrayList<>();
+ private HttpServer node;
+ private CamelContext context;
+ private ProducerTemplate template;
+ private String uri;
+
+ @BeforeEach
+ void start() throws Exception {
+ node = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0);
+ node.createContext("/", this::answer);
+ node.start();
+ uri =
"elasticsearch-rest-client:camel?indexName=my_index&hostAddressesList=127.0.0.1:"
+ + node.getAddress().getPort();
+ context = new DefaultCamelContext();
+ context.start();
+ template = context.createProducerTemplate();
+ }
+
+ @AfterEach
+ void stop() {
+ context.stop();
+ node.stop(0);
+ }
+
+ @Test
+ void searchSendsAJsonBodyAsTheQuery() throws Exception {
+ assertEquals("[{\"title\":\"camel\"}]", template.requestBody(uri +
"&operation=SEARCH", QUERY, String.class));
+
+ assertEquals("/my_index/_search", single().path());
+ assertEquals(json(QUERY), json(single().body()));
+ }
+
+ @Test
+ void searchSendsAJsonStreamBodyAsTheQuery() throws Exception {
+ template.requestBody(uri + "&operation=SEARCH", new
ByteArrayInputStream(QUERY.getBytes(StandardCharsets.UTF_8)),
+ String.class);
+
+ assertEquals(json(QUERY), json(single().body()));
+ }
+
+ @Test
+ void searchPrefersTheQueryHeaderToTheBody() throws Exception {
+ String header = "{\"query\":{\"match_all\":{}}}";
+
+ template.requestBodyAndHeader(uri + "&operation=SEARCH", QUERY,
ElasticSearchRestClientConstant.SEARCH_QUERY, header,
+ String.class);
+
+ assertEquals(json(header), json(single().body()));
+ }
+
+ @Test
+ void searchStillMatchesTheFieldsOfAMapBody() throws Exception {
+ template.requestBody(uri + "&operation=SEARCH", Map.of("title",
"camel"), String.class);
+
+
assertEquals(json("{\"query\":{\"bool\":{\"must\":[{\"match\":{\"title\":\"camel\"}}]}}}"),
json(single().body()));
+ }
+
+ @Test
+ void searchWithoutBodyStillMatchesEveryDocument() {
+ template.requestBody(uri + "&operation=SEARCH", null, String.class);
+
+ assertEquals("", single().body());
+ }
+
+ @Test
+ void getByIdPrefersTheIdHeaderToALeftoverBody() {
+ String document = template.requestBodyAndHeader(uri +
"&operation=GET_BY_ID", "leftover",
+ ElasticSearchRestClientConstant.ID, "1", String.class);
+
+ assertEquals("{\"title\":\"camel\"}", document);
+ assertEquals("GET", single().method());
+ assertEquals("/my_index/_doc/1", single().path());
+ }
+
+ @Test
+ void getByIdStillTakesTheIdOfTheBodyWithoutHeader() {
+ template.requestBody(uri + "&operation=GET_BY_ID", "1", String.class);
+
+ assertEquals("/my_index/_doc/1", single().path());
+ }
+
+ @Test
+ void getByIdOfAMissingDocumentFailsWithTheErrorOfTheServer() {
+ Exchange exchange = template.request(uri + "&operation=GET_BY_ID",
+ e ->
e.getMessage().setHeader(ElasticSearchRestClientConstant.ID, "missing"));
+
+ ResponseException e = assertInstanceOf(ResponseException.class,
exchange.getException());
+ assertEquals(404, e.getResponse().getStatusLine().getStatusCode());
+ }
+
+ @Test
+ void deletePrefersTheIdHeaderToALeftoverBody() {
+ Object deleted = template.requestBodyAndHeader(uri +
"&operation=DELETE", "leftover",
+ ElasticSearchRestClientConstant.ID, "1");
+
+ assertEquals(Boolean.TRUE, deleted);
+ assertEquals("DELETE", single().method());
+ assertEquals("/my_index/_doc/1", single().path());
+ }
+
+ private Recorded single() {
+ assertEquals(1, requests.size(), "requests sent: " + requests);
+ return requests.get(0);
+ }
+
+ private static JsonNode json(String text) throws IOException {
+ return MAPPER.readTree(text);
+ }
+
+ private void answer(HttpExchange exchange) throws IOException {
+ String body = new String(exchange.getRequestBody().readAllBytes(),
StandardCharsets.UTF_8);
+ String method = exchange.getRequestMethod();
+ String path = exchange.getRequestURI().getPath();
+ requests.add(new Recorded(method, path, body));
+ int status = 200;
+ String response;
+ if (path.endsWith("/_search")) {
+ response =
"{\"hits\":{\"hits\":[{\"_source\":{\"title\":\"camel\"}}]}}";
+ } else if (path.endsWith("/_doc/missing")) {
+ status = 404;
+ response =
"{\"_index\":\"my_index\",\"_id\":\"missing\",\"found\":false}";
+ } else if ("DELETE".equals(method)) {
+ response =
"{\"_index\":\"my_index\",\"_id\":\"1\",\"result\":\"deleted\"}";
+ } else {
+ response =
"{\"_index\":\"my_index\",\"_id\":\"1\",\"found\":true,\"_source\":{\"title\":\"camel\"}}";
+ }
+ byte[] bytes = response.getBytes(StandardCharsets.UTF_8);
+ exchange.getResponseHeaders().add("Content-Type", "application/json");
+ exchange.sendResponseHeaders(status, bytes.length);
+ try (OutputStream out = exchange.getResponseBody()) {
+ out.write(bytes);
+ }
+ }
+
+ private record Recorded(String method, String path, String body) {
+ }
+}
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 707cfc139aa9..a03f79d36c49 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
@@ -4858,6 +4858,17 @@ In token mode a `SlackMessage` body now sends its
attachments too, not only its
The Slack CloudEvents data type transformer now sets `CamelCloudEventSource`
to `slack.<appId>` and
`CamelCloudEventSubject` to the channel. Previously they were `slack.true` and
`true`.
+=== camel-elasticsearch-rest-client - SEARCH body and the id of GET_BY_ID and
DELETE
+
+The `SEARCH` operation now sends a body that is not a `Map` (JSON text, bytes,
a stream or a file) as the JSON query,
+when the `CamelElasticsearchSearchQuery` header is not set. Before, such a
body was ignored and every document matched.
+A route whose `SEARCH` step gets a body left by a previous step, such as the
id given by `INDEX_OR_UPDATE`, now sends
+that body as the query and fails: remove the body, or set the query in the
header, to match every document as before.
+
+The `GET_BY_ID` and `DELETE` operations now take the id of the
`CamelElasticsearchId` header first, and the body only
+when the header is not set, as `INDEX_OR_UPDATE` does. Before, the body won
over the header, so a body left by a
+previous step was used as the id.
+
=== camel-telegram - media bodies, messages without text, and answers that are
not ok
The `telegram` producer changes: