This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch CAMEL-26146-jooq-operation-empty-body in repository https://gitbox.apache.org/repos/asf/camel.git
commit 8a51ef6c519690d359e769680cbef86aa4903c2b Author: Claus Ibsen <[email protected]> AuthorDate: Mon Nov 2 17:25:27 2026 +0100 CAMEL-26146: camel-jooq - fail on an empty body and drop the CONSUME operation from the catalog The operation option listed CONSUME in its enums, but JooqOperation has no such value, so the catalog and docs offered a value that fails endpoint creation. It is removed. With a null body the producer created a new instance of the expected type: for operation NONE an empty record of the entityType, which was then stored, and for EXECUTE and FETCH an org.jooq.Query or ResultQuery, which are interfaces and failed with NoSuchMethodException. The producer now fails with an InvalidPayloadException that says what the operation needs. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Claus Ibsen <[email protected]> --- .../org/apache/camel/catalog/components/jooq.json | 4 +- .../apache/camel/catalog/docs/jooq-component.adoc | 3 + .../org/apache/camel/component/jooq/jooq.json | 4 +- .../camel-jooq/src/main/docs/jooq-component.adoc | 3 + .../camel/component/jooq/JooqConfiguration.java | 2 +- .../apache/camel/component/jooq/JooqEndpoint.java | 9 ++- .../camel/component/jooq/JooqComponentTest.java | 12 +++ .../component/jooq/JooqProducerEmptyBodyTest.java | 93 ++++++++++++++++++++++ .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc | 8 ++ 9 files changed, 132 insertions(+), 6 deletions(-) diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/jooq.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/jooq.json index bce0ae172257..d6c6e38ce898 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/jooq.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/jooq.json @@ -29,7 +29,7 @@ "bridgeErrorHandler": { "index": 2, "kind": "property", "displayName": "Bridge Error Handler", "group": "consumer", "label": "consumer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Allows for bridging the consumer to the Camel routing Error Handler, which mean any exceptions (if possible) occurred while the Camel consumer is trying to pickup incoming messages, or the like [...] "consumeDelete": { "index": 3, "kind": "property", "displayName": "Consume Delete", "group": "consumer", "label": "consumer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "configurationClass": "org.apache.camel.component.jooq.JooqConfiguration", "configurationField": "configuration", "description": "Delete entity after it is consumed" }, "lazyStartProducer": { "index": 4, "kind": "property", "displayName": "Lazy Start Producer", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail [...] - "operation": { "index": 5, "kind": "property", "displayName": "Operation", "group": "producer", "label": "producer", "required": false, "type": "enum", "javaType": "org.apache.camel.component.jooq.JooqOperation", "enum": [ "NONE", "EXECUTE", "FETCH", "CONSUME" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "NONE", "configurationClass": "org.apache.camel.component.jooq.JooqConfiguration", "configurationField": "configuration", "description": "Type of oper [...] + "operation": { "index": 5, "kind": "property", "displayName": "Operation", "group": "producer", "label": "producer", "required": false, "type": "enum", "javaType": "org.apache.camel.component.jooq.JooqOperation", "enum": [ "NONE", "EXECUTE", "FETCH" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "NONE", "configurationClass": "org.apache.camel.component.jooq.JooqConfiguration", "configurationField": "configuration", "description": "Type of operation to ex [...] "query": { "index": 6, "kind": "property", "displayName": "Query", "group": "producer", "label": "producer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.jooq.JooqConfiguration", "configurationField": "configuration", "description": "To execute plain SQL query" }, "autowiredEnabled": { "index": 7, "kind": "property", "displayName": "Autowired Enabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether autowiring is enabled. This is used for automatic autowiring options (the option must be marked as autowired) by looking up in the registry to find if there is a single instance of matching t [...] "healthCheckConsumerEnabled": { "index": 8, "kind": "property", "displayName": "Health Check Consumer Enabled", "group": "health", "label": "health", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Used for enabling or disabling all consumer based health checks from this component" }, @@ -49,7 +49,7 @@ "exceptionHandler": { "index": 5, "kind": "parameter", "displayName": "Exception Handler", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "object", "javaType": "org.apache.camel.spi.ExceptionHandler", "optionalPrefix": "consumer.", "deprecated": false, "autowired": false, "secret": false, "description": "To let the consumer use a custom ExceptionHandler. Notice if the option bridgeErrorHandler is enabled then this option is not in use. By def [...] "exchangePattern": { "index": 6, "kind": "parameter", "displayName": "Exchange Pattern", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "enum", "javaType": "org.apache.camel.ExchangePattern", "enum": [ "InOnly", "InOut" ], "deprecated": false, "autowired": false, "secret": false, "description": "Sets the exchange pattern when the consumer creates an exchange." }, "pollStrategy": { "index": 7, "kind": "parameter", "displayName": "Poll Strategy", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "object", "javaType": "org.apache.camel.spi.PollingConsumerPollStrategy", "deprecated": false, "autowired": false, "secret": false, "description": "A pluggable org.apache.camel.PollingConsumerPollingStrategy allowing you to provide your custom implementation to control error handling usually occurred during the pol [...] - "operation": { "index": 8, "kind": "parameter", "displayName": "Operation", "group": "producer", "label": "producer", "required": false, "type": "enum", "javaType": "org.apache.camel.component.jooq.JooqOperation", "enum": [ "NONE", "EXECUTE", "FETCH", "CONSUME" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "NONE", "configurationClass": "org.apache.camel.component.jooq.JooqConfiguration", "configurationField": "configuration", "description": "Type of ope [...] + "operation": { "index": 8, "kind": "parameter", "displayName": "Operation", "group": "producer", "label": "producer", "required": false, "type": "enum", "javaType": "org.apache.camel.component.jooq.JooqOperation", "enum": [ "NONE", "EXECUTE", "FETCH" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "NONE", "configurationClass": "org.apache.camel.component.jooq.JooqConfiguration", "configurationField": "configuration", "description": "Type of operation to e [...] "query": { "index": 9, "kind": "parameter", "displayName": "Query", "group": "producer", "label": "producer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.jooq.JooqConfiguration", "configurationField": "configuration", "description": "To execute plain SQL query" }, "lazyStartProducer": { "index": 10, "kind": "parameter", "displayName": "Lazy Start Producer", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a produ [...] "backoffErrorThreshold": { "index": 11, "kind": "parameter", "displayName": "Backoff Error Threshold", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "description": "The number of subsequent error polls (failed due some error) that should happen before the backoffMultipler should kick-in." }, diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/jooq-component.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/jooq-component.adoc index cf61ef759ff8..915fd415469f 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/jooq-component.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/jooq-component.adoc @@ -109,6 +109,9 @@ When using jooq as a producer you can use any of the following `JooqOperation` o | fetch | Execute a query (an `org.jooq.ResultQuery` in the message body, or the `query` option) and the `org.jooq.Result` of the query is stored as the new message body |=== +A message with no body (and no `query` option for `execute` and `fetch`) fails with an `InvalidPayloadException`. +The `none` operation does not store an empty record. + == Example .JOOQ configuration: diff --git a/components/camel-jooq/src/generated/resources/META-INF/org/apache/camel/component/jooq/jooq.json b/components/camel-jooq/src/generated/resources/META-INF/org/apache/camel/component/jooq/jooq.json index bce0ae172257..d6c6e38ce898 100644 --- a/components/camel-jooq/src/generated/resources/META-INF/org/apache/camel/component/jooq/jooq.json +++ b/components/camel-jooq/src/generated/resources/META-INF/org/apache/camel/component/jooq/jooq.json @@ -29,7 +29,7 @@ "bridgeErrorHandler": { "index": 2, "kind": "property", "displayName": "Bridge Error Handler", "group": "consumer", "label": "consumer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Allows for bridging the consumer to the Camel routing Error Handler, which mean any exceptions (if possible) occurred while the Camel consumer is trying to pickup incoming messages, or the like [...] "consumeDelete": { "index": 3, "kind": "property", "displayName": "Consume Delete", "group": "consumer", "label": "consumer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "configurationClass": "org.apache.camel.component.jooq.JooqConfiguration", "configurationField": "configuration", "description": "Delete entity after it is consumed" }, "lazyStartProducer": { "index": 4, "kind": "property", "displayName": "Lazy Start Producer", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a producer may otherwise fail [...] - "operation": { "index": 5, "kind": "property", "displayName": "Operation", "group": "producer", "label": "producer", "required": false, "type": "enum", "javaType": "org.apache.camel.component.jooq.JooqOperation", "enum": [ "NONE", "EXECUTE", "FETCH", "CONSUME" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "NONE", "configurationClass": "org.apache.camel.component.jooq.JooqConfiguration", "configurationField": "configuration", "description": "Type of oper [...] + "operation": { "index": 5, "kind": "property", "displayName": "Operation", "group": "producer", "label": "producer", "required": false, "type": "enum", "javaType": "org.apache.camel.component.jooq.JooqOperation", "enum": [ "NONE", "EXECUTE", "FETCH" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "NONE", "configurationClass": "org.apache.camel.component.jooq.JooqConfiguration", "configurationField": "configuration", "description": "Type of operation to ex [...] "query": { "index": 6, "kind": "property", "displayName": "Query", "group": "producer", "label": "producer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.jooq.JooqConfiguration", "configurationField": "configuration", "description": "To execute plain SQL query" }, "autowiredEnabled": { "index": 7, "kind": "property", "displayName": "Autowired Enabled", "group": "advanced", "label": "advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether autowiring is enabled. This is used for automatic autowiring options (the option must be marked as autowired) by looking up in the registry to find if there is a single instance of matching t [...] "healthCheckConsumerEnabled": { "index": 8, "kind": "property", "displayName": "Health Check Consumer Enabled", "group": "health", "label": "health", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Used for enabling or disabling all consumer based health checks from this component" }, @@ -49,7 +49,7 @@ "exceptionHandler": { "index": 5, "kind": "parameter", "displayName": "Exception Handler", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "object", "javaType": "org.apache.camel.spi.ExceptionHandler", "optionalPrefix": "consumer.", "deprecated": false, "autowired": false, "secret": false, "description": "To let the consumer use a custom ExceptionHandler. Notice if the option bridgeErrorHandler is enabled then this option is not in use. By def [...] "exchangePattern": { "index": 6, "kind": "parameter", "displayName": "Exchange Pattern", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "enum", "javaType": "org.apache.camel.ExchangePattern", "enum": [ "InOnly", "InOut" ], "deprecated": false, "autowired": false, "secret": false, "description": "Sets the exchange pattern when the consumer creates an exchange." }, "pollStrategy": { "index": 7, "kind": "parameter", "displayName": "Poll Strategy", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "object", "javaType": "org.apache.camel.spi.PollingConsumerPollStrategy", "deprecated": false, "autowired": false, "secret": false, "description": "A pluggable org.apache.camel.PollingConsumerPollingStrategy allowing you to provide your custom implementation to control error handling usually occurred during the pol [...] - "operation": { "index": 8, "kind": "parameter", "displayName": "Operation", "group": "producer", "label": "producer", "required": false, "type": "enum", "javaType": "org.apache.camel.component.jooq.JooqOperation", "enum": [ "NONE", "EXECUTE", "FETCH", "CONSUME" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "NONE", "configurationClass": "org.apache.camel.component.jooq.JooqConfiguration", "configurationField": "configuration", "description": "Type of ope [...] + "operation": { "index": 8, "kind": "parameter", "displayName": "Operation", "group": "producer", "label": "producer", "required": false, "type": "enum", "javaType": "org.apache.camel.component.jooq.JooqOperation", "enum": [ "NONE", "EXECUTE", "FETCH" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "NONE", "configurationClass": "org.apache.camel.component.jooq.JooqConfiguration", "configurationField": "configuration", "description": "Type of operation to e [...] "query": { "index": 9, "kind": "parameter", "displayName": "Query", "group": "producer", "label": "producer", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "configurationClass": "org.apache.camel.component.jooq.JooqConfiguration", "configurationField": "configuration", "description": "To execute plain SQL query" }, "lazyStartProducer": { "index": 10, "kind": "parameter", "displayName": "Lazy Start Producer", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a produ [...] "backoffErrorThreshold": { "index": 11, "kind": "parameter", "displayName": "Backoff Error Threshold", "group": "scheduler", "label": "consumer,scheduler", "required": false, "type": "integer", "javaType": "int", "deprecated": false, "autowired": false, "secret": false, "description": "The number of subsequent error polls (failed due some error) that should happen before the backoffMultipler should kick-in." }, diff --git a/components/camel-jooq/src/main/docs/jooq-component.adoc b/components/camel-jooq/src/main/docs/jooq-component.adoc index cf61ef759ff8..915fd415469f 100644 --- a/components/camel-jooq/src/main/docs/jooq-component.adoc +++ b/components/camel-jooq/src/main/docs/jooq-component.adoc @@ -109,6 +109,9 @@ When using jooq as a producer you can use any of the following `JooqOperation` o | fetch | Execute a query (an `org.jooq.ResultQuery` in the message body, or the `query` option) and the `org.jooq.Result` of the query is stored as the new message body |=== +A message with no body (and no `query` option for `execute` and `fetch`) fails with an `InvalidPayloadException`. +The `none` operation does not store an empty record. + == Example .JOOQ configuration: diff --git a/components/camel-jooq/src/main/java/org/apache/camel/component/jooq/JooqConfiguration.java b/components/camel-jooq/src/main/java/org/apache/camel/component/jooq/JooqConfiguration.java index 0352e048a25a..778ab66c5a10 100644 --- a/components/camel-jooq/src/main/java/org/apache/camel/component/jooq/JooqConfiguration.java +++ b/components/camel-jooq/src/main/java/org/apache/camel/component/jooq/JooqConfiguration.java @@ -29,7 +29,7 @@ public class JooqConfiguration implements Cloneable { private Class<?> entityType; @UriParam(label = "producer", description = "Type of operation to execute on query", defaultValue = "NONE", - enums = "NONE,EXECUTE,FETCH,CONSUME") + enums = "NONE,EXECUTE,FETCH") private JooqOperation operation = JooqOperation.NONE; @UriParam(label = "consumer", defaultValue = "true", description = "Delete entity after it is consumed") diff --git a/components/camel-jooq/src/main/java/org/apache/camel/component/jooq/JooqEndpoint.java b/components/camel-jooq/src/main/java/org/apache/camel/component/jooq/JooqEndpoint.java index a8b50cb6c5a1..f06597c44135 100644 --- a/components/camel-jooq/src/main/java/org/apache/camel/component/jooq/JooqEndpoint.java +++ b/components/camel-jooq/src/main/java/org/apache/camel/component/jooq/JooqEndpoint.java @@ -20,6 +20,7 @@ import org.apache.camel.Category; import org.apache.camel.Consumer; import org.apache.camel.Exchange; import org.apache.camel.Expression; +import org.apache.camel.InvalidPayloadException; import org.apache.camel.NoTypeConversionAvailableException; import org.apache.camel.Processor; import org.apache.camel.Producer; @@ -86,16 +87,20 @@ public class JooqEndpoint extends ScheduledPollEndpoint { protected Expression createProducerExpression() { final Class<?> type; + final String hint; switch (configuration.getOperation()) { case NONE: type = configuration.getEntityType(); + hint = "operation NONE needs a record of the entityType in the message body"; break; case EXECUTE: type = Query.class; + hint = "operation EXECUTE needs an org.jooq.Query in the message body, or the query option"; break; case FETCH: type = ResultQuery.class; + hint = "operation FETCH needs an org.jooq.ResultQuery in the message body, or the query option"; break; default: throw new UnsupportedOperationException("Operation: " + configuration.getOperation()); @@ -111,7 +116,9 @@ public class JooqEndpoint extends ScheduledPollEndpoint { .wrapRuntimeCamelException(new NoTypeConversionAvailableException(defaultValue, type)); } - answer = exchange.getContext().getInjector().newInstance(type); + throw RuntimeCamelException + .wrapRuntimeCamelException( + new InvalidPayloadException(exchange, type, exchange.getIn(), hint)); } return answer; } diff --git a/components/camel-jooq/src/test/java/org/apache/camel/component/jooq/JooqComponentTest.java b/components/camel-jooq/src/test/java/org/apache/camel/component/jooq/JooqComponentTest.java index a8c8c884b123..c22a309b1d37 100644 --- a/components/camel-jooq/src/test/java/org/apache/camel/component/jooq/JooqComponentTest.java +++ b/components/camel-jooq/src/test/java/org/apache/camel/component/jooq/JooqComponentTest.java @@ -16,8 +16,11 @@ */ package org.apache.camel.component.jooq; +import java.util.Arrays; + import org.apache.camel.PropertyBindingException; import org.apache.camel.component.jooq.db.tables.records.BookStoreRecord; +import org.apache.camel.spi.UriParam; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -52,4 +55,13 @@ public class JooqComponentTest extends BaseJooqTest { () -> component.createEndpoint( "jooq://org.apache.camel.component.jooq.db.tables.records.BookStoreRecord?operation=unexpectedOperation")); } + + @Test + public void testOperationEnumsMatchJooqOperation() throws Exception { + // the enums of the operation option are what the catalog and the docs offer + UriParam uriParam = JooqConfiguration.class.getDeclaredField("operation").getAnnotation(UriParam.class); + String expected = String.join(",", Arrays.stream(JooqOperation.values()).map(Enum::name).sorted().toList()); + String actual = String.join(",", Arrays.stream(uriParam.enums().split(",")).sorted().toList()); + assertEquals(expected, actual); + } } diff --git a/components/camel-jooq/src/test/java/org/apache/camel/component/jooq/JooqProducerEmptyBodyTest.java b/components/camel-jooq/src/test/java/org/apache/camel/component/jooq/JooqProducerEmptyBodyTest.java new file mode 100644 index 000000000000..cbda40133c56 --- /dev/null +++ b/components/camel-jooq/src/test/java/org/apache/camel/component/jooq/JooqProducerEmptyBodyTest.java @@ -0,0 +1,93 @@ +/* + * 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.jooq; + +import org.apache.camel.CamelExecutionException; +import org.apache.camel.InvalidPayloadException; +import org.apache.camel.ProducerTemplate; +import org.apache.camel.RoutesBuilder; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.component.jooq.db.tables.records.BookStoreRecord; +import org.apache.camel.util.ObjectHelper; +import org.junit.jupiter.api.Test; + +import static org.apache.camel.component.jooq.db.Tables.BOOK_STORE; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * A message with no body must fail with a clear message, and must not store an empty record. + */ +public class JooqProducerEmptyBodyTest extends BaseJooqTest { + + @Test + public void testStoreWithNoBodyFails() { + InvalidPayloadException cause = sendNullBody("direct:insert"); + assertEquals(BookStoreRecord.class, cause.getType()); + assertTrue(cause.getMessage().contains("operation NONE needs a record of the entityType"), cause.getMessage()); + + assertEquals(0, create.fetchCount(BOOK_STORE), "An empty record must not be stored"); + } + + @Test + public void testExecuteWithNoBodyFails() { + InvalidPayloadException cause = sendNullBody("direct:execute"); + assertTrue(cause.getMessage().contains("operation EXECUTE needs an org.jooq.Query in the message body, or the query"), + cause.getMessage()); + } + + @Test + public void testFetchWithNoBodyFails() { + InvalidPayloadException cause = sendNullBody("direct:fetch"); + assertTrue( + cause.getMessage().contains("operation FETCH needs an org.jooq.ResultQuery in the message body, or the query"), + cause.getMessage()); + } + + private InvalidPayloadException sendNullBody(String uri) { + ProducerTemplate producerTemplate = context.createProducerTemplate(); + CamelExecutionException e = assertThrows(CamelExecutionException.class, + () -> producerTemplate.sendBody(uri, null)); + InvalidPayloadException cause = ObjectHelper.getException(InvalidPayloadException.class, e); + assertNotNull(cause, "Expected an InvalidPayloadException but got: " + e); + return cause; + } + + @Override + protected RoutesBuilder createRouteBuilder() { + JooqComponent jooqComponent = (JooqComponent) context().getComponent("jooq"); + JooqConfiguration jooqConfiguration = new JooqConfiguration(); + jooqConfiguration.setDatabaseConfiguration(create.configuration()); + jooqComponent.setConfiguration(jooqConfiguration); + + return new RouteBuilder() { + @Override + public void configure() { + from("direct:insert") + .to("jooq://org.apache.camel.component.jooq.db.tables.records.BookStoreRecord"); + + from("direct:execute") + .to("jooq://org.apache.camel.component.jooq.db.tables.records.BookStoreRecord?operation=execute"); + + from("direct:fetch") + .to("jooq://org.apache.camel.component.jooq.db.tables.records.BookStoreRecord?operation=fetch"); + } + }; + } +} 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..5e9a7ca6d673 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 @@ -986,6 +986,14 @@ with no stored metadata for the key, now logs a warning once per producer rather non-stateful key without a `keyLifecycleManager` stays silent, as there is no `maxKeyUsage` threshold to miss. For a stateful key the warning names the index-reuse risk; for any other key it names the `usageCount` that will not advance. +=== camel-jooq - a message with no body fails + +The jooq producer now fails with an `InvalidPayloadException` when the message body is null and the `query` option is +not set. Before, operation `NONE` (the default) created a new, empty record of the `entityType` and stored it, so a +message with no body inserted an empty row (or failed with a database constraint error). Operations `EXECUTE` and +`FETCH` failed with a `NoSuchMethodException` for `org.jooq.Query.<init>()`. Send the record, or the `org.jooq.Query`, in +the message body. The `operation` option also no longer lists `CONSUME`, which was never a valid value. + === camel-jsonpath - easy predicates with `+<=+` and `+>=+` match the boundary value An easy predicate (such as `+store.book.price <= 10+` or `+$.store.book.price >= 10+`) now matches the boundary
