refactoring after merge to PE
This commit is contained in:
		
							parent
							
								
									c9e4904135
								
							
						
					
					
						commit
						eb36297b69
					
				@ -321,7 +321,7 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM
 | 
				
			|||||||
                        callback.onSuccess();
 | 
					                        callback.onSuccess();
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    if (DebugModeUtil.isDebugAllAvailable(ctx.getCalculatedField())) {
 | 
					                    if (DebugModeUtil.isDebugAllAvailable(ctx.getCalculatedField())) {
 | 
				
			||||||
                        systemContext.persistCalculatedFieldDebugEvent(tenantId, ctx.getCfId(), entityId, state.getArguments(), tbMsgId, tbMsgType, calculationResult.getResult().toString(), null);
 | 
					                        systemContext.persistCalculatedFieldDebugEvent(tenantId, ctx.getCfId(), entityId, state.getArguments(), tbMsgId, tbMsgType, calculationResult.toStringOrElseNull(), null);
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
 | 
				
			|||||||
@ -27,7 +27,7 @@ import org.thingsboard.server.common.data.EntityType;
 | 
				
			|||||||
import org.thingsboard.server.common.data.ProfileEntityIdInfo;
 | 
					import org.thingsboard.server.common.data.ProfileEntityIdInfo;
 | 
				
			||||||
import org.thingsboard.server.common.data.cf.CalculatedField;
 | 
					import org.thingsboard.server.common.data.cf.CalculatedField;
 | 
				
			||||||
import org.thingsboard.server.common.data.cf.CalculatedFieldLink;
 | 
					import org.thingsboard.server.common.data.cf.CalculatedFieldLink;
 | 
				
			||||||
import org.thingsboard.server.common.data.cf.configuration.ScheduleSupportedCalculatedFieldConfiguration;
 | 
					import org.thingsboard.server.common.data.cf.configuration.ScheduledUpdateSupportedCalculatedFieldConfiguration;
 | 
				
			||||||
import org.thingsboard.server.common.data.id.AssetId;
 | 
					import org.thingsboard.server.common.data.id.AssetId;
 | 
				
			||||||
import org.thingsboard.server.common.data.id.CalculatedFieldId;
 | 
					import org.thingsboard.server.common.data.id.CalculatedFieldId;
 | 
				
			||||||
import org.thingsboard.server.common.data.id.DeviceId;
 | 
					import org.thingsboard.server.common.data.id.DeviceId;
 | 
				
			||||||
@ -482,7 +482,7 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    private void scheduleDynamicArgumentsRefreshTaskForCfIfNeeded(CalculatedFieldCtx cfCtx) {
 | 
					    private void scheduleDynamicArgumentsRefreshTaskForCfIfNeeded(CalculatedFieldCtx cfCtx) {
 | 
				
			||||||
        CalculatedField cf = cfCtx.getCalculatedField();
 | 
					        CalculatedField cf = cfCtx.getCalculatedField();
 | 
				
			||||||
        if (!(cf.getConfiguration() instanceof ScheduleSupportedCalculatedFieldConfiguration scheduledCfConfig)) {
 | 
					        if (!(cf.getConfiguration() instanceof ScheduledUpdateSupportedCalculatedFieldConfiguration scheduledCfConfig)) {
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (!scheduledCfConfig.isScheduledUpdateEnabled()) {
 | 
					        if (!scheduledCfConfig.isScheduledUpdateEnabled()) {
 | 
				
			||||||
 | 
				
			|||||||
@ -34,14 +34,8 @@ public final class CalculatedFieldResult {
 | 
				
			|||||||
                (result.isTextual() && result.asText().isEmpty());
 | 
					                (result.isTextual() && result.asText().isEmpty());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public String getResultAsString() {
 | 
					    public String toStringOrElseNull() {
 | 
				
			||||||
        if (result == null) {
 | 
					        return result == null ? null : result.toString();
 | 
				
			||||||
            return null;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if (result.isTextual()) {
 | 
					 | 
				
			||||||
            return result.asText();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return result.toString();
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -23,7 +23,6 @@ import jakarta.annotation.PostConstruct;
 | 
				
			|||||||
import jakarta.annotation.PreDestroy;
 | 
					import jakarta.annotation.PreDestroy;
 | 
				
			||||||
import lombok.RequiredArgsConstructor;
 | 
					import lombok.RequiredArgsConstructor;
 | 
				
			||||||
import lombok.extern.slf4j.Slf4j;
 | 
					import lombok.extern.slf4j.Slf4j;
 | 
				
			||||||
import org.apache.commons.lang3.math.NumberUtils;
 | 
					 | 
				
			||||||
import org.springframework.stereotype.Service;
 | 
					import org.springframework.stereotype.Service;
 | 
				
			||||||
import org.thingsboard.common.util.ThingsBoardExecutors;
 | 
					import org.thingsboard.common.util.ThingsBoardExecutors;
 | 
				
			||||||
import org.thingsboard.server.actors.calculatedField.CalculatedFieldTelemetryMsg;
 | 
					import org.thingsboard.server.actors.calculatedField.CalculatedFieldTelemetryMsg;
 | 
				
			||||||
@ -31,7 +30,6 @@ import org.thingsboard.server.actors.calculatedField.MultipleTbCallback;
 | 
				
			|||||||
import org.thingsboard.server.cluster.TbClusterService;
 | 
					import org.thingsboard.server.cluster.TbClusterService;
 | 
				
			||||||
import org.thingsboard.server.common.data.DataConstants;
 | 
					import org.thingsboard.server.common.data.DataConstants;
 | 
				
			||||||
import org.thingsboard.server.common.data.EntityType;
 | 
					import org.thingsboard.server.common.data.EntityType;
 | 
				
			||||||
import org.thingsboard.server.common.data.StringUtils;
 | 
					 | 
				
			||||||
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.ArgumentType;
 | 
					import org.thingsboard.server.common.data.cf.configuration.ArgumentType;
 | 
				
			||||||
@ -45,11 +43,7 @@ import org.thingsboard.server.common.data.kv.AttributeKvEntry;
 | 
				
			|||||||
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.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.DoubleDataEntry;
 | 
					 | 
				
			||||||
import org.thingsboard.server.common.data.kv.KvEntry;
 | 
					 | 
				
			||||||
import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
 | 
					import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
 | 
				
			||||||
import org.thingsboard.server.common.data.kv.StringDataEntry;
 | 
					 | 
				
			||||||
import org.thingsboard.server.common.data.kv.TsKvEntry;
 | 
					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.relation.RelationTypeGroup;
 | 
					import org.thingsboard.server.common.data.relation.RelationTypeGroup;
 | 
				
			||||||
@ -76,10 +70,6 @@ import org.thingsboard.server.service.cf.ctx.CalculatedFieldEntityCtxId;
 | 
				
			|||||||
import org.thingsboard.server.service.cf.ctx.state.ArgumentEntry;
 | 
					import org.thingsboard.server.service.cf.ctx.state.ArgumentEntry;
 | 
				
			||||||
import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldCtx;
 | 
					import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldCtx;
 | 
				
			||||||
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.GeofencingCalculatedFieldState;
 | 
					 | 
				
			||||||
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.SingleValueArgumentEntry;
 | 
					 | 
				
			||||||
import org.thingsboard.server.service.cf.ctx.state.TsRollingArgumentEntry;
 | 
					import org.thingsboard.server.service.cf.ctx.state.TsRollingArgumentEntry;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.util.ArrayList;
 | 
					import java.util.ArrayList;
 | 
				
			||||||
@ -96,6 +86,9 @@ import java.util.stream.Collectors;
 | 
				
			|||||||
import static org.thingsboard.server.common.data.DataConstants.SCOPE;
 | 
					import static org.thingsboard.server.common.data.DataConstants.SCOPE;
 | 
				
			||||||
import static org.thingsboard.server.common.data.cf.configuration.geofencing.EntityCoordinates.ENTITY_ID_LATITUDE_ARGUMENT_KEY;
 | 
					import static org.thingsboard.server.common.data.cf.configuration.geofencing.EntityCoordinates.ENTITY_ID_LATITUDE_ARGUMENT_KEY;
 | 
				
			||||||
import static org.thingsboard.server.common.data.cf.configuration.geofencing.EntityCoordinates.ENTITY_ID_LONGITUDE_ARGUMENT_KEY;
 | 
					import static org.thingsboard.server.common.data.cf.configuration.geofencing.EntityCoordinates.ENTITY_ID_LONGITUDE_ARGUMENT_KEY;
 | 
				
			||||||
 | 
					import static org.thingsboard.server.utils.CalculatedFieldArgumentUtils.createDefaultKvEntry;
 | 
				
			||||||
 | 
					import static org.thingsboard.server.utils.CalculatedFieldArgumentUtils.createStateByType;
 | 
				
			||||||
 | 
					import static org.thingsboard.server.utils.CalculatedFieldArgumentUtils.transformSingleValueArgument;
 | 
				
			||||||
import static org.thingsboard.server.utils.CalculatedFieldUtils.toProto;
 | 
					import static org.thingsboard.server.utils.CalculatedFieldUtils.toProto;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@TbRuleEngineComponent
 | 
					@TbRuleEngineComponent
 | 
				
			||||||
@ -144,7 +137,7 @@ public class DefaultCalculatedFieldProcessingService implements CalculatedFieldP
 | 
				
			|||||||
            var result = createStateByType(ctx);
 | 
					            var result = createStateByType(ctx);
 | 
				
			||||||
            result.updateState(ctx, resolveArgumentFutures(argFutures));
 | 
					            result.updateState(ctx, resolveArgumentFutures(argFutures));
 | 
				
			||||||
            return result;
 | 
					            return result;
 | 
				
			||||||
        }, calculatedFieldCallbackExecutor);
 | 
					        }, MoreExecutors.directExecutor());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
@ -171,7 +164,7 @@ public class DefaultCalculatedFieldProcessingService implements CalculatedFieldP
 | 
				
			|||||||
                default -> {
 | 
					                default -> {
 | 
				
			||||||
                    var resolvedEntityIdsFuture = resolveGeofencingEntityIds(ctx.getTenantId(), entityId, entry);
 | 
					                    var resolvedEntityIdsFuture = resolveGeofencingEntityIds(ctx.getTenantId(), entityId, entry);
 | 
				
			||||||
                    argFutures.put(entry.getKey(), Futures.transformAsync(resolvedEntityIdsFuture, resolvedEntityIds ->
 | 
					                    argFutures.put(entry.getKey(), Futures.transformAsync(resolvedEntityIdsFuture, resolvedEntityIds ->
 | 
				
			||||||
                            fetchGeofencingKvEntry(ctx.getTenantId(), resolvedEntityIds, entry.getValue()), calculatedFieldCallbackExecutor));
 | 
					                            fetchGeofencingKvEntry(ctx.getTenantId(), resolvedEntityIds, entry.getValue()), MoreExecutors.directExecutor()));
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -210,7 +203,7 @@ public class DefaultCalculatedFieldProcessingService implements CalculatedFieldP
 | 
				
			|||||||
            OutputType type = calculatedFieldResult.getType();
 | 
					            OutputType type = calculatedFieldResult.getType();
 | 
				
			||||||
            TbMsgType msgType = OutputType.ATTRIBUTES.equals(type) ? TbMsgType.POST_ATTRIBUTES_REQUEST : TbMsgType.POST_TELEMETRY_REQUEST;
 | 
					            TbMsgType msgType = OutputType.ATTRIBUTES.equals(type) ? TbMsgType.POST_ATTRIBUTES_REQUEST : TbMsgType.POST_TELEMETRY_REQUEST;
 | 
				
			||||||
            TbMsgMetaData md = OutputType.ATTRIBUTES.equals(type) ? new TbMsgMetaData(Map.of(SCOPE, calculatedFieldResult.getScope().name())) : TbMsgMetaData.EMPTY;
 | 
					            TbMsgMetaData md = OutputType.ATTRIBUTES.equals(type) ? new TbMsgMetaData(Map.of(SCOPE, calculatedFieldResult.getScope().name())) : TbMsgMetaData.EMPTY;
 | 
				
			||||||
            TbMsg msg = TbMsg.newMsg().type(msgType).originator(entityId).previousCalculatedFieldIds(cfIds).metaData(md).data(calculatedFieldResult.getResult().toString()).build();
 | 
					            TbMsg msg = TbMsg.newMsg().type(msgType).originator(entityId).previousCalculatedFieldIds(cfIds).metaData(md).data(calculatedFieldResult.toStringOrElseNull()).build();
 | 
				
			||||||
            clusterService.pushMsgToRuleEngine(tenantId, entityId, msg, new TbQueueCallback() {
 | 
					            clusterService.pushMsgToRuleEngine(tenantId, entityId, msg, new TbQueueCallback() {
 | 
				
			||||||
                @Override
 | 
					                @Override
 | 
				
			||||||
                public void onSuccess(TbQueueMsgMetadata metadata) {
 | 
					                public void onSuccess(TbQueueMsgMetadata metadata) {
 | 
				
			||||||
@ -337,20 +330,16 @@ public class DefaultCalculatedFieldProcessingService implements CalculatedFieldP
 | 
				
			|||||||
        ListenableFuture<List<Entry<EntityId, AttributeKvEntry>>> allFutures = Futures.allAsList(kvFutures);
 | 
					        ListenableFuture<List<Entry<EntityId, AttributeKvEntry>>> allFutures = Futures.allAsList(kvFutures);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return Futures.transform(allFutures, entries -> ArgumentEntry.createGeofencingValueArgument(entries.stream()
 | 
					        return Futures.transform(allFutures, entries -> ArgumentEntry.createGeofencingValueArgument(entries.stream()
 | 
				
			||||||
                        .collect(Collectors.toMap(Entry::getKey, Entry::getValue))),
 | 
					                        .collect(Collectors.toMap(Entry::getKey, Entry::getValue))), MoreExecutors.directExecutor());
 | 
				
			||||||
                calculatedFieldCallbackExecutor
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private ListenableFuture<ArgumentEntry> fetchKvEntry(TenantId tenantId, EntityId entityId, Argument argument) {
 | 
					    private ListenableFuture<ArgumentEntry> fetchKvEntry(TenantId tenantId, EntityId entityId, Argument argument) {
 | 
				
			||||||
        return switch (argument.getRefEntityKey().getType()) {
 | 
					        return switch (argument.getRefEntityKey().getType()) {
 | 
				
			||||||
            case TS_ROLLING -> fetchTsRolling(tenantId, entityId, argument);
 | 
					            case TS_ROLLING -> fetchTsRolling(tenantId, entityId, argument);
 | 
				
			||||||
            case ATTRIBUTE -> transformSingleValueArgument(
 | 
					            case ATTRIBUTE -> transformSingleValueArgument(
 | 
				
			||||||
                    Futures.transform(
 | 
					                    Futures.transform(attributesService.find(tenantId, entityId, argument.getRefEntityKey().getScope(), argument.getRefEntityKey().getKey()),
 | 
				
			||||||
                            attributesService.find(tenantId, entityId, argument.getRefEntityKey().getScope(), argument.getRefEntityKey().getKey()),
 | 
					 | 
				
			||||||
                            result -> result.or(() -> Optional.of(new BaseAttributeKvEntry(createDefaultKvEntry(argument), System.currentTimeMillis(), 0L))),
 | 
					                            result -> result.or(() -> Optional.of(new BaseAttributeKvEntry(createDefaultKvEntry(argument), System.currentTimeMillis(), 0L))),
 | 
				
			||||||
                            calculatedFieldCallbackExecutor)
 | 
					                            calculatedFieldCallbackExecutor));
 | 
				
			||||||
            );
 | 
					 | 
				
			||||||
            case TS_LATEST -> transformSingleValueArgument(
 | 
					            case TS_LATEST -> transformSingleValueArgument(
 | 
				
			||||||
                    Futures.transform(
 | 
					                    Futures.transform(
 | 
				
			||||||
                            timeseriesService.findLatest(tenantId, entityId, argument.getRefEntityKey().getKey()),
 | 
					                            timeseriesService.findLatest(tenantId, entityId, argument.getRefEntityKey().getKey()),
 | 
				
			||||||
@ -359,16 +348,6 @@ public class DefaultCalculatedFieldProcessingService implements CalculatedFieldP
 | 
				
			|||||||
        };
 | 
					        };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private ListenableFuture<ArgumentEntry> transformSingleValueArgument(ListenableFuture<Optional<? extends KvEntry>> kvEntryFuture) {
 | 
					 | 
				
			||||||
        return Futures.transform(kvEntryFuture, kvEntry -> {
 | 
					 | 
				
			||||||
            if (kvEntry.isPresent() && kvEntry.get().getValue() != null) {
 | 
					 | 
				
			||||||
                return ArgumentEntry.createSingleValueArgument(kvEntry.get());
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                return new SingleValueArgumentEntry();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }, calculatedFieldCallbackExecutor);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private ListenableFuture<ArgumentEntry> fetchTsRolling(TenantId tenantId, EntityId entityId, Argument argument) {
 | 
					    private ListenableFuture<ArgumentEntry> fetchTsRolling(TenantId tenantId, EntityId entityId, Argument argument) {
 | 
				
			||||||
        long currentTime = System.currentTimeMillis();
 | 
					        long currentTime = System.currentTimeMillis();
 | 
				
			||||||
        long timeWindow = argument.getTimeWindow() == 0 ? System.currentTimeMillis() : argument.getTimeWindow();
 | 
					        long timeWindow = argument.getTimeWindow() == 0 ? System.currentTimeMillis() : argument.getTimeWindow();
 | 
				
			||||||
@ -383,29 +362,6 @@ public class DefaultCalculatedFieldProcessingService implements CalculatedFieldP
 | 
				
			|||||||
        return Futures.transform(tsRollingFuture, tsRolling -> tsRolling == null ? new TsRollingArgumentEntry(limit, timeWindow) : ArgumentEntry.createTsRollingArgument(tsRolling, limit, timeWindow), calculatedFieldCallbackExecutor);
 | 
					        return Futures.transform(tsRollingFuture, tsRolling -> tsRolling == null ? new TsRollingArgumentEntry(limit, timeWindow) : ArgumentEntry.createTsRollingArgument(tsRolling, limit, timeWindow), calculatedFieldCallbackExecutor);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private KvEntry createDefaultKvEntry(Argument argument) {
 | 
					 | 
				
			||||||
        String key = argument.getRefEntityKey().getKey();
 | 
					 | 
				
			||||||
        String defaultValue = argument.getDefaultValue();
 | 
					 | 
				
			||||||
        if (StringUtils.isBlank(defaultValue)) {
 | 
					 | 
				
			||||||
            return new StringDataEntry(key, null);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if (NumberUtils.isParsable(defaultValue)) {
 | 
					 | 
				
			||||||
            return new DoubleDataEntry(key, Double.parseDouble(defaultValue));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if ("true".equalsIgnoreCase(defaultValue) || "false".equalsIgnoreCase(defaultValue)) {
 | 
					 | 
				
			||||||
            return new BooleanDataEntry(key, Boolean.parseBoolean(defaultValue));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return new StringDataEntry(key, defaultValue);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private CalculatedFieldState createStateByType(CalculatedFieldCtx ctx) {
 | 
					 | 
				
			||||||
        return switch (ctx.getCfType()) {
 | 
					 | 
				
			||||||
            case SIMPLE -> new SimpleCalculatedFieldState(ctx.getArgNames());
 | 
					 | 
				
			||||||
            case SCRIPT -> new ScriptCalculatedFieldState(ctx.getArgNames());
 | 
					 | 
				
			||||||
            case GEOFENCING -> new GeofencingCalculatedFieldState(ctx.getArgNames());
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private static class TbCallbackWrapper implements TbQueueCallback {
 | 
					    private static class TbCallbackWrapper implements TbQueueCallback {
 | 
				
			||||||
        private final TbCallback callback;
 | 
					        private final TbCallback callback;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -29,7 +29,7 @@ import org.thingsboard.server.common.data.cf.configuration.ArgumentsBasedCalcula
 | 
				
			|||||||
import org.thingsboard.server.common.data.cf.configuration.ExpressionBasedCalculatedFieldConfiguration;
 | 
					import org.thingsboard.server.common.data.cf.configuration.ExpressionBasedCalculatedFieldConfiguration;
 | 
				
			||||||
import org.thingsboard.server.common.data.cf.configuration.Output;
 | 
					import org.thingsboard.server.common.data.cf.configuration.Output;
 | 
				
			||||||
import org.thingsboard.server.common.data.cf.configuration.ReferencedEntityKey;
 | 
					import org.thingsboard.server.common.data.cf.configuration.ReferencedEntityKey;
 | 
				
			||||||
import org.thingsboard.server.common.data.cf.configuration.ScheduleSupportedCalculatedFieldConfiguration;
 | 
					import org.thingsboard.server.common.data.cf.configuration.ScheduledUpdateSupportedCalculatedFieldConfiguration;
 | 
				
			||||||
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;
 | 
				
			||||||
import org.thingsboard.server.common.data.id.EntityId;
 | 
					import org.thingsboard.server.common.data.id.EntityId;
 | 
				
			||||||
@ -67,11 +67,10 @@ public class CalculatedFieldCtx {
 | 
				
			|||||||
    private String expression;
 | 
					    private String expression;
 | 
				
			||||||
    private boolean useLatestTs;
 | 
					    private boolean useLatestTs;
 | 
				
			||||||
    private TbelInvokeService tbelInvokeService;
 | 
					    private TbelInvokeService tbelInvokeService;
 | 
				
			||||||
 | 
					    private RelationService relationService;
 | 
				
			||||||
    private CalculatedFieldScriptEngine calculatedFieldScriptEngine;
 | 
					    private CalculatedFieldScriptEngine calculatedFieldScriptEngine;
 | 
				
			||||||
    private ThreadLocal<Expression> customExpression;
 | 
					    private ThreadLocal<Expression> customExpression;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private RelationService relationService;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private boolean initialized;
 | 
					    private boolean initialized;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private long maxDataPointsPerRollingArg;
 | 
					    private long maxDataPointsPerRollingArg;
 | 
				
			||||||
@ -129,7 +128,7 @@ public class CalculatedFieldCtx {
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            case GEOFENCING -> initialized = true;
 | 
					            case GEOFENCING -> initialized = true;
 | 
				
			||||||
            default -> {
 | 
					            case SIMPLE -> {
 | 
				
			||||||
                if (isValidExpression(expression)) {
 | 
					                if (isValidExpression(expression)) {
 | 
				
			||||||
                    this.customExpression = ThreadLocal.withInitial(() ->
 | 
					                    this.customExpression = ThreadLocal.withInitial(() ->
 | 
				
			||||||
                            new ExpressionBuilder(expression)
 | 
					                            new ExpressionBuilder(expression)
 | 
				
			||||||
@ -323,8 +322,8 @@ public class CalculatedFieldCtx {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public boolean hasSchedulingConfigChanges(CalculatedFieldCtx other) {
 | 
					    public boolean hasSchedulingConfigChanges(CalculatedFieldCtx other) {
 | 
				
			||||||
        if (calculatedField.getConfiguration() instanceof ScheduleSupportedCalculatedFieldConfiguration thisConfig
 | 
					        if (calculatedField.getConfiguration() instanceof ScheduledUpdateSupportedCalculatedFieldConfiguration thisConfig
 | 
				
			||||||
                && other.calculatedField.getConfiguration() instanceof ScheduleSupportedCalculatedFieldConfiguration otherConfig) {
 | 
					                && other.calculatedField.getConfiguration() instanceof ScheduledUpdateSupportedCalculatedFieldConfiguration otherConfig) {
 | 
				
			||||||
            boolean refreshTriggerChanged = thisConfig.isScheduledUpdateEnabled() != otherConfig.isScheduledUpdateEnabled();
 | 
					            boolean refreshTriggerChanged = thisConfig.isScheduledUpdateEnabled() != otherConfig.isScheduledUpdateEnabled();
 | 
				
			||||||
            boolean refreshIntervalChanged = thisConfig.getScheduledUpdateIntervalSec() != otherConfig.getScheduledUpdateIntervalSec();
 | 
					            boolean refreshIntervalChanged = thisConfig.getScheduledUpdateIntervalSec() != otherConfig.getScheduledUpdateIntervalSec();
 | 
				
			||||||
            return refreshTriggerChanged || refreshIntervalChanged;
 | 
					            return refreshTriggerChanged || refreshIntervalChanged;
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,72 @@
 | 
				
			|||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Copyright © 2016-2025 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.utils;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.google.common.util.concurrent.Futures;
 | 
				
			||||||
 | 
					import com.google.common.util.concurrent.ListenableFuture;
 | 
				
			||||||
 | 
					import com.google.common.util.concurrent.MoreExecutors;
 | 
				
			||||||
 | 
					import org.apache.commons.lang3.math.NumberUtils;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.StringUtils;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.cf.configuration.Argument;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.kv.BooleanDataEntry;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.kv.DoubleDataEntry;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.kv.KvEntry;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.kv.StringDataEntry;
 | 
				
			||||||
 | 
					import org.thingsboard.server.service.cf.ctx.state.ArgumentEntry;
 | 
				
			||||||
 | 
					import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldCtx;
 | 
				
			||||||
 | 
					import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldState;
 | 
				
			||||||
 | 
					import org.thingsboard.server.service.cf.ctx.state.GeofencingCalculatedFieldState;
 | 
				
			||||||
 | 
					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.SingleValueArgumentEntry;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.Optional;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class CalculatedFieldArgumentUtils {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static ListenableFuture<ArgumentEntry> transformSingleValueArgument(ListenableFuture<Optional<? extends KvEntry>> kvEntryFuture) {
 | 
				
			||||||
 | 
					        return Futures.transform(kvEntryFuture, kvEntry -> {
 | 
				
			||||||
 | 
					            if (kvEntry.isPresent() && kvEntry.get().getValue() != null) {
 | 
				
			||||||
 | 
					                return ArgumentEntry.createSingleValueArgument(kvEntry.get());
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return new SingleValueArgumentEntry();
 | 
				
			||||||
 | 
					        }, MoreExecutors.directExecutor());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static KvEntry createDefaultKvEntry(Argument argument) {
 | 
				
			||||||
 | 
					        String key = argument.getRefEntityKey().getKey();
 | 
				
			||||||
 | 
					        String defaultValue = argument.getDefaultValue();
 | 
				
			||||||
 | 
					        if (StringUtils.isBlank(defaultValue)) {
 | 
				
			||||||
 | 
					            return new StringDataEntry(key, null);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (NumberUtils.isParsable(defaultValue)) {
 | 
				
			||||||
 | 
					            return new DoubleDataEntry(key, Double.parseDouble(defaultValue));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if ("true".equalsIgnoreCase(defaultValue) || "false".equalsIgnoreCase(defaultValue)) {
 | 
				
			||||||
 | 
					            return new BooleanDataEntry(key, Boolean.parseBoolean(defaultValue));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return new StringDataEntry(key, defaultValue);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static CalculatedFieldState createStateByType(CalculatedFieldCtx ctx) {
 | 
				
			||||||
 | 
					        return switch (ctx.getCfType()) {
 | 
				
			||||||
 | 
					            case SIMPLE -> new SimpleCalculatedFieldState(ctx.getArgNames());
 | 
				
			||||||
 | 
					            case SCRIPT -> new ScriptCalculatedFieldState(ctx.getArgNames());
 | 
				
			||||||
 | 
					            case GEOFENCING -> new GeofencingCalculatedFieldState(ctx.getArgNames());
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -448,7 +448,6 @@ public class GeofencingCalculatedFieldStateTest {
 | 
				
			|||||||
        var config = new GeofencingCalculatedFieldConfiguration();
 | 
					        var config = new GeofencingCalculatedFieldConfiguration();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        EntityCoordinates entityCoordinates = new EntityCoordinates("latitude", "longitude");
 | 
					        EntityCoordinates entityCoordinates = new EntityCoordinates("latitude", "longitude");
 | 
				
			||||||
        entityCoordinates.setRefEntityId(DEVICE_ID);
 | 
					 | 
				
			||||||
        config.setEntityCoordinates(entityCoordinates);
 | 
					        config.setEntityCoordinates(entityCoordinates);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ZoneGroupConfiguration allowedZonesGroup = new ZoneGroupConfiguration("allowedZones", "zone", reportStrategy, true);
 | 
					        ZoneGroupConfiguration allowedZonesGroup = new ZoneGroupConfiguration("allowedZones", "zone", reportStrategy, true);
 | 
				
			||||||
 | 
				
			|||||||
@ -26,7 +26,7 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
 | 
				
			|||||||
        property = "type"
 | 
					        property = "type"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
@JsonSubTypes({
 | 
					@JsonSubTypes({
 | 
				
			||||||
        @JsonSubTypes.Type(value = RelationQueryDynamicSourceConfiguration.class, name = "RELATION_QUERY"),
 | 
					        @JsonSubTypes.Type(value = RelationQueryDynamicSourceConfiguration.class, name = "RELATION_QUERY")
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
@JsonIgnoreProperties(ignoreUnknown = true)
 | 
					@JsonIgnoreProperties(ignoreUnknown = true)
 | 
				
			||||||
public interface CfArgumentDynamicSourceConfiguration {
 | 
					public interface CfArgumentDynamicSourceConfiguration {
 | 
				
			||||||
 | 
				
			|||||||
@ -20,15 +20,17 @@ import lombok.Data;
 | 
				
			|||||||
import org.thingsboard.server.common.data.cf.CalculatedFieldType;
 | 
					import org.thingsboard.server.common.data.cf.CalculatedFieldType;
 | 
				
			||||||
import org.thingsboard.server.common.data.cf.configuration.geofencing.EntityCoordinates;
 | 
					import org.thingsboard.server.common.data.cf.configuration.geofencing.EntityCoordinates;
 | 
				
			||||||
import org.thingsboard.server.common.data.cf.configuration.geofencing.ZoneGroupConfiguration;
 | 
					import org.thingsboard.server.common.data.cf.configuration.geofencing.ZoneGroupConfiguration;
 | 
				
			||||||
 | 
					import org.thingsboard.server.common.data.id.EntityId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.util.HashMap;
 | 
					import java.util.HashMap;
 | 
				
			||||||
import java.util.HashSet;
 | 
					import java.util.HashSet;
 | 
				
			||||||
import java.util.List;
 | 
					import java.util.List;
 | 
				
			||||||
import java.util.Map;
 | 
					import java.util.Map;
 | 
				
			||||||
 | 
					import java.util.Objects;
 | 
				
			||||||
import java.util.Set;
 | 
					import java.util.Set;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Data
 | 
					@Data
 | 
				
			||||||
public class GeofencingCalculatedFieldConfiguration implements ArgumentsBasedCalculatedFieldConfiguration, ScheduleSupportedCalculatedFieldConfiguration {
 | 
					public class GeofencingCalculatedFieldConfiguration implements ArgumentsBasedCalculatedFieldConfiguration, ScheduledUpdateSupportedCalculatedFieldConfiguration {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private EntityCoordinates entityCoordinates;
 | 
					    private EntityCoordinates entityCoordinates;
 | 
				
			||||||
    private List<ZoneGroupConfiguration> zoneGroups;
 | 
					    private List<ZoneGroupConfiguration> zoneGroups;
 | 
				
			||||||
@ -49,6 +51,11 @@ public class GeofencingCalculatedFieldConfiguration implements ArgumentsBasedCal
 | 
				
			|||||||
        return args;
 | 
					        return args;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public List<EntityId> getReferencedEntities() {
 | 
				
			||||||
 | 
					        return zoneGroups.stream().map(ZoneGroupConfiguration::getRefEntityId).filter(Objects::nonNull).toList();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public Output getOutput() {
 | 
					    public Output getOutput() {
 | 
				
			||||||
        return output;
 | 
					        return output;
 | 
				
			||||||
 | 
				
			|||||||
@ -17,7 +17,7 @@ package org.thingsboard.server.common.data.cf.configuration;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
 | 
					import com.fasterxml.jackson.annotation.JsonIgnore;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public interface ScheduleSupportedCalculatedFieldConfiguration extends CalculatedFieldConfiguration {
 | 
					public interface ScheduledUpdateSupportedCalculatedFieldConfiguration extends CalculatedFieldConfiguration {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @JsonIgnore
 | 
					    @JsonIgnore
 | 
				
			||||||
    boolean isScheduledUpdateEnabled();
 | 
					    boolean isScheduledUpdateEnabled();
 | 
				
			||||||
@ -35,9 +35,6 @@ public class EntityCoordinates {
 | 
				
			|||||||
    private final String latitudeKeyName;
 | 
					    private final String latitudeKeyName;
 | 
				
			||||||
    private final String longitudeKeyName;
 | 
					    private final String longitudeKeyName;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Nullable
 | 
					 | 
				
			||||||
    private EntityId refEntityId;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public void validate() {
 | 
					    public void validate() {
 | 
				
			||||||
        if (StringUtils.isBlank(latitudeKeyName)) {
 | 
					        if (StringUtils.isBlank(latitudeKeyName)) {
 | 
				
			||||||
            throw new IllegalArgumentException("Entity coordinates latitude key name must be specified!");
 | 
					            throw new IllegalArgumentException("Entity coordinates latitude key name must be specified!");
 | 
				
			||||||
@ -56,7 +53,6 @@ public class EntityCoordinates {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    private Argument toArgument(String keyName) {
 | 
					    private Argument toArgument(String keyName) {
 | 
				
			||||||
        var argument = new Argument();
 | 
					        var argument = new Argument();
 | 
				
			||||||
        argument.setRefEntityId(refEntityId);
 | 
					 | 
				
			||||||
        argument.setRefEntityKey(new ReferencedEntityKey(keyName, ArgumentType.TS_LATEST, null));
 | 
					        argument.setRefEntityKey(new ReferencedEntityKey(keyName, ArgumentType.TS_LATEST, null));
 | 
				
			||||||
        return argument;
 | 
					        return argument;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -1379,14 +1379,14 @@ public class ProtoUtils {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    public static TransportProtos.EntityIdProto toProto(EntityId entityId) {
 | 
					    public static TransportProtos.EntityIdProto toProto(EntityId entityId) {
 | 
				
			||||||
        return TransportProtos.EntityIdProto.newBuilder()
 | 
					        return TransportProtos.EntityIdProto.newBuilder()
 | 
				
			||||||
                .setEntityType(toProto(entityId.getEntityType()))
 | 
					 | 
				
			||||||
                .setEntityIdMSB(getMsb(entityId))
 | 
					                .setEntityIdMSB(getMsb(entityId))
 | 
				
			||||||
                .setEntityIdLSB(getLsb(entityId))
 | 
					                .setEntityIdLSB(getLsb(entityId))
 | 
				
			||||||
 | 
					                .setType(toProto(entityId.getEntityType()))
 | 
				
			||||||
                .build();
 | 
					                .build();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static EntityId fromProto(TransportProtos.EntityIdProto entityIdProto) {
 | 
					    public static EntityId fromProto(TransportProtos.EntityIdProto entityIdProto) {
 | 
				
			||||||
        return EntityIdFactory.getByTypeAndUuid(fromProto(entityIdProto.getEntityType()), new UUID(entityIdProto.getEntityIdMSB(), entityIdProto.getEntityIdLSB()));
 | 
					        return EntityIdFactory.getByTypeAndUuid(fromProto(entityIdProto.getType()), new UUID(entityIdProto.getEntityIdMSB(), entityIdProto.getEntityIdLSB()));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private static boolean isNotNull(Object obj) {
 | 
					    private static boolean isNotNull(Object obj) {
 | 
				
			||||||
 | 
				
			|||||||
@ -83,9 +83,9 @@ enum ApiUsageRecordKeyProto {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
message EntityIdProto {
 | 
					message EntityIdProto {
 | 
				
			||||||
  EntityTypeProto entityType = 1;
 | 
					  int64 entityIdMSB = 1;
 | 
				
			||||||
  int64 entityIdMSB = 2;
 | 
					  int64 entityIdLSB = 2;
 | 
				
			||||||
  int64 entityIdLSB = 3;
 | 
					  EntityTypeProto type = 4;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 | 
				
			|||||||
@ -357,7 +357,7 @@ class ProtoUtilsTest {
 | 
				
			|||||||
        // toProto
 | 
					        // toProto
 | 
				
			||||||
        TransportProtos.EntityIdProto proto = ProtoUtils.toProto(original);
 | 
					        TransportProtos.EntityIdProto proto = ProtoUtils.toProto(original);
 | 
				
			||||||
        assertThat(proto).isNotNull();
 | 
					        assertThat(proto).isNotNull();
 | 
				
			||||||
        assertThat(proto.getEntityType().getNumber()).isEqualTo(entityType.getProtoNumber());
 | 
					        assertThat(proto.getType().getNumber()).isEqualTo(entityType.getProtoNumber());
 | 
				
			||||||
        assertThat(proto.getEntityIdMSB()).isEqualTo(uuid.getMostSignificantBits());
 | 
					        assertThat(proto.getEntityIdMSB()).isEqualTo(uuid.getMostSignificantBits());
 | 
				
			||||||
        assertThat(proto.getEntityIdLSB()).isEqualTo(uuid.getLeastSignificantBits());
 | 
					        assertThat(proto.getEntityIdLSB()).isEqualTo(uuid.getLeastSignificantBits());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -29,9 +29,9 @@ public class PerimeterDefinitionSerializer extends JsonSerializer<PerimeterDefin
 | 
				
			|||||||
    public void serialize(PerimeterDefinition value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
 | 
					    public void serialize(PerimeterDefinition value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
 | 
				
			||||||
        if (value instanceof CirclePerimeterDefinition c) {
 | 
					        if (value instanceof CirclePerimeterDefinition c) {
 | 
				
			||||||
            gen.writeStartObject();
 | 
					            gen.writeStartObject();
 | 
				
			||||||
            gen.writeNumberField("latitude",  c.getLatitude());
 | 
					            gen.writeNumberField("latitude", c.getLatitude());
 | 
				
			||||||
            gen.writeNumberField("longitude", c.getLongitude());
 | 
					            gen.writeNumberField("longitude", c.getLongitude());
 | 
				
			||||||
            gen.writeNumberField("radius",    c.getRadius());
 | 
					            gen.writeNumberField("radius", c.getRadius());
 | 
				
			||||||
            gen.writeEndObject();
 | 
					            gen.writeEndObject();
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -22,7 +22,7 @@ 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.CalculatedFieldLink;
 | 
					import org.thingsboard.server.common.data.cf.CalculatedFieldLink;
 | 
				
			||||||
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.ScheduleSupportedCalculatedFieldConfiguration;
 | 
					import org.thingsboard.server.common.data.cf.configuration.ScheduledUpdateSupportedCalculatedFieldConfiguration;
 | 
				
			||||||
import org.thingsboard.server.common.data.id.CalculatedFieldId;
 | 
					import org.thingsboard.server.common.data.id.CalculatedFieldId;
 | 
				
			||||||
import org.thingsboard.server.common.data.id.CalculatedFieldLinkId;
 | 
					import org.thingsboard.server.common.data.id.CalculatedFieldLinkId;
 | 
				
			||||||
import org.thingsboard.server.common.data.id.EntityId;
 | 
					import org.thingsboard.server.common.data.id.EntityId;
 | 
				
			||||||
@ -94,7 +94,7 @@ public class BaseCalculatedFieldService extends AbstractEntityService implements
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private void updatedSchedulingConfiguration(CalculatedField calculatedField) {
 | 
					    private void updatedSchedulingConfiguration(CalculatedField calculatedField) {
 | 
				
			||||||
        if (calculatedField.getConfiguration() instanceof ScheduleSupportedCalculatedFieldConfiguration configuration) {
 | 
					        if (calculatedField.getConfiguration() instanceof ScheduledUpdateSupportedCalculatedFieldConfiguration configuration) {
 | 
				
			||||||
            if (!configuration.isScheduledUpdateEnabled()) {
 | 
					            if (!configuration.isScheduledUpdateEnabled()) {
 | 
				
			||||||
                return;
 | 
					                return;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
				
			|||||||
@ -66,17 +66,14 @@ public class CalculatedFieldDataValidator extends DataValidator<CalculatedField>
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private void validateNumberOfArgumentsPerCF(TenantId tenantId, CalculatedField calculatedField) {
 | 
					    private void validateNumberOfArgumentsPerCF(TenantId tenantId, CalculatedField calculatedField) {
 | 
				
			||||||
 | 
					        if (!(calculatedField instanceof ArgumentsBasedCalculatedFieldConfiguration argumentsBasedCfg)) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        long maxArgumentsPerCF = apiLimitService.getLimit(tenantId, DefaultTenantProfileConfiguration::getMaxArgumentsPerCF);
 | 
					        long maxArgumentsPerCF = apiLimitService.getLimit(tenantId, DefaultTenantProfileConfiguration::getMaxArgumentsPerCF);
 | 
				
			||||||
        if (maxArgumentsPerCF <= 0) {
 | 
					        if (maxArgumentsPerCF <= 0) {
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (CalculatedFieldType.GEOFENCING.equals(calculatedField.getType()) && maxArgumentsPerCF < 3) {
 | 
					        if (argumentsBasedCfg.getArguments().size() > maxArgumentsPerCF) {
 | 
				
			||||||
            throw new DataValidationException("Geofencing calculated field requires at least 3 arguments, but the system limit is " +
 | 
					 | 
				
			||||||
                    maxArgumentsPerCF + ". Contact your administrator to increase the limit."
 | 
					 | 
				
			||||||
            );
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if (calculatedField.getConfiguration() instanceof ArgumentsBasedCalculatedFieldConfiguration configuration
 | 
					 | 
				
			||||||
                && configuration.getArguments().size() > maxArgumentsPerCF) {
 | 
					 | 
				
			||||||
            throw new DataValidationException("Calculated field arguments limit reached!");
 | 
					            throw new DataValidationException("Calculated field arguments limit reached!");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -109,7 +109,6 @@ public class CalculatedFieldServiceTest extends AbstractServiceTest {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        // Coordinates: TS_LATEST, no dynamic source
 | 
					        // Coordinates: TS_LATEST, no dynamic source
 | 
				
			||||||
        EntityCoordinates entityCoordinates = new EntityCoordinates("latitude", "longitude");
 | 
					        EntityCoordinates entityCoordinates = new EntityCoordinates("latitude", "longitude");
 | 
				
			||||||
        entityCoordinates.setRefEntityId(device.getId());
 | 
					 | 
				
			||||||
        cfg.setEntityCoordinates(entityCoordinates);
 | 
					        cfg.setEntityCoordinates(entityCoordinates);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Zone-group argument (ATTRIBUTE) — no DYNAMIC configuration, so no scheduling even if the scheduled interval is set
 | 
					        // Zone-group argument (ATTRIBUTE) — no DYNAMIC configuration, so no scheduling even if the scheduled interval is set
 | 
				
			||||||
@ -156,7 +155,6 @@ public class CalculatedFieldServiceTest extends AbstractServiceTest {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        // Coordinates: TS_LATEST, no dynamic source
 | 
					        // Coordinates: TS_LATEST, no dynamic source
 | 
				
			||||||
        EntityCoordinates entityCoordinates = new EntityCoordinates("latitude", "longitude");
 | 
					        EntityCoordinates entityCoordinates = new EntityCoordinates("latitude", "longitude");
 | 
				
			||||||
        entityCoordinates.setRefEntityId(device.getId());
 | 
					 | 
				
			||||||
        cfg.setEntityCoordinates(entityCoordinates);
 | 
					        cfg.setEntityCoordinates(entityCoordinates);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Zone-group argument (ATTRIBUTE) — make it DYNAMIC so scheduling is enabled
 | 
					        // Zone-group argument (ATTRIBUTE) — make it DYNAMIC so scheduling is enabled
 | 
				
			||||||
@ -208,7 +206,6 @@ public class CalculatedFieldServiceTest extends AbstractServiceTest {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        // Coordinates: TS_LATEST, no dynamic source
 | 
					        // Coordinates: TS_LATEST, no dynamic source
 | 
				
			||||||
        EntityCoordinates entityCoordinates = new EntityCoordinates("latitude", "longitude");
 | 
					        EntityCoordinates entityCoordinates = new EntityCoordinates("latitude", "longitude");
 | 
				
			||||||
        entityCoordinates.setRefEntityId(device.getId());
 | 
					 | 
				
			||||||
        cfg.setEntityCoordinates(entityCoordinates);
 | 
					        cfg.setEntityCoordinates(entityCoordinates);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Zone-group argument (ATTRIBUTE) — make it DYNAMIC so scheduling is enabled
 | 
					        // Zone-group argument (ATTRIBUTE) — make it DYNAMIC so scheduling is enabled
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user