added last records type of cf

This commit is contained in:
IrynaMatveieva 2024-11-25 17:28:44 +02:00
parent 2c7c6f0c5e
commit c6d91c4ce8
14 changed files with 327 additions and 52 deletions

View File

@ -48,12 +48,16 @@ import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.Aggregation;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.BooleanDataEntry; import org.thingsboard.server.common.data.kv.BooleanDataEntry;
import org.thingsboard.server.common.data.kv.DoubleDataEntry; import org.thingsboard.server.common.data.kv.DoubleDataEntry;
import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.KvEntry;
import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.msg.TbMsgType;
import org.thingsboard.server.common.data.page.PageDataIterable; import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
@ -69,7 +73,10 @@ import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.cf.ctx.CalculatedFieldCtx; import org.thingsboard.server.service.cf.ctx.CalculatedFieldCtx;
import org.thingsboard.server.service.cf.ctx.CalculatedFieldCtxId; import org.thingsboard.server.service.cf.ctx.CalculatedFieldCtxId;
import org.thingsboard.server.service.cf.ctx.state.ArgumentEntry;
import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldState; import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldState;
import org.thingsboard.server.service.cf.ctx.state.CalculationContext;
import org.thingsboard.server.service.cf.ctx.state.LastRecordsCalculatedFieldState;
import org.thingsboard.server.service.cf.ctx.state.ScriptCalculatedFieldState; import org.thingsboard.server.service.cf.ctx.state.ScriptCalculatedFieldState;
import org.thingsboard.server.service.cf.ctx.state.SimpleCalculatedFieldState; import org.thingsboard.server.service.cf.ctx.state.SimpleCalculatedFieldState;
import org.thingsboard.server.service.partition.AbstractPartitionBasedService; import org.thingsboard.server.service.partition.AbstractPartitionBasedService;
@ -84,6 +91,7 @@ import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import static org.thingsboard.server.common.data.DataConstants.SCOPE; import static org.thingsboard.server.common.data.DataConstants.SCOPE;
@ -109,6 +117,8 @@ public class DefaultCalculatedFieldExecutionService extends AbstractPartitionBas
private final ConcurrentMap<CalculatedFieldId, List<CalculatedFieldLink>> calculatedFieldLinks = new ConcurrentHashMap<>(); private final ConcurrentMap<CalculatedFieldId, List<CalculatedFieldLink>> calculatedFieldLinks = new ConcurrentHashMap<>();
private final ConcurrentMap<CalculatedFieldCtxId, CalculatedFieldCtx> states = new ConcurrentHashMap<>(); private final ConcurrentMap<CalculatedFieldCtxId, CalculatedFieldCtx> states = new ConcurrentHashMap<>();
private static final int MAX_LAST_RECORDS_VALUE = 1024;
@Value("${calculatedField.initFetchPackSize:50000}") @Value("${calculatedField.initFetchPackSize:50000}")
@Getter @Getter
private int initFetchPackSize; private int initFetchPackSize;
@ -215,7 +225,19 @@ public class DefaultCalculatedFieldExecutionService extends AbstractPartitionBas
public void onTelemetryUpdate(TenantId tenantId, CalculatedFieldId calculatedFieldId, Map<String, KvEntry> updatedTelemetry) { public void onTelemetryUpdate(TenantId tenantId, CalculatedFieldId calculatedFieldId, Map<String, KvEntry> updatedTelemetry) {
try { try {
CalculatedField calculatedField = calculatedFields.computeIfAbsent(calculatedFieldId, id -> calculatedFieldService.findById(tenantId, id)); CalculatedField calculatedField = calculatedFields.computeIfAbsent(calculatedFieldId, id -> calculatedFieldService.findById(tenantId, id));
updateOrInitializeState(calculatedField, calculatedField.getEntityId(), updatedTelemetry); Map<String, ArgumentEntry> argumentValues = updatedTelemetry.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
entry -> {
ArgumentEntry argumentEntry = new ArgumentEntry();
argumentEntry.setKvEntry(entry.getValue());
if (entry.getValue() instanceof TsKvEntry) {
argumentEntry.setKvEntries(List.of((TsKvEntry) entry.getValue()));
}
return argumentEntry;
}
));
updateOrInitializeState(calculatedField, calculatedField.getEntityId(), argumentValues);
log.info("Successfully updated time series for calculatedFieldId: [{}]", calculatedFieldId); log.info("Successfully updated time series for calculatedFieldId: [{}]", calculatedFieldId);
} catch (Exception e) { } catch (Exception e) {
log.trace("Failed to update telemetry for calculatedFieldId: [{}]", calculatedFieldId, e); log.trace("Failed to update telemetry for calculatedFieldId: [{}]", calculatedFieldId, e);
@ -308,12 +330,12 @@ public class DefaultCalculatedFieldExecutionService extends AbstractPartitionBas
private void initializeStateForEntity(TenantId tenantId, CalculatedField calculatedField, EntityId entityId, TbCallback callback) { private void initializeStateForEntity(TenantId tenantId, CalculatedField calculatedField, EntityId entityId, TbCallback callback) {
Map<String, Argument> arguments = calculatedField.getConfiguration().getArguments(); Map<String, Argument> arguments = calculatedField.getConfiguration().getArguments();
Map<String, KvEntry> argumentValues = new HashMap<>(); Map<String, ArgumentEntry> argumentValues = new HashMap<>();
AtomicInteger remaining = new AtomicInteger(arguments.size()); AtomicInteger remaining = new AtomicInteger(arguments.size());
arguments.forEach((key, argument) -> Futures.addCallback(fetchArgumentValue(tenantId, argument, entityId), new FutureCallback<>() { arguments.forEach((key, argument) -> Futures.addCallback(fetchArgumentValue(calculatedField, argument), new FutureCallback<>() {
@Override @Override
public void onSuccess(Optional<? extends KvEntry> result) { public void onSuccess(ArgumentEntry result) {
argumentValues.put(key, result.orElse(null)); argumentValues.put(key, result);
if (remaining.decrementAndGet() == 0) { if (remaining.decrementAndGet() == 0) {
updateOrInitializeState(calculatedField, entityId, argumentValues); updateOrInitializeState(calculatedField, entityId, argumentValues);
} }
@ -327,10 +349,37 @@ public class DefaultCalculatedFieldExecutionService extends AbstractPartitionBas
}, calculatedFieldCallbackExecutor)); }, calculatedFieldCallbackExecutor));
} }
private ListenableFuture<Optional<? extends KvEntry>> fetchArgumentValue(TenantId tenantId, Argument argument, EntityId targetEntityId) { private ListenableFuture<ArgumentEntry> fetchArgumentValue(CalculatedField calculatedField, Argument argument) {
TenantId tenantId = calculatedField.getTenantId();
EntityId cfEntityId = calculatedField.getEntityId();
EntityId argumentEntityId = argument.getEntityId(); EntityId argumentEntityId = argument.getEntityId();
EntityId entityId = EntityType.DEVICE_PROFILE.equals(argumentEntityId.getEntityType()) || EntityType.ASSET_PROFILE.equals(argumentEntityId.getEntityType()) ? targetEntityId : argumentEntityId; EntityId entityId = EntityType.DEVICE_PROFILE.equals(argumentEntityId.getEntityType()) || EntityType.ASSET_PROFILE.equals(argumentEntityId.getEntityType())
return switch (argument.getType()) { ? cfEntityId
: argumentEntityId;
if (CalculatedFieldType.LAST_RECORDS.equals(calculatedField.getType())) {
return fetchLastRecords(tenantId, entityId, argument);
}
return fetchKvEntry(tenantId, entityId, argument);
}
private ListenableFuture<ArgumentEntry> fetchLastRecords(TenantId tenantId, EntityId entityId, Argument argument) {
long startTs = Math.max(argument.getStartTs(), 0);
long timeWindow = argument.getTimeWindow() == 0 ? System.currentTimeMillis() : argument.getTimeWindow();
long endTs = startTs + timeWindow;
int limit = argument.getLimit() == 0 ? MAX_LAST_RECORDS_VALUE : argument.getLimit();
ReadTsKvQuery query = new BaseReadTsKvQuery(argument.getKey(), startTs, endTs, 0, limit, Aggregation.NONE);
ListenableFuture<List<TsKvEntry>> lastRecordsFuture = timeseriesService.findAll(tenantId, entityId, List.of(query));
return Futures.transform(lastRecordsFuture, lastRecords -> {
ArgumentEntry argumentEntry = new ArgumentEntry();
argumentEntry.setKvEntries(lastRecords);
return argumentEntry;
}, calculatedFieldExecutor);
}
private ListenableFuture<ArgumentEntry> fetchKvEntry(TenantId tenantId, EntityId entityId, Argument argument) {
ListenableFuture<Optional<? extends KvEntry>> kvEntryFuture = switch (argument.getType()) {
case "ATTRIBUTES" -> Futures.transform( case "ATTRIBUTES" -> Futures.transform(
attributesService.find(tenantId, entityId, argument.getScope(), argument.getKey()), attributesService.find(tenantId, entityId, argument.getScope(), argument.getKey()),
result -> result.or(() -> Optional.of( result -> result.or(() -> Optional.of(
@ -342,9 +391,16 @@ public class DefaultCalculatedFieldExecutionService extends AbstractPartitionBas
result -> result.or(() -> Optional.of( result -> result.or(() -> Optional.of(
new BasicTsKvEntry(System.currentTimeMillis(), createDefaultKvEntry(argument)) new BasicTsKvEntry(System.currentTimeMillis(), createDefaultKvEntry(argument))
)), )),
MoreExecutors.directExecutor()); calculatedFieldExecutor);
default -> throw new IllegalArgumentException("Invalid argument type '" + argument.getType() + "'."); default -> throw new IllegalArgumentException("Invalid argument type '" + argument.getType() + "'.");
}; };
return Futures.transform(kvEntryFuture, kvEntry -> {
ArgumentEntry argumentEntry = new ArgumentEntry();
if (kvEntry.isPresent()) {
argumentEntry.setKvEntry(kvEntry.orElse(null));
}
return argumentEntry;
}, calculatedFieldExecutor);
} }
private KvEntry createDefaultKvEntry(Argument argument) { private KvEntry createDefaultKvEntry(Argument argument) {
@ -359,7 +415,7 @@ public class DefaultCalculatedFieldExecutionService extends AbstractPartitionBas
return new StringDataEntry(key, defaultValue); return new StringDataEntry(key, defaultValue);
} }
private void updateOrInitializeState(CalculatedField calculatedField, EntityId entityId, Map<String, KvEntry> argumentValues) { private void updateOrInitializeState(CalculatedField calculatedField, EntityId entityId, Map<String, ArgumentEntry> argumentValues) {
CalculatedFieldCtxId ctxId = new CalculatedFieldCtxId(calculatedField.getUuidId(), entityId.getId()); CalculatedFieldCtxId ctxId = new CalculatedFieldCtxId(calculatedField.getUuidId(), entityId.getId());
CalculatedFieldCtx calculatedFieldCtx = states.computeIfAbsent(ctxId, ctx -> new CalculatedFieldCtx(ctxId, null)); CalculatedFieldCtx calculatedFieldCtx = states.computeIfAbsent(ctxId, ctx -> new CalculatedFieldCtx(ctxId, null));
@ -373,7 +429,12 @@ public class DefaultCalculatedFieldExecutionService extends AbstractPartitionBas
states.put(ctxId, calculatedFieldCtx); states.put(ctxId, calculatedFieldCtx);
rocksDBService.put(JacksonUtil.writeValueAsString(ctxId), JacksonUtil.writeValueAsString(calculatedFieldCtx)); rocksDBService.put(JacksonUtil.writeValueAsString(ctxId), JacksonUtil.writeValueAsString(calculatedFieldCtx));
ListenableFuture<CalculatedFieldResult> resultFuture = state.performCalculation(calculatedField.getTenantId(), calculatedField.getConfiguration(), tbelInvokeService); CalculationContext ctx = CalculationContext.builder()
.tenantId(calculatedField.getTenantId())
.configuration(calculatedField.getConfiguration())
.tbelInvokeService(tbelInvokeService)
.build();
ListenableFuture<CalculatedFieldResult> resultFuture = state.performCalculation(ctx);
Futures.addCallback(resultFuture, new FutureCallback<>() { Futures.addCallback(resultFuture, new FutureCallback<>() {
@Override @Override
public void onSuccess(CalculatedFieldResult result) { public void onSuccess(CalculatedFieldResult result) {
@ -414,6 +475,7 @@ public class DefaultCalculatedFieldExecutionService extends AbstractPartitionBas
return switch (calculatedFieldType) { return switch (calculatedFieldType) {
case SIMPLE -> new SimpleCalculatedFieldState(); case SIMPLE -> new SimpleCalculatedFieldState();
case SCRIPT -> new ScriptCalculatedFieldState(); case SCRIPT -> new ScriptCalculatedFieldState();
case LAST_RECORDS -> new LastRecordsCalculatedFieldState();
}; };
} }

View File

@ -0,0 +1,30 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.cf.ctx.state;
import lombok.Data;
import org.thingsboard.server.common.data.kv.KvEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import java.util.List;
@Data
public class ArgumentEntry {
private KvEntry kvEntry;
private List<TsKvEntry> kvEntries;
}

View File

@ -19,11 +19,8 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.script.api.tbel.TbelInvokeService;
import org.thingsboard.server.common.data.cf.CalculatedFieldType; import org.thingsboard.server.common.data.cf.CalculatedFieldType;
import org.thingsboard.server.common.data.cf.configuration.Argument; import org.thingsboard.server.common.data.cf.configuration.Argument;
import org.thingsboard.server.common.data.cf.configuration.CalculatedFieldConfiguration;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.KvEntry;
import org.thingsboard.server.service.cf.CalculatedFieldResult; import org.thingsboard.server.service.cf.CalculatedFieldResult;
@ -36,7 +33,8 @@ import java.util.Map;
) )
@JsonSubTypes({ @JsonSubTypes({
@JsonSubTypes.Type(value = SimpleCalculatedFieldState.class, name = "SIMPLE"), @JsonSubTypes.Type(value = SimpleCalculatedFieldState.class, name = "SIMPLE"),
@JsonSubTypes.Type(value = ScriptCalculatedFieldState.class, name = "SCRIPT") @JsonSubTypes.Type(value = ScriptCalculatedFieldState.class, name = "SCRIPT"),
@JsonSubTypes.Type(value = LastRecordsCalculatedFieldState.class, name = "LAST_RECORDS")
}) })
public interface CalculatedFieldState { public interface CalculatedFieldState {
@ -47,8 +45,8 @@ public interface CalculatedFieldState {
return argumentValues.keySet().containsAll(arguments.keySet()); return argumentValues.keySet().containsAll(arguments.keySet());
} }
void initState(Map<String, KvEntry> argumentValues); void initState(Map<String, ArgumentEntry> argumentValues);
ListenableFuture<CalculatedFieldResult> performCalculation(TenantId tenantId, CalculatedFieldConfiguration calculatedFieldConfiguration, TbelInvokeService tbelInvokeService); ListenableFuture<CalculatedFieldResult> performCalculation(CalculationContext ctx);
} }

View File

@ -0,0 +1,35 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.cf.ctx.state;
import lombok.Builder;
import lombok.Data;
import org.thingsboard.script.api.tbel.TbelInvokeService;
import org.thingsboard.server.common.data.cf.configuration.CalculatedFieldConfiguration;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.KvEntry;
import java.util.Map;
@Data
@Builder
public class CalculationContext {
private TenantId tenantId;
private CalculatedFieldConfiguration configuration;
private TbelInvokeService tbelInvokeService;
}

View File

@ -0,0 +1,91 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.cf.ctx.state;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.Data;
import org.thingsboard.server.common.data.cf.CalculatedFieldType;
import org.thingsboard.server.common.data.cf.configuration.Argument;
import org.thingsboard.server.common.data.cf.configuration.CalculatedFieldConfiguration;
import org.thingsboard.server.common.data.cf.configuration.Output;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.service.cf.CalculatedFieldResult;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Data
public class LastRecordsCalculatedFieldState implements CalculatedFieldState {
private Map<String, List<TsKvEntry>> arguments;
public LastRecordsCalculatedFieldState() {
}
@Override
public CalculatedFieldType getType() {
return CalculatedFieldType.LAST_RECORDS;
}
@Override
public void initState(Map<String, ArgumentEntry> argumentValues) {
if (arguments == null) {
arguments = new HashMap<>();
}
argumentValues.forEach((key, argumentEntry) -> {
List<TsKvEntry> tsKvEntryList = arguments.computeIfAbsent(key, k -> new ArrayList<>());
tsKvEntryList.addAll(argumentEntry.getKvEntries());
});
}
@Override
public ListenableFuture<CalculatedFieldResult> performCalculation(CalculationContext ctx) {
CalculatedFieldConfiguration configuration = ctx.getConfiguration();
Map<String, Argument> configArguments = configuration.getArguments();
Output output = configuration.getOutput();
Map<String, Object> resultMap = new HashMap<>();
arguments.replaceAll((key, entries) -> {
int limit = configArguments.get(key).getLimit();
List<TsKvEntry> limitedEntries = entries.stream()
.sorted(Comparator.comparingLong(TsKvEntry::getTs).reversed())
.limit(limit)
.collect(Collectors.toList());
Map<Long, Object> valueWithTs = limitedEntries.stream()
.collect(Collectors.toMap(TsKvEntry::getTs, TsKvEntry::getValue));
resultMap.put(key, valueWithTs);
return limitedEntries;
});
CalculatedFieldResult calculatedFieldResult = new CalculatedFieldResult();
calculatedFieldResult.setType(output.getType());
calculatedFieldResult.setScope(output.getScope());
calculatedFieldResult.setResultMap(resultMap);
return Futures.immediateFuture(calculatedFieldResult);
}
}

View File

@ -40,7 +40,7 @@ public class ScriptCalculatedFieldState implements CalculatedFieldState {
@JsonIgnore @JsonIgnore
private CalculatedFieldScriptEngine calculatedFieldScriptEngine; private CalculatedFieldScriptEngine calculatedFieldScriptEngine;
private Map<String, KvEntry> arguments = new HashMap<>(); private Map<String, KvEntry> arguments;
public ScriptCalculatedFieldState() { public ScriptCalculatedFieldState() {
} }
@ -50,22 +50,27 @@ public class ScriptCalculatedFieldState implements CalculatedFieldState {
return CalculatedFieldType.SCRIPT; return CalculatedFieldType.SCRIPT;
} }
@Override
public void initState(Map<String, KvEntry> argumentValues) {
if (arguments == null) {
this.arguments = new HashMap<>();
}
this.arguments.putAll(argumentValues);
}
@Override @Override
public ListenableFuture<CalculatedFieldResult> performCalculation(TenantId tenantId, CalculatedFieldConfiguration calculatedFieldConfiguration, TbelInvokeService tbelInvokeService) { public void initState(Map<String, ArgumentEntry> argumentValues) {
if (arguments == null) {
arguments = new HashMap<>();
}
argumentValues.forEach((key, value) -> arguments.put(key, value.getKvEntry()));
}
@Override
public ListenableFuture<CalculatedFieldResult> performCalculation(CalculationContext ctx) {
CalculatedFieldConfiguration calculatedFieldConfiguration = ctx.getConfiguration();
TbelInvokeService tbelInvokeService = ctx.getTbelInvokeService();
if (tbelInvokeService == null) { if (tbelInvokeService == null) {
throw new IllegalArgumentException("TBEL script engine is disabled!"); throw new IllegalArgumentException("TBEL script engine is disabled!");
} }
if (calculatedFieldScriptEngine == null) { if (calculatedFieldScriptEngine == null) {
initEngine(tenantId, calculatedFieldConfiguration, tbelInvokeService); initEngine(ctx.getTenantId(), calculatedFieldConfiguration, tbelInvokeService);
} }
ListenableFuture<Object> resultFuture = calculatedFieldScriptEngine.executeScriptAsync(arguments); ListenableFuture<Object> resultFuture = calculatedFieldScriptEngine.executeScriptAsync(arguments);

View File

@ -20,12 +20,10 @@ import com.google.common.util.concurrent.ListenableFuture;
import lombok.Data; import lombok.Data;
import net.objecthunter.exp4j.Expression; import net.objecthunter.exp4j.Expression;
import net.objecthunter.exp4j.ExpressionBuilder; import net.objecthunter.exp4j.ExpressionBuilder;
import org.thingsboard.script.api.tbel.TbelInvokeService;
import org.thingsboard.server.common.data.cf.CalculatedFieldType; import org.thingsboard.server.common.data.cf.CalculatedFieldType;
import org.thingsboard.server.common.data.cf.configuration.Argument; import org.thingsboard.server.common.data.cf.configuration.Argument;
import org.thingsboard.server.common.data.cf.configuration.CalculatedFieldConfiguration; import org.thingsboard.server.common.data.cf.configuration.CalculatedFieldConfiguration;
import org.thingsboard.server.common.data.cf.configuration.Output; import org.thingsboard.server.common.data.cf.configuration.Output;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.KvEntry;
import org.thingsboard.server.service.cf.CalculatedFieldResult; import org.thingsboard.server.service.cf.CalculatedFieldResult;
@ -37,21 +35,26 @@ public class SimpleCalculatedFieldState implements CalculatedFieldState {
private Map<String, KvEntry> arguments; private Map<String, KvEntry> arguments;
public SimpleCalculatedFieldState() {
}
@Override @Override
public CalculatedFieldType getType() { public CalculatedFieldType getType() {
return CalculatedFieldType.SIMPLE; return CalculatedFieldType.SIMPLE;
} }
@Override @Override
public void initState(Map<String, KvEntry> argumentValues) { public void initState(Map<String, ArgumentEntry> argumentValues) {
if (arguments == null) { if (arguments == null) {
arguments = new HashMap<>(); arguments = new HashMap<>();
} }
arguments.putAll(argumentValues); argumentValues.forEach((key, value) -> arguments.put(key, value.getKvEntry()));
} }
@Override @Override
public ListenableFuture<CalculatedFieldResult> performCalculation(TenantId tenantId, CalculatedFieldConfiguration calculatedFieldConfiguration, TbelInvokeService tbelInvokeService) { public ListenableFuture<CalculatedFieldResult> performCalculation(CalculationContext ctx) {
CalculatedFieldConfiguration calculatedFieldConfiguration = ctx.getConfiguration();
Output output = calculatedFieldConfiguration.getOutput(); Output output = calculatedFieldConfiguration.getOutput();
Map<String, Argument> arguments = calculatedFieldConfiguration.getArguments(); Map<String, Argument> arguments = calculatedFieldConfiguration.getArguments();

View File

@ -17,6 +17,6 @@ package org.thingsboard.server.common.data.cf;
public enum CalculatedFieldType { public enum CalculatedFieldType {
SIMPLE, SCRIPT SIMPLE, SCRIPT, LAST_RECORDS
} }

View File

@ -29,6 +29,7 @@ public class Argument {
private String defaultValue; private String defaultValue;
private int limit; private int limit;
private long startTs;
private long timeWindow; private long timeWindow;
} }

View File

@ -101,6 +101,9 @@ public abstract class BaseCalculatedFieldConfiguration implements CalculatedFiel
argumentNode.put("type", argument.getType()); argumentNode.put("type", argument.getType());
argumentNode.put("scope", String.valueOf(argument.getScope())); argumentNode.put("scope", String.valueOf(argument.getScope()));
argumentNode.put("defaultValue", argument.getDefaultValue()); argumentNode.put("defaultValue", argument.getDefaultValue());
argumentNode.put("limit", String.valueOf(argument.getLimit()));
argumentNode.put("startTs", String.valueOf(argument.getStartTs()));
argumentNode.put("timeWindow", String.valueOf(argument.getTimeWindow()));
}); });
if (expression != null) { if (expression != null) {
@ -144,7 +147,18 @@ public abstract class BaseCalculatedFieldConfiguration implements CalculatedFiel
if (scope != null && !scope.isNull() && !scope.asText().equals("null")) { if (scope != null && !scope.isNull() && !scope.asText().equals("null")) {
argument.setScope(AttributeScope.valueOf(scope.asText())); argument.setScope(AttributeScope.valueOf(scope.asText()));
} }
if (argumentNode.hasNonNull("defaultValue")) {
argument.setDefaultValue(argumentNode.get("defaultValue").asText()); argument.setDefaultValue(argumentNode.get("defaultValue").asText());
}
if (argumentNode.hasNonNull("limit")) {
argument.setLimit(argumentNode.get("limit").asInt());
}
if (argumentNode.hasNonNull("startTs")) {
argument.setStartTs(argumentNode.get("startTs").asLong());
}
if (argumentNode.hasNonNull("timeWindow")) {
argument.setTimeWindow(argumentNode.get("timeWindow").asInt());
}
arguments.put(key, argument); arguments.put(key, argument);
}); });
} }

View File

@ -35,7 +35,8 @@ import java.util.UUID;
) )
@JsonSubTypes({ @JsonSubTypes({
@JsonSubTypes.Type(value = SimpleCalculatedFieldConfiguration.class, name = "SIMPLE"), @JsonSubTypes.Type(value = SimpleCalculatedFieldConfiguration.class, name = "SIMPLE"),
@JsonSubTypes.Type(value = ScriptCalculatedFieldConfiguration.class, name = "SCRIPT") @JsonSubTypes.Type(value = ScriptCalculatedFieldConfiguration.class, name = "SCRIPT"),
@JsonSubTypes.Type(value = LastRecordsCalculatedFieldConfiguration.class, name = "LAST_RECORDS")
}) })
public interface CalculatedFieldConfiguration { public interface CalculatedFieldConfiguration {

View File

@ -0,0 +1,39 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.cf.configuration;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Data;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.cf.CalculatedFieldType;
import java.util.UUID;
@Data
public class LastRecordsCalculatedFieldConfiguration extends BaseCalculatedFieldConfiguration implements CalculatedFieldConfiguration {
public LastRecordsCalculatedFieldConfiguration() {
}
public LastRecordsCalculatedFieldConfiguration(JsonNode config, EntityType entityType, UUID entityId) {
super(config, entityType, entityId);
}
@Override
public CalculatedFieldType getType() {
return CalculatedFieldType.LAST_RECORDS;
}
}

View File

@ -24,8 +24,9 @@ import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.cf.CalculatedField; import org.thingsboard.server.common.data.cf.CalculatedField;
import org.thingsboard.server.common.data.cf.configuration.CalculatedFieldConfiguration;
import org.thingsboard.server.common.data.cf.CalculatedFieldType; import org.thingsboard.server.common.data.cf.CalculatedFieldType;
import org.thingsboard.server.common.data.cf.configuration.CalculatedFieldConfiguration;
import org.thingsboard.server.common.data.cf.configuration.LastRecordsCalculatedFieldConfiguration;
import org.thingsboard.server.common.data.cf.configuration.ScriptCalculatedFieldConfiguration; import org.thingsboard.server.common.data.cf.configuration.ScriptCalculatedFieldConfiguration;
import org.thingsboard.server.common.data.cf.configuration.SimpleCalculatedFieldConfiguration; import org.thingsboard.server.common.data.cf.configuration.SimpleCalculatedFieldConfiguration;
import org.thingsboard.server.common.data.id.CalculatedFieldId; import org.thingsboard.server.common.data.id.CalculatedFieldId;
@ -120,14 +121,11 @@ public class CalculatedFieldEntity extends BaseSqlEntity<CalculatedField> implem
} }
private CalculatedFieldConfiguration readCalculatedFieldConfiguration(JsonNode config, EntityType entityType, UUID entityId) { private CalculatedFieldConfiguration readCalculatedFieldConfiguration(JsonNode config, EntityType entityType, UUID entityId) {
switch (CalculatedFieldType.valueOf(type)) { return switch (CalculatedFieldType.valueOf(type)) {
case SIMPLE: case SIMPLE -> new SimpleCalculatedFieldConfiguration(config, entityType, entityId);
return new SimpleCalculatedFieldConfiguration(config, entityType, entityId); case SCRIPT -> new ScriptCalculatedFieldConfiguration(config, entityType, entityId);
case SCRIPT: case LAST_RECORDS -> new LastRecordsCalculatedFieldConfiguration(config, entityType, entityId);
return new ScriptCalculatedFieldConfiguration(config, entityType, entityId); };
default:
throw new IllegalArgumentException("Unsupported calculated field type: " + type + "!");
}
} }
} }

View File

@ -29,6 +29,7 @@ import org.thingsboard.server.common.data.cf.CalculatedFieldLink;
import org.thingsboard.server.common.data.cf.CalculatedFieldLinkConfiguration; import org.thingsboard.server.common.data.cf.CalculatedFieldLinkConfiguration;
import org.thingsboard.server.common.data.cf.CalculatedFieldType; import org.thingsboard.server.common.data.cf.CalculatedFieldType;
import org.thingsboard.server.common.data.cf.configuration.CalculatedFieldConfiguration; import org.thingsboard.server.common.data.cf.configuration.CalculatedFieldConfiguration;
import org.thingsboard.server.common.data.cf.configuration.LastRecordsCalculatedFieldConfiguration;
import org.thingsboard.server.common.data.cf.configuration.ScriptCalculatedFieldConfiguration; import org.thingsboard.server.common.data.cf.configuration.ScriptCalculatedFieldConfiguration;
import org.thingsboard.server.common.data.cf.configuration.SimpleCalculatedFieldConfiguration; import org.thingsboard.server.common.data.cf.configuration.SimpleCalculatedFieldConfiguration;
import org.thingsboard.server.common.data.id.CalculatedFieldId; import org.thingsboard.server.common.data.id.CalculatedFieldId;
@ -136,14 +137,11 @@ public class DefaultNativeCalculatedFieldRepository implements NativeCalculatedF
} }
private CalculatedFieldConfiguration readCalculatedFieldConfiguration(CalculatedFieldType type, JsonNode config, EntityType entityType, UUID entityId) { private CalculatedFieldConfiguration readCalculatedFieldConfiguration(CalculatedFieldType type, JsonNode config, EntityType entityType, UUID entityId) {
switch (type) { return switch (type) {
case SIMPLE: case SIMPLE -> new SimpleCalculatedFieldConfiguration(config, entityType, entityId);
return new SimpleCalculatedFieldConfiguration(config, entityType, entityId); case SCRIPT -> new ScriptCalculatedFieldConfiguration(config, entityType, entityId);
case SCRIPT: case LAST_RECORDS -> new LastRecordsCalculatedFieldConfiguration(config, entityType, entityId);
return new ScriptCalculatedFieldConfiguration(config, entityType, entityId); };
default:
throw new IllegalArgumentException("Unsupported calculated field type: " + type + "!");
}
} }
} }