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 137d447a68b1 CAMEL-26146: camel-jooq - fail on an empty body and drop
the CONSUME operation from the catalog (#28917)
137d447a68b1 is described below
commit 137d447a68b15f2e564c576b127c30aa3c85cefe
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Nov 2 20:44:55 2026 +0100
CAMEL-26146: camel-jooq - fail on an empty body and drop the CONSUME
operation from the catalog (#28917)
…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 5c38a93ddb1e..41f6ca1f15c1 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
@@ -1066,6 +1066,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