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