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.
