Added validation for new configuration + fixed relation creation for profile entities
This commit is contained in:
parent
3643b54985
commit
baba433f0f
@ -321,7 +321,7 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM
|
||||
boolean stateSizeChecked = false;
|
||||
try {
|
||||
if (ctx.isInitialized() && state.isReady()) {
|
||||
CalculatedFieldResult calculationResult = state.performCalculation(ctx).get(systemContext.getCfCalculationResultTimeout(), TimeUnit.SECONDS);
|
||||
CalculatedFieldResult calculationResult = state.performCalculation(entityId, ctx).get(systemContext.getCfCalculationResultTimeout(), TimeUnit.SECONDS);
|
||||
state.checkStateSize(ctxId, ctx.getMaxStateSize());
|
||||
stateSizeChecked = true;
|
||||
if (state.isSizeOk()) {
|
||||
|
||||
@ -35,15 +35,13 @@ public abstract class BaseCalculatedFieldState implements CalculatedFieldState {
|
||||
|
||||
protected long latestTimestamp = -1;
|
||||
|
||||
private boolean dirty;
|
||||
|
||||
public BaseCalculatedFieldState(List<String> requiredArguments) {
|
||||
this.requiredArguments = requiredArguments;
|
||||
this.arguments = new HashMap<>();
|
||||
}
|
||||
|
||||
public BaseCalculatedFieldState() {
|
||||
this(new ArrayList<>(), new HashMap<>(), false, -1, false);
|
||||
this(new ArrayList<>(), new HashMap<>(), false, -1);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -20,6 +20,7 @@ import com.fasterxml.jackson.annotation.JsonSubTypes;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import org.thingsboard.server.common.data.cf.CalculatedFieldType;
|
||||
import org.thingsboard.server.common.data.id.EntityId;
|
||||
import org.thingsboard.server.service.cf.CalculatedFieldResult;
|
||||
import org.thingsboard.server.service.cf.ctx.CalculatedFieldEntityCtxId;
|
||||
|
||||
@ -47,15 +48,18 @@ public interface CalculatedFieldState {
|
||||
|
||||
long getLatestTimestamp();
|
||||
|
||||
void setDirty(boolean dirty);
|
||||
default void setDirty(boolean dirty) {
|
||||
}
|
||||
|
||||
boolean isDirty();
|
||||
default boolean isDirty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
void setRequiredArguments(List<String> requiredArguments);
|
||||
|
||||
boolean updateState(CalculatedFieldCtx ctx, Map<String, ArgumentEntry> argumentValues);
|
||||
|
||||
ListenableFuture<CalculatedFieldResult> performCalculation(CalculatedFieldCtx ctx);
|
||||
ListenableFuture<CalculatedFieldResult> performCalculation(EntityId entityId, CalculatedFieldCtx ctx);
|
||||
|
||||
@JsonIgnore
|
||||
boolean isReady();
|
||||
@ -70,7 +74,6 @@ public interface CalculatedFieldState {
|
||||
void checkStateSize(CalculatedFieldEntityCtxId ctxId, long maxStateSize);
|
||||
|
||||
default void checkArgumentSize(String name, ArgumentEntry entry, CalculatedFieldCtx ctx) {
|
||||
// TODO: Do we need to restrict the size of Geofencing arguments? Number of zones?
|
||||
if (entry instanceof TsRollingArgumentEntry || entry instanceof GeofencingArgumentEntry) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -43,7 +43,6 @@ import java.util.stream.Collectors;
|
||||
|
||||
import static org.thingsboard.server.common.data.cf.configuration.GeofencingCalculatedFieldConfiguration.ENTITY_ID_LATITUDE_ARGUMENT_KEY;
|
||||
import static org.thingsboard.server.common.data.cf.configuration.GeofencingCalculatedFieldConfiguration.ENTITY_ID_LONGITUDE_ARGUMENT_KEY;
|
||||
import static org.thingsboard.server.common.data.cf.configuration.GeofencingCalculatedFieldConfiguration.coordinateKeys;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@ -116,20 +115,20 @@ public class GeofencingCalculatedFieldState implements CalculatedFieldState {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListenableFuture<CalculatedFieldResult> performCalculation(CalculatedFieldCtx ctx) {
|
||||
public ListenableFuture<CalculatedFieldResult> performCalculation(EntityId entityId, CalculatedFieldCtx ctx) {
|
||||
double latitude = (double) arguments.get(ENTITY_ID_LATITUDE_ARGUMENT_KEY).getValue();
|
||||
double longitude = (double) arguments.get(ENTITY_ID_LONGITUDE_ARGUMENT_KEY).getValue();
|
||||
Coordinates entityCoordinates = new Coordinates(latitude, longitude);
|
||||
|
||||
var configuration = (GeofencingCalculatedFieldConfiguration) ctx.getCalculatedField().getConfiguration();
|
||||
if (configuration.isTrackRelationToZones()) {
|
||||
// TODO: currently creates relation to device profile if CF created for profile)
|
||||
return calculateWithRelations(ctx, entityCoordinates, configuration);
|
||||
if (configuration.isCreateRelationsWithMatchedZones()) {
|
||||
return calculateWithRelations(entityId, ctx, entityCoordinates, configuration);
|
||||
}
|
||||
return calculateWithoutRelations(ctx, entityCoordinates, configuration);
|
||||
}
|
||||
|
||||
private ListenableFuture<CalculatedFieldResult> calculateWithRelations(
|
||||
EntityId entityId,
|
||||
CalculatedFieldCtx ctx,
|
||||
Coordinates entityCoordinates,
|
||||
GeofencingCalculatedFieldConfiguration configuration) {
|
||||
@ -160,7 +159,7 @@ public class GeofencingCalculatedFieldState implements CalculatedFieldState {
|
||||
List<ListenableFuture<Boolean>> relationFutures = zoneEventMap.entrySet().stream()
|
||||
.filter(entry -> entry.getValue().isTransitionEvent())
|
||||
.map(entry -> {
|
||||
EntityRelation relation = toRelation(entry.getKey(), ctx, configuration);
|
||||
EntityRelation relation = toRelation(entry.getKey(), entityId, configuration);
|
||||
return switch (entry.getValue()) {
|
||||
case ENTERED -> ctx.getRelationService().saveRelationAsync(ctx.getTenantId(), relation);
|
||||
case LEFT -> ctx.getRelationService().deleteRelationAsync(ctx.getTenantId(), relation);
|
||||
@ -218,11 +217,10 @@ public class GeofencingCalculatedFieldState implements CalculatedFieldState {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Create a new class field to not do this on each calculation.
|
||||
private Map<String, GeofencingArgumentEntry> getGeofencingArguments() {
|
||||
return arguments.entrySet()
|
||||
.stream()
|
||||
.filter(entry -> !coordinateKeys.contains(entry.getKey()))
|
||||
.filter(entry -> entry.getValue().getType().equals(ArgumentEntryType.GEOFENCING))
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, entry -> (GeofencingArgumentEntry) entry.getValue()));
|
||||
}
|
||||
|
||||
@ -259,10 +257,10 @@ public class GeofencingCalculatedFieldState implements CalculatedFieldState {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private EntityRelation toRelation(EntityId zoneId, CalculatedFieldCtx ctx, GeofencingCalculatedFieldConfiguration configuration) {
|
||||
private EntityRelation toRelation(EntityId zoneId, EntityId entityId, GeofencingCalculatedFieldConfiguration configuration) {
|
||||
return switch (configuration.getZoneRelationDirection()) {
|
||||
case TO -> new EntityRelation(zoneId, ctx.getEntityId(), configuration.getZoneRelationType());
|
||||
case FROM -> new EntityRelation(ctx.getEntityId(), zoneId, configuration.getZoneRelationType());
|
||||
case TO -> new EntityRelation(zoneId, entityId, configuration.getZoneRelationType());
|
||||
case FROM -> new EntityRelation(entityId, zoneId, configuration.getZoneRelationType());
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -27,6 +27,7 @@ import org.thingsboard.script.api.tbel.TbelCfCtx;
|
||||
import org.thingsboard.script.api.tbel.TbelCfSingleValueArg;
|
||||
import org.thingsboard.server.common.data.cf.CalculatedFieldType;
|
||||
import org.thingsboard.server.common.data.cf.configuration.Output;
|
||||
import org.thingsboard.server.common.data.id.EntityId;
|
||||
import org.thingsboard.server.service.cf.CalculatedFieldResult;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@ -53,7 +54,7 @@ public class ScriptCalculatedFieldState extends BaseCalculatedFieldState {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListenableFuture<CalculatedFieldResult> performCalculation(CalculatedFieldCtx ctx) {
|
||||
public ListenableFuture<CalculatedFieldResult> performCalculation(EntityId entityId, CalculatedFieldCtx ctx) {
|
||||
Map<String, TbelCfArg> arguments = new LinkedHashMap<>();
|
||||
List<Object> args = new ArrayList<>(ctx.getArgNames().size() + 1);
|
||||
args.add(new Object()); // first element is a ctx, but we will set it later;
|
||||
|
||||
@ -25,6 +25,7 @@ import org.thingsboard.common.util.JacksonUtil;
|
||||
import org.thingsboard.script.api.tbel.TbUtils;
|
||||
import org.thingsboard.server.common.data.cf.CalculatedFieldType;
|
||||
import org.thingsboard.server.common.data.cf.configuration.Output;
|
||||
import org.thingsboard.server.common.data.id.EntityId;
|
||||
import org.thingsboard.server.common.data.kv.BasicKvEntry;
|
||||
import org.thingsboard.server.service.cf.CalculatedFieldResult;
|
||||
|
||||
@ -52,7 +53,7 @@ public class SimpleCalculatedFieldState extends BaseCalculatedFieldState {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListenableFuture<CalculatedFieldResult> performCalculation(CalculatedFieldCtx ctx) {
|
||||
public ListenableFuture<CalculatedFieldResult> performCalculation(EntityId entityId, CalculatedFieldCtx ctx) {
|
||||
var expr = ctx.getCustomExpression().get();
|
||||
|
||||
for (Map.Entry<String, ArgumentEntry> entry : this.arguments.entrySet()) {
|
||||
|
||||
@ -125,7 +125,7 @@ public class ScriptCalculatedFieldStateTest {
|
||||
void testPerformCalculation() throws ExecutionException, InterruptedException {
|
||||
state.arguments = new HashMap<>(Map.of("deviceTemperature", deviceTemperatureArgEntry, "assetHumidity", assetHumidityArgEntry));
|
||||
|
||||
CalculatedFieldResult result = state.performCalculation(ctx).get();
|
||||
CalculatedFieldResult result = state.performCalculation(ctx.getEntityId(), ctx).get();
|
||||
|
||||
assertThat(result).isNotNull();
|
||||
Output output = getCalculatedFieldConfig().getOutput();
|
||||
|
||||
@ -134,7 +134,7 @@ public class SimpleCalculatedFieldStateTest {
|
||||
"key3", key3ArgEntry
|
||||
));
|
||||
|
||||
CalculatedFieldResult result = state.performCalculation(ctx).get();
|
||||
CalculatedFieldResult result = state.performCalculation(ctx.getEntityId(), ctx).get();
|
||||
|
||||
assertThat(result).isNotNull();
|
||||
Output output = getCalculatedFieldConfig().getOutput();
|
||||
@ -151,7 +151,7 @@ public class SimpleCalculatedFieldStateTest {
|
||||
"key3", key3ArgEntry
|
||||
));
|
||||
|
||||
assertThatThrownBy(() -> state.performCalculation(ctx))
|
||||
assertThatThrownBy(() -> state.performCalculation(ctx.getEntityId(), ctx))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("Argument 'key2' is not a number.");
|
||||
}
|
||||
@ -164,7 +164,7 @@ public class SimpleCalculatedFieldStateTest {
|
||||
"key3", key3ArgEntry
|
||||
));
|
||||
|
||||
CalculatedFieldResult result = state.performCalculation(ctx).get();
|
||||
CalculatedFieldResult result = state.performCalculation(ctx.getEntityId(), ctx).get();
|
||||
|
||||
assertThat(result).isNotNull();
|
||||
Output output = getCalculatedFieldConfig().getOutput();
|
||||
@ -185,7 +185,7 @@ public class SimpleCalculatedFieldStateTest {
|
||||
output.setDecimalsByDefault(3);
|
||||
ctx.setOutput(output);
|
||||
|
||||
CalculatedFieldResult result = state.performCalculation(ctx).get();
|
||||
CalculatedFieldResult result = state.performCalculation(ctx.getEntityId(), ctx).get();
|
||||
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.getType()).isEqualTo(output.getType());
|
||||
|
||||
@ -41,7 +41,7 @@ public class GeofencingCalculatedFieldConfiguration extends BaseCalculatedFieldC
|
||||
ENTITY_ID_LONGITUDE_ARGUMENT_KEY
|
||||
);
|
||||
|
||||
private boolean trackRelationToZones;
|
||||
private boolean createRelationsWithMatchedZones;
|
||||
private String zoneRelationType;
|
||||
private EntitySearchDirection zoneRelationDirection;
|
||||
private Map<String, GeofencingZoneGroupConfiguration> geofencingZoneGroupConfigurations;
|
||||
@ -52,7 +52,6 @@ public class GeofencingCalculatedFieldConfiguration extends BaseCalculatedFieldC
|
||||
}
|
||||
|
||||
// TODO: update validate method in PE version.
|
||||
// Add relation tracking configuration validation
|
||||
@Override
|
||||
public void validate() {
|
||||
if (arguments == null) {
|
||||
@ -72,6 +71,19 @@ public class GeofencingCalculatedFieldConfiguration extends BaseCalculatedFieldC
|
||||
}
|
||||
validateZoneGroupAruguments(zoneGroupsArguments);
|
||||
validateZoneGroupConfigurations(zoneGroupsArguments);
|
||||
validateZoneRelationsConfiguration();
|
||||
}
|
||||
|
||||
private void validateZoneRelationsConfiguration() {
|
||||
if (!createRelationsWithMatchedZones) {
|
||||
return;
|
||||
}
|
||||
if (StringUtils.isBlank(zoneRelationType)) {
|
||||
throw new IllegalArgumentException("Zone relation type must be specified when to maintain relations with matched zones!");
|
||||
}
|
||||
if (zoneRelationDirection == null) {
|
||||
throw new IllegalArgumentException("Zone relation direction must be specified to maintain relations with matched zones!");
|
||||
}
|
||||
}
|
||||
|
||||
private void validateZoneGroupConfigurations(Map<String, Argument> zoneGroupsArguments) {
|
||||
@ -146,7 +158,7 @@ public class GeofencingCalculatedFieldConfiguration extends BaseCalculatedFieldC
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
}
|
||||
|
||||
private static ReferencedEntityKey validateAndGetRefEntityKey(Argument argument, String argumentKey) {
|
||||
private ReferencedEntityKey validateAndGetRefEntityKey(Argument argument, String argumentKey) {
|
||||
ReferencedEntityKey refEntityKey = argument.getRefEntityKey();
|
||||
if (refEntityKey == null || refEntityKey.getType() == null) {
|
||||
throw new IllegalArgumentException("Missing or invalid reference entity key for argument: " + argumentKey);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user