Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -29,6 +31,7 @@
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;
Expand Down Expand Up @@ -687,11 +690,33 @@ private static Object lookupResult(
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 "";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -521,6 +522,53 @@ public void testOGNLPropertyMapNotMap() {
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,
Expand Down
Loading