From d16ee53bcd76cceb0906778473c6ba6fa51e1e28 Mon Sep 17 00:00:00 2001 From: VIacheslavKlimov Date: Thu, 9 Oct 2025 13:16:19 +0300 Subject: [PATCH] CF: expressions usage refactoring --- .../cf/ctx/state/CalculatedFieldCtx.java | 27 +++++++++------- .../ctx/state/ScriptCalculatedFieldState.java | 15 +++++++-- .../ctx/state/SimpleCalculatedFieldState.java | 32 ++++++++++++------- 3 files changed, 48 insertions(+), 26 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java index 14634bbaab..9e9842c28d 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java @@ -176,13 +176,8 @@ public class CalculatedFieldCtx { public void init() { switch (cfType) { case SCRIPT -> { - try { - initTbelExpression(expression); - initialized = true; - } catch (Exception e) { - initialized = false; - throw new RuntimeException("Failed to init calculated field ctx. Invalid expression syntax.", e); - } + initTbelExpression(expression); + initialized = true; } case GEOFENCING -> initialized = true; case SIMPLE -> { @@ -205,8 +200,7 @@ public class CalculatedFieldCtx { } } - public double evaluateSimpleExpression(String expressionStr, CalculatedFieldState state) { - Expression expression = simpleExpressions.get(expressionStr).get(); + public double evaluateSimpleExpression(Expression expression, CalculatedFieldState state) { for (Map.Entry entry : state.getArguments().entrySet()) { try { BasicKvEntry kvEntry = ((SingleValueArgumentEntry) entry.getValue()).getKvEntryValue(); @@ -225,6 +219,10 @@ public class CalculatedFieldCtx { } public ListenableFuture evaluateTbelExpression(String expression, CalculatedFieldState state) { + return evaluateTbelExpression(tbelExpressions.get(expression), state); + } + + public ListenableFuture evaluateTbelExpression(CalculatedFieldScriptEngine expression, CalculatedFieldState state) { Map arguments = new LinkedHashMap<>(); List args = new ArrayList<>(argNames.size() + 1); args.add(new Object()); // first element is a ctx, but we will set it later; @@ -239,7 +237,7 @@ public class CalculatedFieldCtx { } args.set(0, new TbelCfCtx(arguments, state.getLatestTimestamp())); - return tbelExpressions.get(expression).executeScriptAsync(args.toArray()); + return expression.executeScriptAsync(args.toArray()); } public ScheduledFuture scheduleReevaluation(long delayMs, TbActorRef actorCtx) { @@ -258,8 +256,13 @@ public class CalculatedFieldCtx { } else if (tbelExpressions.containsKey(expression)) { return; } - CalculatedFieldScriptEngine engine = initEngine(tenantId, expression, tbelInvokeService); - tbelExpressions.put(expression, engine); + try { + CalculatedFieldScriptEngine engine = initEngine(tenantId, expression, tbelInvokeService); + tbelExpressions.put(expression, engine); + } catch (Exception e) { + initialized = false; + throw new RuntimeException("Failed to init calculated field ctx. Invalid expression syntax.", e); + } } private void initSimpleExpression(String expression) { diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/ScriptCalculatedFieldState.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/ScriptCalculatedFieldState.java index 3b6f9b1f87..9c5a25fda9 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/ScriptCalculatedFieldState.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/ScriptCalculatedFieldState.java @@ -21,6 +21,7 @@ import com.google.common.util.concurrent.MoreExecutors; import lombok.EqualsAndHashCode; import lombok.extern.slf4j.Slf4j; import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.actors.TbActorRef; import org.thingsboard.server.common.data.cf.CalculatedFieldType; import org.thingsboard.server.common.data.cf.configuration.Output; import org.thingsboard.server.common.data.id.EntityId; @@ -33,18 +34,21 @@ import java.util.Map; @EqualsAndHashCode(callSuper = true) public class ScriptCalculatedFieldState extends BaseCalculatedFieldState { + private CalculatedFieldScriptEngine tbelExpression; + public ScriptCalculatedFieldState(EntityId entityId) { super(entityId); } @Override - public CalculatedFieldType getType() { - return CalculatedFieldType.SCRIPT; + public void setCtx(CalculatedFieldCtx ctx, TbActorRef actorCtx) { + super.setCtx(ctx, actorCtx); + this.tbelExpression = ctx.getTbelExpressions().get(ctx.getExpression()); } @Override public ListenableFuture performCalculation(Map updatedArgs, CalculatedFieldCtx ctx) { - ListenableFuture resultFuture = ctx.evaluateTbelExpression(ctx.getExpression(), this); + ListenableFuture resultFuture = ctx.evaluateTbelExpression(tbelExpression, this); Output output = ctx.getOutput(); return Futures.transform(resultFuture, result -> TelemetryCalculatedFieldResult.builder() @@ -56,4 +60,9 @@ public class ScriptCalculatedFieldState extends BaseCalculatedFieldState { ); } + @Override + public CalculatedFieldType getType() { + return CalculatedFieldType.SCRIPT; + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/SimpleCalculatedFieldState.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/SimpleCalculatedFieldState.java index 3a98fee361..65cb595632 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/SimpleCalculatedFieldState.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/SimpleCalculatedFieldState.java @@ -20,8 +20,10 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.EqualsAndHashCode; +import net.objecthunter.exp4j.Expression; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.script.api.tbel.TbUtils; +import org.thingsboard.server.actors.TbActorRef; import org.thingsboard.server.common.data.cf.CalculatedFieldType; import org.thingsboard.server.common.data.cf.configuration.Output; import org.thingsboard.server.common.data.id.EntityId; @@ -33,26 +35,21 @@ import java.util.Map; @EqualsAndHashCode(callSuper = true) public class SimpleCalculatedFieldState extends BaseCalculatedFieldState { + private ThreadLocal expression; + public SimpleCalculatedFieldState(EntityId entityId) { super(entityId); } @Override - public CalculatedFieldType getType() { - return CalculatedFieldType.SIMPLE; - } - - @Override - protected void validateNewEntry(String key, ArgumentEntry newEntry) { - if (newEntry instanceof TsRollingArgumentEntry) { - throw new IllegalArgumentException("Unsupported argument type detected for argument: " + key + ". " + - "Rolling argument entry is not supported for simple calculated fields."); - } + public void setCtx(CalculatedFieldCtx ctx, TbActorRef actorCtx) { + super.setCtx(ctx, actorCtx); + this.expression = ctx.getSimpleExpressions().get(ctx.getExpression()); } @Override public ListenableFuture performCalculation(Map updatedArgs, CalculatedFieldCtx ctx) { - double expressionResult = ctx.evaluateSimpleExpression(ctx.getExpression(), this); + double expressionResult = ctx.evaluateSimpleExpression(expression.get(), this); Output output = ctx.getOutput(); Object result = formatResult(expressionResult, output.getDecimalsByDefault()); @@ -96,4 +93,17 @@ public class SimpleCalculatedFieldState extends BaseCalculatedFieldState { } } + @Override + protected void validateNewEntry(String key, ArgumentEntry newEntry) { + if (newEntry instanceof TsRollingArgumentEntry) { + throw new IllegalArgumentException("Unsupported argument type detected for argument: " + key + ". " + + "Rolling argument entry is not supported for simple calculated fields."); + } + } + + @Override + public CalculatedFieldType getType() { + return CalculatedFieldType.SIMPLE; + } + }