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

commit fc71d23e067071a6759a3645ab70883e5db92977
Author: Claus Ibsen <[email protected]>
AuthorDate: Sat Oct 24 20:26:45 2026 +0200

    CAMEL-26026: camel-solr - the operation of the header or the endpoint wins 
over the type of the body (#28312)
    
    Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../apache/camel/catalog/docs/solr-component.adoc  |  8 ++-
 .../camel-solr/src/main/docs/solr-component.adoc   |  8 ++-
 .../apache/camel/component/solr/SolrProducer.java  | 68 +++++++++++++---------
 .../solr/integration/SolrInsertAndDeleteTest.java  | 47 +++++++++++++++
 .../ROOT/pages/camel-4x-upgrade-guide-4_18.adoc    |  9 +++
 .../ROOT/pages/camel-4x-upgrade-guide-4_22.adoc    |  9 +++
 .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc    |  9 +++
 7 files changed, 123 insertions(+), 35 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/solr-component.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/solr-component.adoc
index 9297ba0b76a2..f34f48a1d1c4 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/solr-component.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/solr-component.adoc
@@ -45,9 +45,11 @@ include::partial$component-endpoint-headers.adoc[]
 
 === Message Operations
 
-The following Solr operations are currently supported. Set an
-exchange header with a key of "operation" and a value set to one of
-the following. Some operations also require the message body to be set.
+The following Solr operations are currently supported. Set the operation with 
the `CamelSolrOperation` header, or
+with the `operation` option of the endpoint (the header wins). Some operations 
also require the message body to be set.
+When neither is set, the body decides: a Map, a file or a collection inserts, 
a collection of Strings deletes by id, and
+the `CamelSolrQueryString` header searches. A body that is a request of SolrJ, 
such as a `SolrQuery` or an
+`UpdateRequest`, runs its own operation.
 
 * `INSERT`
 * `DELETE`
diff --git a/components/camel-solr/src/main/docs/solr-component.adoc 
b/components/camel-solr/src/main/docs/solr-component.adoc
index 9297ba0b76a2..f34f48a1d1c4 100644
--- a/components/camel-solr/src/main/docs/solr-component.adoc
+++ b/components/camel-solr/src/main/docs/solr-component.adoc
@@ -45,9 +45,11 @@ include::partial$component-endpoint-headers.adoc[]
 
 === Message Operations
 
-The following Solr operations are currently supported. Set an
-exchange header with a key of "operation" and a value set to one of
-the following. Some operations also require the message body to be set.
+The following Solr operations are currently supported. Set the operation with 
the `CamelSolrOperation` header, or
+with the `operation` option of the endpoint (the header wins). Some operations 
also require the message body to be set.
+When neither is set, the body decides: a Map, a file or a collection inserts, 
a collection of Strings deletes by id, and
+the `CamelSolrQueryString` header searches. A body that is a request of SolrJ, 
such as a `SolrQuery` or an
+`UpdateRequest`, runs its own operation.
 
 * `INSERT`
 * `DELETE`
diff --git 
a/components/camel-solr/src/main/java/org/apache/camel/component/solr/SolrProducer.java
 
b/components/camel-solr/src/main/java/org/apache/camel/component/solr/SolrProducer.java
index 888f6c9eeb2f..929e40caa7bf 100644
--- 
a/components/camel-solr/src/main/java/org/apache/camel/component/solr/SolrProducer.java
+++ 
b/components/camel-solr/src/main/java/org/apache/camel/component/solr/SolrProducer.java
@@ -18,6 +18,7 @@ package org.apache.camel.component.solr;
 
 import java.io.File;
 import java.io.IOException;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Map;
 import java.util.Optional;
@@ -168,48 +169,43 @@ public class SolrProducer extends DefaultAsyncProducer {
     }
 
     private SolrOperation resolveSolrOperation(Exchange exchange, boolean 
hasSolrParams) {
-        // Operation can be driven by either (in order of preference):
-        // a. If the body is a specific class (SolrQuery, QueryRequest, 
UpdateRequest, SolrInputDocument(=Map)).
-        // b. If the body is not one of the specific classes, the operation is 
set by the
-        // header if it exists.
-        // c. If neither the operation can not be derived from the body or
-        // header, the configuration is used.
-        // In the event we can't discover the operation from a, b or c we throw
-        // an error.
+        // The operation is, in this order:
+        // a. the operation of a body that is a request of SolrJ, which the 
route builds (SolrPing, SolrQuery,
+        // QueryRequest, UpdateRequest), or none for another SolrRequest, 
which is sent as it is;
+        // b. the operation of the header, or else of the configuration;
+        // c. derived from the body, when neither is set: the query string 
header searches, a Map or a file inserts, and
+        // a collection inserts, or deletes by id when it holds strings. The 
body is the data of the message, so it never
+        // changes the operation that the header or the configuration names.
+        // In the event we can't discover the operation from a, b or c we 
throw an error.
 
-        Object body = exchange.getMessage().getBody();
+        Message message = exchange.getMessage();
+        Object body = message.getBody();
         if (ObjectHelper.isNotEmpty(body)) {
             LOG.debug("Operation request body: {}", body);
         }
         if (body instanceof SolrPing) {
             return SolrOperation.PING;
         }
-        if (body instanceof SolrQuery
-                || body instanceof QueryRequest
-                || 
exchange.getMessage().getHeader(SolrConstants.PARAM_QUERY_STRING) != null) {
+        if (body instanceof SolrQuery || body instanceof QueryRequest) {
             return SolrOperation.SEARCH;
         }
-        if (body instanceof Map
-                || body instanceof WrappedFile
-                || body instanceof File
-                || body instanceof UpdateRequest) {
+        if (body instanceof UpdateRequest) {
             return SolrOperation.INSERT;
         }
+        // SolrPing, QueryRequest and UpdateRequest are SolrRequests too: any 
other SolrRequest is sent as it is
         if (body instanceof SolrRequest) {
             return null;
         }
-        // collection: if strings then delete else insert
-        if (body instanceof Collection<?> collection) {
-            return SolrUtils.isCollectionOfType(collection, String.class)
-                    ? SolrOperation.DELETE
-                    : SolrOperation.INSERT;
-        }
 
-        SolrOperation operation;
-        String actionString = 
exchange.getMessage().getHeader(SolrConstants.PARAM_OPERATION, String.class);
+        String actionString = message.getHeader(SolrConstants.PARAM_OPERATION, 
String.class);
         if (ObjectHelper.isNotEmpty(actionString)) {
-            operation = SolrOperation.getSolrOperationFrom(actionString);
-            if (operation != null && 
!operation.name().equalsIgnoreCase(actionString)) {
+            SolrOperation operation = 
SolrOperation.getSolrOperationFrom(actionString);
+            if (operation == null) {
+                throw new IllegalArgumentException(
+                        "Unknown " + SolrConstants.PARAM_OPERATION + " " + 
actionString + ": use one of "
+                                                   + 
Arrays.toString(SolrOperation.values()));
+            }
+            if (!operation.name().equalsIgnoreCase(actionString)) {
                 LOG.warn(operation.createFutureDeprecationMessage(actionString,
                         operation.getActionParameter(actionString)));
             }
@@ -217,9 +213,23 @@ public class SolrProducer extends DefaultAsyncProducer {
             return operation;
         }
         if (configuration.getOperation() != null) {
-            operation = configuration.getOperation();
-            LOG.debug("Operation obtained from config: {}", operation);
-            return operation;
+            LOG.debug("Operation obtained from config: {}", 
configuration.getOperation());
+            return configuration.getOperation();
+        }
+
+        if (message.getHeader(SolrConstants.PARAM_QUERY_STRING) != null) {
+            return SolrOperation.SEARCH;
+        }
+        if (body instanceof Map
+                || body instanceof WrappedFile
+                || body instanceof File) {
+            return SolrOperation.INSERT;
+        }
+        // collection: if strings then delete else insert
+        if (body instanceof Collection<?> collection) {
+            return SolrUtils.isCollectionOfType(collection, String.class)
+                    ? SolrOperation.DELETE
+                    : SolrOperation.INSERT;
         }
         // when "invalid" body with solr params (e.g. commit),
         // assume insert request and allow processing without "body"
diff --git 
a/components/camel-solr/src/test/java/org/apache/camel/component/solr/integration/SolrInsertAndDeleteTest.java
 
b/components/camel-solr/src/test/java/org/apache/camel/component/solr/integration/SolrInsertAndDeleteTest.java
index e573872593c5..bf2ec81de631 100644
--- 
a/components/camel-solr/src/test/java/org/apache/camel/component/solr/integration/SolrInsertAndDeleteTest.java
+++ 
b/components/camel-solr/src/test/java/org/apache/camel/component/solr/integration/SolrInsertAndDeleteTest.java
@@ -46,6 +46,8 @@ import static org.junit.jupiter.api.Assertions.*;
 
 public class SolrInsertAndDeleteTest extends SolrTestSupport {
 
+    private static final String INSERT_START_ENDPOINT = DEFAULT_START_ENDPOINT 
+ "Insert";
+
     @Test
     public void testDeleteById() throws Exception {
 
@@ -99,6 +101,49 @@ public class SolrInsertAndDeleteTest extends 
SolrTestSupport {
         assertEquals(0, 
executeSolrQuery("id:test*").getResults().getNumFound(), "wrong number of 
entries found");
     }
 
+    @Test
+    public void testInsertHeaderDoesNotDeleteTheIdsOfTheBody() throws 
Exception {
+        // the operation of the header wins over the type of the body: a List 
of ids does not turn an insert into a delete,
+        // and as the ids are not documents, the insert fails
+        solrInsertTestEntry(TEST_ID);
+        solrCommit();
+        assertEquals(1, executeSolrQuery("id:" + 
TEST_ID).getResults().getNumFound(), "wrong number of entries found");
+
+        Map<String, Object> headers = new 
HashMap<>(SolrUtils.getHeadersForCommit());
+        headers.put(SolrConstants.PARAM_OPERATION, 
SolrOperation.INSERT.name());
+        Exchange exchange = template.send(DEFAULT_START_ENDPOINT, e -> {
+            e.getMessage().setBody(List.of(TEST_ID));
+            e.getMessage().setHeaders(headers);
+        });
+
+        assertInstanceOf(InvalidPayloadException.class, 
exchange.getException());
+        assertEquals(1, executeSolrQuery("id:" + 
TEST_ID).getResults().getNumFound(), "wrong number of entries found");
+    }
+
+    @Test
+    public void testInsertOptionDoesNotDeleteTheIdsOfTheBody() throws 
Exception {
+        // the operation of the endpoint wins over the type of the body as well
+        solrInsertTestEntry(TEST_ID);
+        solrCommit();
+        assertEquals(1, executeSolrQuery("id:" + 
TEST_ID).getResults().getNumFound(), "wrong number of entries found");
+
+        Exchange exchange = template.send(INSERT_START_ENDPOINT, e -> 
e.getMessage().setBody(List.of(TEST_ID)));
+
+        assertInstanceOf(InvalidPayloadException.class, 
exchange.getException());
+        assertEquals(1, executeSolrQuery("id:" + 
TEST_ID).getResults().getNumFound(), "wrong number of entries found");
+    }
+
+    @Test
+    public void testUnknownOperationFails() {
+        Exchange exchange = template.send(DEFAULT_START_ENDPOINT, e -> {
+            e.getMessage().setBody("*:*");
+            e.getMessage().setHeader(SolrConstants.PARAM_OPERATION, "NOSUCH");
+        });
+
+        assertInstanceOf(IllegalArgumentException.class, 
exchange.getException());
+        assertTrue(exchange.getException().getMessage().contains("NOSUCH"), 
exchange.getException().getMessage());
+    }
+
     @Test
     public void testDeleteByQuery() throws Exception {
 
@@ -522,6 +567,8 @@ public class SolrInsertAndDeleteTest extends 
SolrTestSupport {
                         .to(DEFAULT_SOLR_ENDPOINT);
                 from(DEFAULT_START_ENDPOINT_AUTO_COMMIT)
                         .to(DEFAULT_SOLR_ENDPOINT + "?autoCommit=true");
+                from(INSERT_START_ENDPOINT)
+                        .to(DEFAULT_SOLR_ENDPOINT + 
"?operation=INSERT&autoCommit=true");
                 from(DEFAULT_START_ENDPOINT_SPLIT_THEN_COMMIT)
                         .filter(header(SolrConstants.PARAM_OPERATION).isNull())
                         .setHeader(SolrConstants.PARAM_OPERATION, 
constant(SolrOperation.INSERT))
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_18.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_18.adoc
index e345d9cdea54..9e1ce04ab972 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_18.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_18.adoc
@@ -450,6 +450,15 @@ the arguments of the call, as in `Error invoking 
resourceById with [password, re
 username]: ...`, and no longer shows their values. The arguments hold the 
options of the endpoint, such as a password or
 a token, and the message data. The debug log of the call masks the values of 
the sensitive options.
 
+=== camel-solr - the operation of the header or the endpoint wins over the 
type of the body
+
+The solr producer now runs the operation of the `CamelSolrOperation` header, 
or else of the `operation` option, whatever
+the body is. Before, the type of the body came first: a Map, a file or a 
collection that is not all Strings ran an
+insert, and a collection of Strings ran a delete by id, even when the header 
or the option named another operation. The
+body decides only when neither is set, and a body that is a request of SolrJ, 
such as a `SolrQuery` or an
+`UpdateRequest`, still runs its own operation. The `CamelSolrQueryString` 
header makes a search only when no operation
+is set, and a `CamelSolrOperation` header with a value that is not an 
operation now fails.
+
 == Upgrading from 4.18.4 to 4.18.5
 
 === camel-azure-eventhubs
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
index e39078eb48c4..44c3133ed62c 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
@@ -250,6 +250,15 @@ the arguments of the call, as in `Error invoking 
resourceById with [password, re
 username]: ...`, and no longer shows their values. The arguments hold the 
options of the endpoint, such as a password or
 a token, and the message data. The debug log of the call masks the values of 
the sensitive options.
 
+=== camel-solr - the operation of the header or the endpoint wins over the 
type of the body
+
+The solr producer now runs the operation of the `CamelSolrOperation` header, 
or else of the `operation` option, whatever
+the body is. Before, the type of the body came first: a Map, a file or a 
collection that is not all Strings ran an
+insert, and a collection of Strings ran a delete by id, even when the header 
or the option named another operation. The
+body decides only when neither is set, and a body that is a request of SolrJ, 
such as a `SolrQuery` or an
+`UpdateRequest`, still runs its own operation. The `CamelSolrQueryString` 
header makes a search only when no operation
+is set, and a `CamelSolrOperation` header with a value that is not an 
operation now fails.
+
 == Upgrading from 4.22.1 to 4.22.2
 
 === camel-lra - only an LRA hosted by the configured coordinator is joined
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 c4a87ac1fcb7..e7fa909b25d1 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
@@ -3440,3 +3440,12 @@ When the call of an API component fails, such as one of 
fhir, box, google-drive
 the arguments of the call, as in `Error invoking resourceById with [password, 
resourceClass, serverUrl, stringId,
 username]: ...`, and no longer shows their values. The arguments hold the 
options of the endpoint, such as a password or
 a token, and the message data. The debug log of the call masks the values of 
the sensitive options.
+
+=== camel-solr - the operation of the header or the endpoint wins over the 
type of the body
+
+The solr producer now runs the operation of the `CamelSolrOperation` header, 
or else of the `operation` option, whatever
+the body is. Before, the type of the body came first: a Map, a file or a 
collection that is not all Strings ran an
+insert, and a collection of Strings ran a delete by id, even when the header 
or the option named another operation. The
+body decides only when neither is set, and a body that is a request of SolrJ, 
such as a `SolrQuery` or an
+`UpdateRequest`, still runs its own operation. The `CamelSolrQueryString` 
header makes a search only when no operation
+is set, and a `CamelSolrOperation` header with a value that is not an 
operation now fails.

Reply via email to