Skip to content

Commit d3b9b59

Browse files
authored
runtime-v2: allow plugins to supply custom EL functions (#1225)
1 parent aadcda3 commit d3b9b59

File tree

23 files changed

+262
-162
lines changed

23 files changed

+262
-162
lines changed

runtime/v2/runner-test/src/test/java/com/walmartlabs/concord/runtime/v2/runner/MainTest.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1746,6 +1746,20 @@ public void flowCallOutExpressionCompat() throws Exception {
17461746
assertLog(runtime.allLogs(), ".*" + Pattern.quote("out as map with loop: [abc_0, abc_1]") + ".*");
17471747
}
17481748

1749+
@Test
1750+
public void prefixedFunctionsInExpressions() throws Exception {
1751+
deploy("prefixedFunctions");
1752+
1753+
save(ProcessConfiguration.builder()
1754+
.putArguments("name", "world")
1755+
.build());
1756+
1757+
run();
1758+
1759+
assertLog(runtime.allLogs(), ".*Hi, world!.*");
1760+
assertLog(runtime.allLogs(), ".*Hello, world!.*");
1761+
}
1762+
17491763
private void deploy(String name) throws URISyntaxException, IOException {
17501764
runtime.deploy(name);
17511765
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.walmartlabs.concord.runtime.v2.runner.functions;
2+
3+
import com.walmartlabs.concord.runtime.v2.sdk.ELFunction;
4+
5+
import javax.inject.Named;
6+
7+
@Named
8+
public class TestFunction {
9+
10+
@ELFunction("testGreet")
11+
public static String regularGreet(String name) {
12+
return "Hi, " + name + "!";
13+
}
14+
15+
@ELFunction("testFunction:greet")
16+
public static String prefixedGreet(String name) {
17+
return "Hello, " + name + "!";
18+
}
19+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
flows:
2+
default:
3+
- log: "${testGreet(name)}"
4+
- log: "${testFunction:greet(name)}"

runtime/v2/runner/src/main/java/com/walmartlabs/concord/runtime/v2/runner/el/DefaultExpressionEvaluator.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,11 @@ public class DefaultExpressionEvaluator implements ExpressionEvaluator {
3636

3737
@Inject
3838
public DefaultExpressionEvaluator(TaskProviders taskProviders,
39+
FunctionHolder functionHolder,
3940
List<CustomTaskMethodResolver> taskMethodResolvers,
4041
List<CustomBeanMethodResolver> beanMethodResolvers,
4142
SensitiveDataProcessor sensitiveDataProcessor) {
42-
this.delegate = new LazyExpressionEvaluator(taskProviders, taskMethodResolvers, beanMethodResolvers, sensitiveDataProcessor);
43+
this.delegate = new LazyExpressionEvaluator(taskProviders, functionHolder, taskMethodResolvers, beanMethodResolvers, sensitiveDataProcessor);
4344
}
4445

4546
@Override
Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,21 +22,23 @@
2222

2323
import java.lang.reflect.Method;
2424
import java.util.Map;
25+
import java.util.Set;
26+
import java.util.concurrent.ConcurrentHashMap;
2527

26-
public class FunctionMapper extends javax.el.FunctionMapper {
28+
public class FunctionHolder {
2729

28-
private final Map<String, Method> functions;
30+
private final Map<String, Method> functions = new ConcurrentHashMap<>();
2931

30-
public FunctionMapper(Map<String, Method> functions) {
31-
this.functions = functions;
32+
public FunctionHolder register(String name, Method method) {
33+
functions.put(name, method);
34+
return this;
3235
}
3336

34-
@Override
35-
public Method resolveFunction(String prefix, String localName) {
36-
if (prefix == null || prefix.trim().isEmpty()) {
37-
return functions.get(localName);
38-
}
37+
public Method resolve(String name) {
38+
return functions.get(name);
39+
}
3940

40-
return functions.get(prefix + ":" + localName);
41+
public Set<String> names() {
42+
return Set.copyOf(functions.keySet());
4143
}
4244
}

runtime/v2/runner/src/main/java/com/walmartlabs/concord/runtime/v2/runner/el/LazyExpressionEvaluator.java

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,8 @@
2222

2323
import com.walmartlabs.concord.common.ConfigurationUtils;
2424
import com.walmartlabs.concord.common.ExceptionUtils;
25-
import com.walmartlabs.concord.runtime.v2.runner.el.functions.*;
26-
import com.walmartlabs.concord.runtime.v2.runner.el.resolvers.MapELResolver;
2725
import com.walmartlabs.concord.runtime.v2.runner.el.resolvers.*;
26+
import com.walmartlabs.concord.runtime.v2.runner.el.resolvers.MapELResolver;
2827
import com.walmartlabs.concord.runtime.v2.runner.tasks.TaskProviders;
2928
import com.walmartlabs.concord.runtime.v2.runner.vm.WrappedException;
3029
import com.walmartlabs.concord.runtime.v2.sdk.*;
@@ -50,12 +49,13 @@ public class LazyExpressionEvaluator implements ExpressionEvaluator {
5049
private final SensitiveDataProcessor sensitiveDataProcessor;
5150

5251
public LazyExpressionEvaluator(TaskProviders taskProviders,
52+
FunctionHolder functionHolder,
5353
List<CustomTaskMethodResolver> taskMethodResolvers,
5454
List<CustomBeanMethodResolver> beanMethodResolvers,
5555
SensitiveDataProcessor sensitiveDataProcessor) {
5656
this.taskProviders = taskProviders;
5757
this.sensitiveDataProcessor = sensitiveDataProcessor;
58-
this.functionMapper = createFunctionMapper();
58+
this.functionMapper = new DelegatingFunctionMapper(functionHolder);
5959
this.taskMethodResolvers = taskMethodResolvers;
6060
this.beanMethodResolvers = beanMethodResolvers;
6161
}
@@ -203,22 +203,6 @@ private ELResolver createResolver(LazyEvalContext evalContext,
203203
return r;
204204
}
205205

206-
private static FunctionMapper createFunctionMapper() {
207-
var functions = new HashMap<String, Method>();
208-
functions.put("hasVariable", HasVariableFunction.getMethod());
209-
functions.put("hasNonNullVariable", HasNonNullVariableFunction.getMethod());
210-
functions.put("orDefault", OrDefaultFunction.getMethod());
211-
functions.put("allVariables", AllVariablesFunction.getMethod());
212-
functions.put("currentFlowName", CurrentFlowNameFunction.getMethod());
213-
functions.put("evalAsMap", EvalAsMapFunction.getMethod());
214-
functions.put("isDebug", IsDebugFunction.getMethod());
215-
functions.put("throw", ThrowFunction.getMethod());
216-
functions.put("hasFlow", HasFlowFunction.getMethod());
217-
functions.put("uuid", UuidFunction.getMethod());
218-
functions.put("isDryRun", IsDryRunFunction.getMethod());
219-
return new FunctionMapper(functions);
220-
}
221-
222206
private static boolean hasExpression(String s) {
223207
return s.contains("${");
224208
}
@@ -272,4 +256,20 @@ private static Optional<String> propertyNameFromException(PropertyNotFoundExcept
272256

273257
return Optional.empty();
274258
}
259+
260+
private static class DelegatingFunctionMapper extends FunctionMapper {
261+
final FunctionHolder functionHolder;
262+
263+
DelegatingFunctionMapper(FunctionHolder functionHolder) {
264+
this.functionHolder = functionHolder;
265+
}
266+
267+
@Override
268+
public Method resolveFunction(String prefix, String localName) {
269+
if (prefix != null && !prefix.isBlank()) {
270+
return functionHolder.resolve(prefix + ":" + localName);
271+
}
272+
return functionHolder.resolve(localName);
273+
}
274+
}
275275
}

runtime/v2/runner/src/main/java/com/walmartlabs/concord/runtime/v2/runner/el/functions/AllVariablesFunction.java

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* *****
55
* Concord
66
* -----
7-
* Copyright (C) 2017 - 2020 Walmart Inc.
7+
* Copyright (C) 2017 - 2025 Walmart Inc.
88
* -----
99
* Licensed under the Apache License, Version 2.0 (the "License");
1010
* you may not use this file except in compliance with the License.
@@ -20,21 +20,14 @@
2020
* =====
2121
*/
2222

23+
import com.walmartlabs.concord.runtime.v2.sdk.ELFunction;
2324
import com.walmartlabs.concord.runtime.v2.runner.el.ThreadLocalEvalContext;
2425

25-
import java.lang.reflect.Method;
2626
import java.util.Map;
2727

2828
public final class AllVariablesFunction {
2929

30-
public static Method getMethod() {
31-
try {
32-
return AllVariablesFunction.class.getMethod("allVariables");
33-
} catch (Exception e) {
34-
throw new RuntimeException("Method not found");
35-
}
36-
}
37-
30+
@ELFunction
3831
public static Map<String, Object> allVariables() {
3932
return ThreadLocalEvalContext.get()
4033
.variables()

runtime/v2/runner/src/main/java/com/walmartlabs/concord/runtime/v2/runner/el/functions/CurrentFlowNameFunction.java

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* *****
55
* Concord
66
* -----
7-
* Copyright (C) 2017 - 2020 Walmart Inc.
7+
* Copyright (C) 2017 - 2025 Walmart Inc.
88
* -----
99
* Licensed under the Apache License, Version 2.0 (the "License");
1010
* you may not use this file except in compliance with the License.
@@ -20,21 +20,13 @@
2020
* =====
2121
*/
2222

23+
import com.walmartlabs.concord.runtime.v2.sdk.ELFunction;
2324
import com.walmartlabs.concord.runtime.v2.runner.el.ThreadLocalEvalContext;
2425
import com.walmartlabs.concord.runtime.v2.sdk.Context;
2526

26-
import java.lang.reflect.Method;
27-
2827
public final class CurrentFlowNameFunction {
2928

30-
public static Method getMethod() {
31-
try {
32-
return CurrentFlowNameFunction.class.getMethod("currentFlowName");
33-
} catch (Exception e) {
34-
throw new RuntimeException("Method not found");
35-
}
36-
}
37-
29+
@ELFunction
3830
public static String currentFlowName() {
3931
Context ctx = ThreadLocalEvalContext.get().context();
4032
if (ctx == null) {

runtime/v2/runner/src/main/java/com/walmartlabs/concord/runtime/v2/runner/el/functions/EvalAsMapFunction.java

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* *****
55
* Concord
66
* -----
7-
* Copyright (C) 2017 - 2020 Walmart Inc.
7+
* Copyright (C) 2017 - 2025 Walmart Inc.
88
* -----
99
* Licensed under the Apache License, Version 2.0 (the "License");
1010
* you may not use this file except in compliance with the License.
@@ -20,28 +20,21 @@
2020
* =====
2121
*/
2222

23-
import com.walmartlabs.concord.runtime.v2.sdk.EvalContextFactory;
24-
import com.walmartlabs.concord.runtime.v2.sdk.ExpressionEvaluator;
23+
import com.walmartlabs.concord.runtime.v2.sdk.ELFunction;
2524
import com.walmartlabs.concord.runtime.v2.runner.el.ThreadLocalEvalContext;
2625
import com.walmartlabs.concord.runtime.v2.sdk.Context;
26+
import com.walmartlabs.concord.runtime.v2.sdk.EvalContextFactory;
27+
import com.walmartlabs.concord.runtime.v2.sdk.ExpressionEvaluator;
2728
import com.walmartlabs.concord.svm.Runtime;
2829

29-
import java.lang.reflect.Method;
3030
import java.util.HashMap;
3131
import java.util.Map;
3232

3333
import static com.walmartlabs.concord.common.ConfigurationUtils.deepMerge;
3434

3535
public final class EvalAsMapFunction {
3636

37-
public static Method getMethod() {
38-
try {
39-
return EvalAsMapFunction.class.getMethod("evalAsMap", Object.class);
40-
} catch (Exception e) {
41-
throw new RuntimeException("Method not found");
42-
}
43-
}
44-
37+
@ELFunction
4538
@SuppressWarnings("unchecked")
4639
public static Map<String, Object> evalAsMap(Object value) {
4740
Context ctx = ThreadLocalEvalContext.get().context();

runtime/v2/runner/src/main/java/com/walmartlabs/concord/runtime/v2/runner/el/functions/HasFlowFunction.java

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,21 +22,13 @@
2222

2323
import com.walmartlabs.concord.runtime.v2.model.ProcessDefinition;
2424
import com.walmartlabs.concord.runtime.v2.model.Profile;
25+
import com.walmartlabs.concord.runtime.v2.sdk.ELFunction;
2526
import com.walmartlabs.concord.runtime.v2.runner.el.ThreadLocalEvalContext;
2627
import com.walmartlabs.concord.runtime.v2.sdk.Context;
2728

28-
import java.lang.reflect.Method;
29-
3029
public final class HasFlowFunction {
3130

32-
public static Method getMethod() {
33-
try {
34-
return HasFlowFunction.class.getMethod("hasFlow", String.class);
35-
} catch (Exception e) {
36-
throw new RuntimeException("Method not found");
37-
}
38-
}
39-
31+
@ELFunction
4032
public static boolean hasFlow(String name) {
4133
if (name == null || name.trim().isEmpty()) {
4234
return false;

0 commit comments

Comments
 (0)