142 lines
5.1 KiB
Java
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;
|
|
}
|
|
}
|