Files
2025.2/tmp/VariableGetPathFunction02.java
Andreas Isler 2f9c0a5c9e Initial commit
2026-05-22 14:59:58 +02:00

142 lines
5.1 KiB
Java

package com.customer.work.service;
import com.fasterxml.jackson.databind.JsonNode;
import org.flowable.common.engine.api.variable.VariableContainer;
import org.flowable.common.engine.impl.el.function.AbstractFlowableVariableExpressionFunction;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
/**
* Flowable EL function delegate that reads a variable by dot-separated path.
* Works in both BPMN processes and CMMN cases without any explicit scope parameter.
*
* Flowable's AbstractFlowableVariableExpressionFunction automatically injects the
* current VariableContainer (execution / planItemInstance / caseInstance) as the
* first argument before the expression is evaluated, so no ThreadLocal or listener
* is required.
*
* Registered automatically by Flowable's Spring Boot integration when it finds a
* FlowableFunctionDelegate bean in the application context.
*
* Usage in a Flowable backend expression (prefixes var / vars / variables all work):
* ${var:getPath('myVar')}
* ${var:getPath('order.customer.name')}
* ${var:getPath('order.lines.0.price')} ← list index by position
*
* Path resolution per segment:
* - java.util.List / array → integer index (e.g. "2")
* - java.util.Map → key lookup
* - Jackson JsonNode → field lookup, scalar nodes unwrapped to Java types
* - POJO → public getter (getX / isX) or field, superclass included
*
* Returns null when any segment along the path does not exist.
*/
@Component
public class VariableGetPathFunction extends AbstractFlowableVariableExpressionFunction {
public VariableGetPathFunction() {
super(List.of("getPath"), "getPath");
}
/**
* Called by Flowable's EL engine. The {@code container} is injected automatically;
* {@code path} is the dot-separated path string supplied in the expression.
*/
public static Object getPath(VariableContainer container, String path) {
if (container == null || path == null || path.isBlank()) {
return null;
}
String[] segments = path.split("\\.", -1);
Object current = container.getVariable(segments[0]);
for (int i = 1; i < segments.length; i++) {
if (current == null) {
return null;
}
current = step(current, segments[i]);
}
return current;
}
// -------------------------------------------------------------------------
// Path navigation
// -------------------------------------------------------------------------
@SuppressWarnings("unchecked")
private static Object step(Object obj, String segment) {
// List / array index
if (obj instanceof List<?> list) {
try {
int idx = Integer.parseInt(segment);
return (idx >= 0 && idx < list.size()) ? list.get(idx) : null;
} catch (NumberFormatException ignored) {
}
}
if (obj instanceof Object[] arr) {
try {
int idx = Integer.parseInt(segment);
return (idx >= 0 && idx < arr.length) ? arr[idx] : null;
} catch (NumberFormatException ignored) {
}
}
// Map key lookup
if (obj instanceof Map<?, ?> map) {
return map.get(segment);
}
// Jackson JsonNode — unwrap scalar nodes to plain Java values
if (obj instanceof JsonNode jn) {
JsonNode node = jn.get(segment);
if (node == null || node.isNull()) return null;
if (node.isTextual()) return node.asText();
if (node.isBoolean()) return node.asBoolean();
if (node.isLong()) return node.asLong();
if (node.isInt()) return node.asInt();
if (node.isDouble()) return node.asDouble();
return node;
}
// POJO: try getter (getX / isX), then field with superclass walk
try {
Method m = obj.getClass().getMethod(
"get" + Character.toUpperCase(segment.charAt(0)) + segment.substring(1));
return m.invoke(obj);
} catch (Exception ignored) {
}
try {
Method m = obj.getClass().getMethod(
"is" + Character.toUpperCase(segment.charAt(0)) + segment.substring(1));
return m.invoke(obj);
} catch (Exception ignored) {
}
try {
java.lang.reflect.Field f = findField(obj.getClass(), segment);
if (f != null) {
f.setAccessible(true);
return f.get(obj);
}
} catch (Exception ignored) {
}
return null;
}
private static java.lang.reflect.Field findField(Class<?> clazz, String name) {
while (clazz != null && clazz != Object.class) {
try {
return clazz.getDeclaredField(name);
} catch (NoSuchFieldException e) {
clazz = clazz.getSuperclass();
}
}
return null;
}
}