added CF config marker interface

This commit is contained in:
IrynaMatveieva 2025-08-26 11:38:01 +03:00
parent 46a9d81a85
commit 90d34b5bd0
13 changed files with 123 additions and 91 deletions

View File

@ -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.CalculatedFieldConfiguration; import org.thingsboard.server.common.data.cf.configuration.ArgumentsBasedCalculatedFieldConfiguration;
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,9 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
private void scheduleDynamicArgumentsRefreshTaskForCfIfNeeded(CalculatedFieldCtx cfCtx) { private void scheduleDynamicArgumentsRefreshTaskForCfIfNeeded(CalculatedFieldCtx cfCtx) {
CalculatedField cf = cfCtx.getCalculatedField(); CalculatedField cf = cfCtx.getCalculatedField();
CalculatedFieldConfiguration cfConfig = cf.getConfiguration(); if (!(cf.getConfiguration() instanceof ArgumentsBasedCalculatedFieldConfiguration cfConfig)) {
return;
}
if (!cfConfig.isScheduledUpdateEnabled()) { if (!cfConfig.isScheduledUpdateEnabled()) {
return; return;
} }

View File

@ -44,6 +44,7 @@ import org.thingsboard.script.api.tbel.TbelInvokeService;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EventInfo; import org.thingsboard.server.common.data.EventInfo;
import org.thingsboard.server.common.data.cf.CalculatedField; import org.thingsboard.server.common.data.cf.CalculatedField;
import org.thingsboard.server.common.data.cf.configuration.ArgumentsBasedCalculatedFieldConfiguration;
import org.thingsboard.server.common.data.cf.configuration.CalculatedFieldConfiguration; import org.thingsboard.server.common.data.cf.configuration.CalculatedFieldConfiguration;
import org.thingsboard.server.common.data.event.EventType; import org.thingsboard.server.common.data.event.EventType;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
@ -212,7 +213,8 @@ public class CalculatedFieldController extends BaseController {
@RequestBody JsonNode inputParams) { @RequestBody JsonNode inputParams) {
String expression = inputParams.get("expression").asText(); String expression = inputParams.get("expression").asText();
Map<String, TbelCfArg> arguments = Objects.requireNonNullElse( Map<String, TbelCfArg> arguments = Objects.requireNonNullElse(
JacksonUtil.convertValue(inputParams.get("arguments"), new TypeReference<>() {}), JacksonUtil.convertValue(inputParams.get("arguments"), new TypeReference<>() {
}),
Collections.emptyMap() Collections.emptyMap()
); );
@ -278,7 +280,8 @@ public class CalculatedFieldController extends BaseController {
} }
private void checkReferencedEntities(CalculatedFieldConfiguration calculatedFieldConfig) throws ThingsboardException { private void checkReferencedEntities(CalculatedFieldConfiguration calculatedFieldConfig) throws ThingsboardException {
List<EntityId> referencedEntityIds = calculatedFieldConfig.getReferencedEntities(); if (calculatedFieldConfig instanceof ArgumentsBasedCalculatedFieldConfiguration config) {
List<EntityId> referencedEntityIds = config.getReferencedEntities();
for (EntityId referencedEntityId : referencedEntityIds) { for (EntityId referencedEntityId : referencedEntityIds) {
EntityType entityType = referencedEntityId.getEntityType(); EntityType entityType = referencedEntityId.getEntityType();
switch (entityType) { switch (entityType) {
@ -286,10 +289,11 @@ public class CalculatedFieldController extends BaseController {
return; return;
} }
case CUSTOMER, ASSET, DEVICE -> checkEntityId(referencedEntityId, Operation.READ); case CUSTOMER, ASSET, DEVICE -> checkEntityId(referencedEntityId, Operation.READ);
default -> throw new IllegalArgumentException("Calculated fields do not support '" + entityType + "' for referenced entities."); default ->
throw new IllegalArgumentException("Calculated fields do not support '" + entityType + "' for referenced entities.");
}
} }
} }
} }
} }

View File

@ -25,7 +25,7 @@ import org.thingsboard.server.common.data.cf.CalculatedField;
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;
import org.thingsboard.server.common.data.cf.configuration.CalculatedFieldConfiguration; import org.thingsboard.server.common.data.cf.configuration.ArgumentsBasedCalculatedFieldConfiguration;
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.SimpleCalculatedFieldConfiguration; import org.thingsboard.server.common.data.cf.configuration.SimpleCalculatedFieldConfiguration;
@ -83,10 +83,12 @@ public class CalculatedFieldCtx {
this.tenantId = calculatedField.getTenantId(); this.tenantId = calculatedField.getTenantId();
this.entityId = calculatedField.getEntityId(); this.entityId = calculatedField.getEntityId();
this.cfType = calculatedField.getType(); this.cfType = calculatedField.getType();
CalculatedFieldConfiguration configuration = calculatedField.getConfiguration(); this.arguments = new HashMap<>();
this.arguments = configuration.getArguments();
this.mainEntityArguments = new HashMap<>(); this.mainEntityArguments = new HashMap<>();
this.linkedEntityArguments = new HashMap<>(); this.linkedEntityArguments = new HashMap<>();
this.argNames = new ArrayList<>();
if (calculatedField.getConfiguration() instanceof ArgumentsBasedCalculatedFieldConfiguration configuration) {
this.arguments.putAll(configuration.getArguments());
for (Map.Entry<String, Argument> entry : arguments.entrySet()) { for (Map.Entry<String, Argument> entry : arguments.entrySet()) {
var refId = entry.getValue().getRefEntityId(); var refId = entry.getValue().getRefEntityId();
var refKey = entry.getValue().getRefEntityKey(); var refKey = entry.getValue().getRefEntityKey();
@ -99,10 +101,11 @@ public class CalculatedFieldCtx {
linkedEntityArguments.computeIfAbsent(refId, key -> new HashMap<>()).put(refKey, entry.getKey()); linkedEntityArguments.computeIfAbsent(refId, key -> new HashMap<>()).put(refKey, entry.getKey());
} }
} }
this.argNames = new ArrayList<>(arguments.keySet()); this.argNames.addAll(arguments.keySet());
this.output = configuration.getOutput(); this.output = configuration.getOutput();
this.expression = configuration.getExpression(); this.expression = configuration.getExpression();
this.useLatestTs = CalculatedFieldType.SIMPLE.equals(calculatedField.getType()) && ((SimpleCalculatedFieldConfiguration) configuration).isUseLatestTs(); this.useLatestTs = CalculatedFieldType.SIMPLE.equals(calculatedField.getType()) && ((SimpleCalculatedFieldConfiguration) configuration).isUseLatestTs();
}
this.tbelInvokeService = tbelInvokeService; this.tbelInvokeService = tbelInvokeService;
this.relationService = relationService; this.relationService = relationService;
@ -316,12 +319,14 @@ public class CalculatedFieldCtx {
} }
public boolean hasSchedulingConfigChanges(CalculatedFieldCtx other) { public boolean hasSchedulingConfigChanges(CalculatedFieldCtx other) {
CalculatedFieldConfiguration thisConfig = calculatedField.getConfiguration(); if (calculatedField.getConfiguration() instanceof ArgumentsBasedCalculatedFieldConfiguration thisConfig
CalculatedFieldConfiguration otherConfig = other.calculatedField.getConfiguration(); && other.calculatedField.getConfiguration() instanceof ArgumentsBasedCalculatedFieldConfiguration 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;
} }
return false;
}
public String getSizeExceedsLimitMessage() { public String getSizeExceedsLimitMessage() {
return "Failed to init CF state. State size exceeds limit of " + (maxStateSize / 1024) + "Kb!"; return "Failed to init CF state. State size exceeds limit of " + (maxStateSize / 1024) + "Kb!";

View File

@ -24,6 +24,7 @@ import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.ExportableEntity; import org.thingsboard.server.common.data.ExportableEntity;
import org.thingsboard.server.common.data.HasVersion; import org.thingsboard.server.common.data.HasVersion;
import org.thingsboard.server.common.data.cf.CalculatedField; import org.thingsboard.server.common.data.cf.CalculatedField;
import org.thingsboard.server.common.data.cf.configuration.ArgumentsBasedCalculatedFieldConfiguration;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.EntityIdFactory;
@ -153,11 +154,13 @@ public class DefaultEntityExportService<I extends EntityId, E extends Exportable
List<CalculatedField> calculatedFields = calculatedFieldService.findCalculatedFieldsByEntityId(ctx.getTenantId(), entityId); List<CalculatedField> calculatedFields = calculatedFieldService.findCalculatedFieldsByEntityId(ctx.getTenantId(), entityId);
calculatedFields.forEach(calculatedField -> { calculatedFields.forEach(calculatedField -> {
calculatedField.setEntityId(getExternalIdOrElseInternal(ctx, entityId)); calculatedField.setEntityId(getExternalIdOrElseInternal(ctx, entityId));
calculatedField.getConfiguration().getArguments().values().forEach(argument -> { if (calculatedField.getConfiguration() instanceof ArgumentsBasedCalculatedFieldConfiguration configuration) {
configuration.getArguments().values().forEach(argument -> {
if (argument.getRefEntityId() != null) { if (argument.getRefEntityId() != null) {
argument.setRefEntityId(getExternalIdOrElseInternal(ctx, argument.getRefEntityId())); argument.setRefEntityId(getExternalIdOrElseInternal(ctx, argument.getRefEntityId()));
} }
}); });
}
}); });
return calculatedFields; return calculatedFields;
} }

View File

@ -35,6 +35,7 @@ import org.thingsboard.server.common.data.HasVersion;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.cf.CalculatedField; import org.thingsboard.server.common.data.cf.CalculatedField;
import org.thingsboard.server.common.data.cf.configuration.ArgumentsBasedCalculatedFieldConfiguration;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.EntityIdFactory;
@ -321,11 +322,13 @@ public abstract class BaseEntityImportService<I extends EntityId, E extends Expo
.peek(calculatedField -> { .peek(calculatedField -> {
calculatedField.setTenantId(ctx.getTenantId()); calculatedField.setTenantId(ctx.getTenantId());
calculatedField.setEntityId(savedEntity.getId()); calculatedField.setEntityId(savedEntity.getId());
calculatedField.getConfiguration().getArguments().values().forEach(argument -> { if (calculatedField.getConfiguration() instanceof ArgumentsBasedCalculatedFieldConfiguration configuration) {
configuration.getArguments().values().forEach(argument -> {
if (argument.getRefEntityId() != null) { if (argument.getRefEntityId() != null) {
argument.setRefEntityId(idProvider.getInternalId(argument.getRefEntityId(), ctx.isFinalImportAttempt())); argument.setRefEntityId(idProvider.getInternalId(argument.getRefEntityId(), ctx.isFinalImportAttempt()));
} }
}); });
}
}).toList(); }).toList();
for (CalculatedField existingField : existing) { for (CalculatedField existingField : existing) {

View File

@ -0,0 +1,31 @@
package org.thingsboard.server.common.data.cf.configuration;
import com.fasterxml.jackson.annotation.JsonIgnore;
import java.util.Map;
public interface ArgumentsBasedCalculatedFieldConfiguration extends CalculatedFieldConfiguration {
Map<String, Argument> getArguments();
String getExpression();
void setExpression(String expression);
Output getOutput();
void validate();
@JsonIgnore
default boolean isScheduledUpdateEnabled() {
return false;
}
default void setScheduledUpdateIntervalSec(int scheduledUpdateIntervalSec) {
}
default int getScheduledUpdateIntervalSec() {
return 0;
}
}

View File

@ -27,7 +27,7 @@ import java.util.Objects;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Data @Data
public abstract class BaseCalculatedFieldConfiguration implements CalculatedFieldConfiguration { public abstract class BaseCalculatedFieldConfiguration implements ArgumentsBasedCalculatedFieldConfiguration {
protected Map<String, Argument> arguments; protected Map<String, Argument> arguments;
protected String expression; protected String expression;

View File

@ -25,8 +25,8 @@ import org.thingsboard.server.common.data.id.CalculatedFieldId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map;
@JsonTypeInfo( @JsonTypeInfo(
use = JsonTypeInfo.Id.NAME, use = JsonTypeInfo.Id.NAME,
@ -44,33 +44,13 @@ public interface CalculatedFieldConfiguration {
@JsonIgnore @JsonIgnore
CalculatedFieldType getType(); CalculatedFieldType getType();
Map<String, Argument> getArguments();
String getExpression();
void setExpression(String expression);
Output getOutput();
@JsonIgnore @JsonIgnore
List<EntityId> getReferencedEntities(); default List<EntityId> getReferencedEntities() {
return Collections.emptyList();
List<CalculatedFieldLink> buildCalculatedFieldLinks(TenantId tenantId, EntityId cfEntityId, CalculatedFieldId calculatedFieldId); }
CalculatedFieldLink buildCalculatedFieldLink(TenantId tenantId, EntityId referencedEntityId, CalculatedFieldId calculatedFieldId); CalculatedFieldLink buildCalculatedFieldLink(TenantId tenantId, EntityId referencedEntityId, CalculatedFieldId calculatedFieldId);
void validate(); List<CalculatedFieldLink> buildCalculatedFieldLinks(TenantId tenantId, EntityId cfEntityId, CalculatedFieldId calculatedFieldId);
@JsonIgnore
default boolean isScheduledUpdateEnabled() {
return false;
}
default void setScheduledUpdateIntervalSec(int scheduledUpdateIntervalSec) {
}
default int getScheduledUpdateIntervalSec() {
return 0;
}
} }

View File

@ -27,7 +27,7 @@ import java.util.stream.Collectors;
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class GeofencingCalculatedFieldConfiguration extends BaseCalculatedFieldConfiguration implements CalculatedFieldConfiguration { public class GeofencingCalculatedFieldConfiguration extends BaseCalculatedFieldConfiguration implements ArgumentsBasedCalculatedFieldConfiguration {
public static final String ENTITY_ID_LATITUDE_ARGUMENT_KEY = "latitude"; public static final String ENTITY_ID_LATITUDE_ARGUMENT_KEY = "latitude";
public static final String ENTITY_ID_LONGITUDE_ARGUMENT_KEY = "longitude"; public static final String ENTITY_ID_LONGITUDE_ARGUMENT_KEY = "longitude";

View File

@ -21,7 +21,7 @@ import org.thingsboard.server.common.data.cf.CalculatedFieldType;
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class ScriptCalculatedFieldConfiguration extends BaseCalculatedFieldConfiguration implements CalculatedFieldConfiguration { public class ScriptCalculatedFieldConfiguration extends BaseCalculatedFieldConfiguration implements ArgumentsBasedCalculatedFieldConfiguration {
@Override @Override
public CalculatedFieldType getType() { public CalculatedFieldType getType() {

View File

@ -21,7 +21,7 @@ import org.thingsboard.server.common.data.cf.CalculatedFieldType;
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class SimpleCalculatedFieldConfiguration extends BaseCalculatedFieldConfiguration implements CalculatedFieldConfiguration { public class SimpleCalculatedFieldConfiguration extends BaseCalculatedFieldConfiguration implements ArgumentsBasedCalculatedFieldConfiguration {
private boolean useLatestTs; private boolean useLatestTs;

View File

@ -21,6 +21,7 @@ import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.cf.CalculatedField; import org.thingsboard.server.common.data.cf.CalculatedField;
import org.thingsboard.server.common.data.cf.CalculatedFieldLink; import org.thingsboard.server.common.data.cf.CalculatedFieldLink;
import org.thingsboard.server.common.data.cf.configuration.ArgumentsBasedCalculatedFieldConfiguration;
import org.thingsboard.server.common.data.cf.configuration.CalculatedFieldConfiguration; import org.thingsboard.server.common.data.cf.configuration.CalculatedFieldConfiguration;
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;
@ -93,7 +94,7 @@ public class BaseCalculatedFieldService extends AbstractEntityService implements
} }
private void updatedSchedulingConfiguration(CalculatedField calculatedField) { private void updatedSchedulingConfiguration(CalculatedField calculatedField) {
CalculatedFieldConfiguration configuration = calculatedField.getConfiguration(); if (calculatedField.getConfiguration() instanceof ArgumentsBasedCalculatedFieldConfiguration configuration) {
if (!configuration.isScheduledUpdateEnabled()) { if (!configuration.isScheduledUpdateEnabled()) {
return; return;
} }
@ -102,6 +103,7 @@ public class BaseCalculatedFieldService extends AbstractEntityService implements
.getMinAllowedScheduledUpdateIntervalInSecForCF(); .getMinAllowedScheduledUpdateIntervalInSecForCF();
configuration.setScheduledUpdateIntervalSec(Math.max(configuration.getScheduledUpdateIntervalSec(), tenantProfileMinAllowedValue)); configuration.setScheduledUpdateIntervalSec(Math.max(configuration.getScheduledUpdateIntervalSec(), tenantProfileMinAllowedValue));
} }
}
@Override @Override
public CalculatedField findById(TenantId tenantId, CalculatedFieldId calculatedFieldId) { public CalculatedField findById(TenantId tenantId, CalculatedFieldId calculatedFieldId) {

View File

@ -19,7 +19,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.cf.CalculatedField; import org.thingsboard.server.common.data.cf.CalculatedField;
import org.thingsboard.server.common.data.cf.CalculatedFieldType; import org.thingsboard.server.common.data.cf.CalculatedFieldType;
import org.thingsboard.server.common.data.cf.configuration.CalculatedFieldConfiguration; import org.thingsboard.server.common.data.cf.configuration.ArgumentsBasedCalculatedFieldConfiguration;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
@ -75,13 +75,14 @@ public class CalculatedFieldDataValidator extends DataValidator<CalculatedField>
maxArgumentsPerCF + ". Contact your administrator to increase the limit." maxArgumentsPerCF + ". Contact your administrator to increase the limit."
); );
} }
if (calculatedField.getConfiguration().getArguments().size() > maxArgumentsPerCF) { 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!");
} }
} }
private void validateCalculatedFieldConfiguration(CalculatedField calculatedField) { private void validateCalculatedFieldConfiguration(CalculatedField calculatedField) {
CalculatedFieldConfiguration configuration = calculatedField.getConfiguration(); if (calculatedField.getConfiguration() instanceof ArgumentsBasedCalculatedFieldConfiguration configuration) {
if (configuration.getArguments().containsKey("ctx")) { if (configuration.getArguments().containsKey("ctx")) {
throw new DataValidationException("Argument name 'ctx' is reserved and cannot be used."); throw new DataValidationException("Argument name 'ctx' is reserved and cannot be used.");
} }
@ -91,5 +92,6 @@ public class CalculatedFieldDataValidator extends DataValidator<CalculatedField>
throw new DataValidationException(e.getMessage(), e); throw new DataValidationException(e.getMessage(), e);
} }
} }
}
} }