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 1239c76ac3c1 CAMEL-25746: camel-bean - the error for a key read from a
stream, JSON text or a List says what to do (#28042)
1239c76ac3c1 is described below
commit 1239c76ac3c17dbc468e107918c2e4adba7f8509
Author: Claus Ibsen <[email protected]>
AuthorDate: Sun Oct 18 20:53:53 2026 +0200
CAMEL-25746: camel-bean - the error for a key read from a stream, JSON text
or a List says what to do (#28042)
"Key: x not found in bean", which Simple raises for ${body[x]} and
${variable.v[x]}, named the problem but not the
fix. It now adds a hint for a value that has no keys: the message body as a
stream or bytes (unmarshal it first; a
variable keeps the value it had when it was set), JSON text (unmarshal it
first), and a List, an array or another
Collection (pick an element first, such as [0][qty], or split it). Other
text keeps the message as it was.
The hint for a Map or List as the platform-http response body, the first
part of this change, is covered on main by
CAMEL-25751 and CAMEL-25749.
Signed-off-by: Claus Ibsen <[email protected]>
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01STT6whBgK1AqsSsUKrnE8m
---
.../apache/camel/language/bean/BeanExpression.java | 27 +++++++++++-
.../apache/camel/language/simple/SimpleTest.java | 48 ++++++++++++++++++++++
2 files changed, 74 insertions(+), 1 deletion(-)
diff --git
a/components/camel-bean/src/main/java/org/apache/camel/language/bean/BeanExpression.java
b/components/camel-bean/src/main/java/org/apache/camel/language/bean/BeanExpression.java
index bdc6d8a8757e..9158f62e694d 100644
---
a/components/camel-bean/src/main/java/org/apache/camel/language/bean/BeanExpression.java
+++
b/components/camel-bean/src/main/java/org/apache/camel/language/bean/BeanExpression.java
@@ -16,6 +16,8 @@
*/
package org.apache.camel.language.bean;
+import java.io.InputStream;
+import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@@ -29,6 +31,7 @@ import org.apache.camel.ExpressionIllegalSyntaxException;
import org.apache.camel.NoSuchBeanException;
import org.apache.camel.Predicate;
import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.StreamCache;
import org.apache.camel.TypeConverter;
import org.apache.camel.component.bean.BeanComponent;
import org.apache.camel.component.bean.BeanConstants;
@@ -687,11 +690,33 @@ public class BeanExpression implements Expression,
Predicate {
throw new IndexOutOfBoundsException(
"Key: " + key + " not found in bean: " + bean + " of type:
"
+
ObjectHelper.classCanonicalName(bean) + " using OGNL path [" + ognlPath
- + "]");
+ + "]" + noKeysHint(result !=
null ? result : bean, key));
} else {
// null safe so we can return null
return null;
}
}
+ /**
+ * What to do when a key is read from a value that has no keys
(CAMEL-25746): the message body as a stream or bytes
+ * (a request body before it is unmarshalled, or a variable set from it),
JSON text, or a List, array or other
+ * Collection, where [0] picks an element.
+ */
+ static String noKeysHint(Object value, String key) {
+ if (value instanceof StreamCache || value instanceof InputStream ||
value instanceof byte[]) {
+ return " (the value is the message body as a stream, not a Map:
unmarshal it first, for example with json,"
+ + " then read the key; a variable keeps the value it had
when it was set)";
+ }
+ if (value instanceof CharSequence text) {
+ String trimmed = text.toString().strip();
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
+ return " (the value is JSON text, not a Map: unmarshal it
first, for example with json, then read the key)";
+ }
+ }
+ if (value instanceof Collection || value instanceof Object[]) {
+ String type = value instanceof List ? "a List" : value instanceof
Object[] ? "an array" : "a Collection";
+ return " (" + type + " has no keys: pick an element first, such as
[0][" + key + "], or split it)";
+ }
+ return "";
+ }
}
diff --git
a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleTest.java
b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleTest.java
index 3e29ee9a5e56..9ebb4de8394c 100644
---
a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleTest.java
+++
b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleTest.java
@@ -16,6 +16,7 @@
*/
package org.apache.camel.language.simple;
+import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
@@ -521,6 +522,53 @@ public class SimpleTest extends LanguageTestSupport {
cause.getMessage());
}
+ @Test
+ public void testOGNLKeyOnAStreamSaysToUnmarshal() {
+ // CAMEL-25746: the request body as a stream, before it is unmarshalled
+ exchange.getIn().setBody(new ByteArrayInputStream("{\"qty\":
2}".getBytes(StandardCharsets.UTF_8)));
+ RuntimeBeanExpressionException e =
assertThrows(RuntimeBeanExpressionException.class,
+ () -> assertExpression("${body[qty]}", null));
+
+ IndexOutOfBoundsException cause =
assertIsInstanceOf(IndexOutOfBoundsException.class, e.getCause());
+ assertTrue(cause.getMessage().startsWith("Key: qty not found in bean:
"), cause.getMessage());
+ assertTrue(cause.getMessage().contains("the value is the message body
as a stream, not a Map: unmarshal it first"),
+ cause.getMessage());
+ }
+
+ @Test
+ public void testOGNLKeyOnJsonTextSaysToUnmarshal() {
+ exchange.getIn().setHeader("order", "{\"qty\": 2}");
+ RuntimeBeanExpressionException e =
assertThrows(RuntimeBeanExpressionException.class,
+ () -> assertExpression("${header.order[qty]}", null));
+
+ IndexOutOfBoundsException cause =
assertIsInstanceOf(IndexOutOfBoundsException.class, e.getCause());
+ assertTrue(cause.getMessage().contains("the value is JSON text, not a
Map: unmarshal it first"), cause.getMessage());
+ }
+
+ @Test
+ public void testOGNLKeyOnAListSaysToPickAnElement() {
+ exchange.getIn().setBody(List.of(Map.of("sku", "CAMEL-MUG", "qty",
42)));
+ RuntimeBeanExpressionException e =
assertThrows(RuntimeBeanExpressionException.class,
+ () -> assertExpression("${body[qty]}", null));
+
+ IndexOutOfBoundsException cause =
assertIsInstanceOf(IndexOutOfBoundsException.class, e.getCause());
+ assertTrue(cause.getMessage().contains("a List has no keys: pick an
element first, such as [0][qty]"),
+ cause.getMessage());
+ }
+
+ @Test
+ public void testOGNLKeyOnAnArraySaysToPickAnElement() {
+ exchange.getIn().setBody(new Object[] { Map.of("sku", "CAMEL-MUG",
"qty", 42) });
+ RuntimeBeanExpressionException e =
assertThrows(RuntimeBeanExpressionException.class,
+ () -> assertExpression("${body[qty]}", null));
+
+ IndexOutOfBoundsException cause =
assertIsInstanceOf(IndexOutOfBoundsException.class, e.getCause());
+ assertTrue(cause.getMessage().contains("an array has no keys: pick an
element first, such as [0][qty]"),
+ cause.getMessage());
+ // [0] picks the element the hint points to
+ assertExpression("${body[0][qty]}", 42);
+ }
+
@Test
public void testOGNLPropertyMapIllegalSyntax() {
ExpressionIllegalSyntaxException e =
assertThrows(ExpressionIllegalSyntaxException.class,