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