added logic to handle cf update msg

This commit is contained in:
IrynaMatveieva 2024-11-14 17:19:14 +02:00
parent 8079ef66ef
commit 9ad8d35245
15 changed files with 160 additions and 52 deletions

View File

@ -15,17 +15,25 @@
*/
package org.thingsboard.server.service.entitiy.cf;
import lombok.Builder;
import lombok.Data;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.thingsboard.server.common.data.cf.CalculatedFieldConfiguration;
import org.thingsboard.server.common.data.cf.CalculatedFieldType;
import java.util.Map;
@Data
@Builder
public class CalculatedFieldState {
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type"
)
@JsonSubTypes({
@JsonSubTypes.Type(value = SimpleCalculatedFieldState.class, name = "SIMPLE")
})
public interface CalculatedFieldState {
// TODO: use value object(TsKv) instead of string
Map<String, String> arguments;
String result;
CalculatedFieldType getType();
void performCalculation(Map<String, String> argumentValues, CalculatedFieldConfiguration calculatedFieldConfiguration, boolean initialCalculation);
}

View File

@ -40,6 +40,7 @@ import org.thingsboard.server.common.data.cf.BaseCalculatedFieldConfiguration;
import org.thingsboard.server.common.data.cf.CalculatedField;
import org.thingsboard.server.common.data.cf.CalculatedFieldConfiguration;
import org.thingsboard.server.common.data.cf.CalculatedFieldLink;
import org.thingsboard.server.common.data.cf.CalculatedFieldType;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.AssetProfileId;
@ -141,12 +142,11 @@ public class DefaultTbCalculatedFieldService extends AbstractTbEntityService imp
onCalculatedFieldDelete(calculatedFieldId, callback);
callback.onSuccess();
}
CalculatedField cf = calculatedFieldService.findById(tenantId, calculatedFieldId);
if (proto.getUpdated()) {
log.info("Executing onCalculatedFieldUpdate, calculatedFieldId=[{}]", calculatedFieldId);
//TODO: improve the check. Maybe it was renamed or just the name of the result changed.
onCalculatedFieldDelete(calculatedFieldId, callback);
onCalculatedFieldUpdate(cf, callback);
}
CalculatedField cf = calculatedFieldService.findById(tenantId, calculatedFieldId);
List<CalculatedFieldLink> links = calculatedFieldService.findAllCalculatedFieldLinksById(tenantId, calculatedFieldId);
if (cf != null) {
EntityId entityId = cf.getEntityId();
@ -235,6 +235,31 @@ public class DefaultTbCalculatedFieldService extends AbstractTbEntityService imp
}
}
private void onCalculatedFieldUpdate(CalculatedField newCalculatedField, TbCallback callback) {
CalculatedField oldCalculatedField = calculatedFields.get(newCalculatedField.getId());
if (hasSignificantChanged(oldCalculatedField, newCalculatedField)) {
onCalculatedFieldDelete(newCalculatedField.getId(), callback);
} else {
calculatedFields.put(newCalculatedField.getId(), newCalculatedField);
callback.onSuccess();
}
}
private boolean hasSignificantChanged(CalculatedField oldCalculatedField, CalculatedField newCalculatedField) {
if (oldCalculatedField == null) {
return true;
}
boolean entityIdChanged = !oldCalculatedField.getEntityId().equals(newCalculatedField.getEntityId());
boolean typeChanged = !oldCalculatedField.getType().equals(newCalculatedField.getType());
CalculatedFieldConfiguration oldConfig = oldCalculatedField.getConfiguration();
CalculatedFieldConfiguration newConfig = newCalculatedField.getConfiguration();
boolean argumentsChanged = !oldConfig.getArguments().equals(newConfig.getArguments());
boolean outputTypeChanged = !oldConfig.getOutput().getType().equals(newConfig.getOutput().getType());
boolean outputNameChanged = !oldConfig.getOutput().getName().equals(newConfig.getOutput().getName());
return entityIdChanged || typeChanged || argumentsChanged || outputTypeChanged || outputNameChanged;
}
private void fetchCalculatedFields() {
PageDataIterable<CalculatedField> cfs = new PageDataIterable<>(calculatedFieldService::findAllCalculatedFields, initFetchPackSize);
cfs.forEach(cf -> calculatedFields.putIfAbsent(cf.getId(), cf));
@ -309,39 +334,33 @@ public class DefaultTbCalculatedFieldService extends AbstractTbEntityService imp
}
private void updateOrInitializeState(CalculatedField calculatedField, EntityId entityId, Map<String, String> argumentValues) {
String ctxId = calculatedField.getId().getId() + "_" + entityId.getId();
String ctxId = generateCtxId(calculatedField.getId(), entityId);
CalculatedFieldCtx calculatedFieldCtx = states.computeIfAbsent(ctxId,
ctx -> new CalculatedFieldCtx(calculatedField.getId(), calculatedField.getEntityId(), null));
CalculatedFieldState state = calculatedFieldCtx.getState();
if (state != null) {
// calculation based on the previous data
String calculation = performCalculation(state.getArguments(), calculatedField.getConfiguration());
Map<String, String> updatedArguments = new HashMap<>(state.getArguments());
state = CalculatedFieldState.builder()
.arguments(updatedArguments)
.result(calculation)
.build();
state.performCalculation(argumentValues, calculatedField.getConfiguration(), false);
} else {
// initial calculation
String calculation = performCalculation(argumentValues, calculatedField.getConfiguration());
state = CalculatedFieldState.builder()
.arguments(argumentValues)
.result(calculation)
.build();
CalculatedFieldState newState = createStateByType(calculatedField.getType());
newState.performCalculation(argumentValues, calculatedField.getConfiguration(), true);
}
calculatedFieldCtx.setState(state);
states.put(ctxId, calculatedFieldCtx);
rocksDBService.put(ctxId, Objects.requireNonNull(JacksonUtil.writeValueAsString(calculatedFieldCtx)));
}
private String performCalculation(Map<String, String> argumentValues, CalculatedFieldConfiguration calculatedFieldConfiguration) {
BaseCalculatedFieldConfiguration.Output output = calculatedFieldConfiguration.getOutput();
return "calculation";
private CalculatedFieldState createStateByType(CalculatedFieldType calculatedFieldType) {
return switch (calculatedFieldType) {
case SIMPLE -> new SimpleCalculatedFieldState();
default ->
throw new IllegalArgumentException("Invalid calculated field type '" + calculatedFieldType + "'.");
};
}
private String generateCtxId(CalculatedFieldId calculatedFieldId, EntityId entityId) {
return calculatedFieldId.getId() + "_" + entityId.getId();
}
}

View File

@ -0,0 +1,49 @@
/**
* Copyright © 2016-2024 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.service.entitiy.cf;
import lombok.Data;
import org.thingsboard.server.common.data.cf.CalculatedFieldConfiguration;
import org.thingsboard.server.common.data.cf.CalculatedFieldType;
import java.util.HashMap;
import java.util.Map;
@Data
public class SimpleCalculatedFieldState implements CalculatedFieldState {
// TODO: use value object(TsKv) instead of string
Map<String, String> arguments = new HashMap<>();
String result;
@Override
public CalculatedFieldType getType() {
return CalculatedFieldType.SIMPLE;
}
@Override
public void performCalculation(Map<String, String> argumentValues, CalculatedFieldConfiguration calculatedFieldConfiguration, boolean initialCalculation) {
if (initialCalculation) {
// todo: perform initial calculation
this.arguments = argumentValues;
} else {
// todo: perform calculation based on previous data
this.arguments.putAll(argumentValues);
}
this.result = "result";
}
}

View File

@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.cf.CalculatedField;
import org.thingsboard.server.common.data.cf.CalculatedFieldConfiguration;
import org.thingsboard.server.common.data.cf.CalculatedFieldType;
import org.thingsboard.server.common.data.cf.SimpleCalculatedFieldConfiguration;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
@ -124,7 +125,7 @@ public class CalculatedFieldControllerTest extends AbstractControllerTest {
private CalculatedField getCalculatedField(DeviceId deviceId) {
CalculatedField calculatedField = new CalculatedField();
calculatedField.setEntityId(deviceId);
calculatedField.setType("SIMPLE");
calculatedField.setType(CalculatedFieldType.SIMPLE);
calculatedField.setName("Test Calculated Field");
calculatedField.setConfigurationVersion(1);
calculatedField.setConfiguration(getCalculatedFieldConfig(null));

View File

@ -115,6 +115,7 @@ public abstract class BaseCalculatedFieldConfiguration implements CalculatedFiel
@Data
public static class Output {
private String name;
private String type;
private String expression;
}

View File

@ -20,15 +20,17 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import org.thingsboard.server.common.data.*;
import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.ExportableEntity;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.HasVersion;
import org.thingsboard.server.common.data.id.CalculatedFieldId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.validation.Length;
import org.thingsboard.server.common.data.validation.NoXss;
import java.io.Serializable;
@Schema
@Data
@EqualsAndHashCode(callSuper = true)
@ -41,7 +43,7 @@ public class CalculatedField extends BaseData<CalculatedFieldId> implements HasN
@NoXss
@Length(fieldName = "type")
private String type;
private CalculatedFieldType type;
@NoXss
@Length(fieldName = "name")
@Schema(description = "User defined name of the calculated field.")
@ -65,7 +67,7 @@ public class CalculatedField extends BaseData<CalculatedFieldId> implements HasN
super(id);
}
public CalculatedField(TenantId tenantId, EntityId entityId, String type, String name, int configurationVersion, CalculatedFieldConfiguration configuration, Long version, CalculatedFieldId externalId) {
public CalculatedField(TenantId tenantId, EntityId entityId, CalculatedFieldType type, String name, int configurationVersion, CalculatedFieldConfiguration configuration, Long version, CalculatedFieldId externalId) {
this.tenantId = tenantId;
this.entityId = entityId;
this.type = type;

View File

@ -37,7 +37,7 @@ import java.util.UUID;
public interface CalculatedFieldConfiguration {
@JsonIgnore
String getType();
CalculatedFieldType getType();
Map<String, BaseCalculatedFieldConfiguration.Argument> getArguments();

View File

@ -0,0 +1,22 @@
/**
* Copyright © 2016-2024 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.common.data.cf;
public enum CalculatedFieldType {
SIMPLE, SCRIPT
}

View File

@ -33,7 +33,7 @@ public class SimpleCalculatedFieldConfiguration extends BaseCalculatedFieldConfi
}
@Override
public String getType() {
return "SIMPLE";
public CalculatedFieldType getType() {
return CalculatedFieldType.SIMPLE;
}
}

View File

@ -25,6 +25,7 @@ import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.cf.CalculatedField;
import org.thingsboard.server.common.data.cf.CalculatedFieldConfiguration;
import org.thingsboard.server.common.data.cf.CalculatedFieldType;
import org.thingsboard.server.common.data.cf.SimpleCalculatedFieldConfiguration;
import org.thingsboard.server.common.data.id.CalculatedFieldId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
@ -90,7 +91,7 @@ public class CalculatedFieldEntity extends BaseSqlEntity<CalculatedField> implem
this.tenantId = calculatedField.getTenantId().getId();
this.entityType = calculatedField.getEntityId().getEntityType().name();
this.entityId = calculatedField.getEntityId().getId();
this.type = calculatedField.getType();
this.type = calculatedField.getType().name();
this.name = calculatedField.getName();
this.configurationVersion = calculatedField.getConfigurationVersion();
this.configuration = calculatedField.getConfiguration().calculatedFieldConfigToJson(EntityType.valueOf(entityType), entityId);
@ -106,7 +107,7 @@ public class CalculatedFieldEntity extends BaseSqlEntity<CalculatedField> implem
calculatedField.setCreatedTime(createdTime);
calculatedField.setTenantId(TenantId.fromUUID(tenantId));
calculatedField.setEntityId(EntityIdFactory.getByTypeAndUuid(entityType, entityId));
calculatedField.setType(type);
calculatedField.setType(CalculatedFieldType.valueOf(type));
calculatedField.setName(name);
calculatedField.setConfigurationVersion(configurationVersion);
calculatedField.setConfiguration(readCalculatedFieldConfiguration(configuration, EntityType.valueOf(entityType), entityId));
@ -118,8 +119,8 @@ public class CalculatedFieldEntity extends BaseSqlEntity<CalculatedField> implem
}
private CalculatedFieldConfiguration readCalculatedFieldConfiguration(JsonNode config, EntityType entityType, UUID entityId) {
switch (type) {
case "SIMPLE":
switch (CalculatedFieldType.valueOf(type)) {
case SIMPLE:
return new SimpleCalculatedFieldConfiguration(config, entityType, entityId);
default:
throw new IllegalArgumentException("Unsupported calculated field type: " + type + "!");

View File

@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.cf.CalculatedField;
import org.thingsboard.server.common.data.cf.CalculatedFieldConfiguration;
import org.thingsboard.server.common.data.cf.CalculatedFieldLink;
import org.thingsboard.server.common.data.cf.CalculatedFieldLinkConfiguration;
import org.thingsboard.server.common.data.cf.CalculatedFieldType;
import org.thingsboard.server.common.data.cf.SimpleCalculatedFieldConfiguration;
import org.thingsboard.server.common.data.id.CalculatedFieldId;
import org.thingsboard.server.common.data.id.CalculatedFieldLinkId;
@ -73,7 +74,7 @@ public class DefaultNativeCalculatedFieldRepository implements NativeCalculatedF
UUID tenantId = (UUID) row.get("tenant_id");
EntityType entityType = EntityType.valueOf((String) row.get("entity_type"));
UUID entityId = (UUID) row.get("entity_id");
String type = (String) row.get("type");
CalculatedFieldType type = CalculatedFieldType.valueOf((String) row.get("type"));
String name = (String) row.get("name");
int configurationVersion = (int) row.get("configuration_version");
JsonNode configuration = JacksonUtil.toJsonNode((String) row.get("configuration"));
@ -133,9 +134,9 @@ public class DefaultNativeCalculatedFieldRepository implements NativeCalculatedF
});
}
private CalculatedFieldConfiguration readCalculatedFieldConfiguration(String type, JsonNode config, EntityType entityType, UUID entityId) {
private CalculatedFieldConfiguration readCalculatedFieldConfiguration(CalculatedFieldType type, JsonNode config, EntityType entityType, UUID entityId) {
switch (type) {
case "SIMPLE":
case SIMPLE:
return new SimpleCalculatedFieldConfiguration(config, entityType, entityId);
default:
throw new IllegalArgumentException("Unsupported calculated field type: " + type + "!");

View File

@ -31,6 +31,7 @@ import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetInfo;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.cf.CalculatedField;
import org.thingsboard.server.common.data.cf.CalculatedFieldType;
import org.thingsboard.server.common.data.cf.SimpleCalculatedFieldConfiguration;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
@ -874,7 +875,7 @@ public class AssetServiceTest extends AbstractServiceTest {
CalculatedField calculatedField = new CalculatedField();
calculatedField.setTenantId(tenantId);
calculatedField.setName("Test CF");
calculatedField.setType("SIMPLE");
calculatedField.setType(CalculatedFieldType.SIMPLE);
calculatedField.setEntityId(savedAssetWithCf.getId());
SimpleCalculatedFieldConfiguration config = new SimpleCalculatedFieldConfiguration();

View File

@ -25,6 +25,7 @@ import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.cf.CalculatedField;
import org.thingsboard.server.common.data.cf.CalculatedFieldConfiguration;
import org.thingsboard.server.common.data.cf.CalculatedFieldType;
import org.thingsboard.server.common.data.cf.SimpleCalculatedFieldConfiguration;
import org.thingsboard.server.common.data.id.CalculatedFieldId;
import org.thingsboard.server.common.data.id.EntityId;
@ -137,7 +138,7 @@ public class CalculatedFieldServiceTest extends AbstractServiceTest {
CalculatedField calculatedField = new CalculatedField();
calculatedField.setTenantId(tenantId);
calculatedField.setEntityId(entityId);
calculatedField.setType("SIMPLE");
calculatedField.setType(CalculatedFieldType.SIMPLE);
calculatedField.setName("Test Calculated Field");
calculatedField.setConfigurationVersion(1);
calculatedField.setConfiguration(getCalculatedFieldConfig(referencedEntityId));

View File

@ -32,6 +32,7 @@ import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.cf.CalculatedField;
import org.thingsboard.server.common.data.cf.CalculatedFieldType;
import org.thingsboard.server.common.data.cf.SimpleCalculatedFieldConfiguration;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
@ -369,7 +370,7 @@ public class CustomerServiceTest extends AbstractServiceTest {
CalculatedField calculatedField = new CalculatedField();
calculatedField.setTenantId(tenantId);
calculatedField.setName("Test CF");
calculatedField.setType("SIMPLE");
calculatedField.setType(CalculatedFieldType.SIMPLE);
calculatedField.setEntityId(savedAsset.getId());
SimpleCalculatedFieldConfiguration config = new SimpleCalculatedFieldConfiguration();

View File

@ -40,6 +40,7 @@ import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.cf.CalculatedField;
import org.thingsboard.server.common.data.cf.CalculatedFieldType;
import org.thingsboard.server.common.data.cf.SimpleCalculatedFieldConfiguration;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
@ -1212,7 +1213,7 @@ public class DeviceServiceTest extends AbstractServiceTest {
CalculatedField calculatedField = new CalculatedField();
calculatedField.setTenantId(tenantId);
calculatedField.setName("Test CF");
calculatedField.setType("SIMPLE");
calculatedField.setType(CalculatedFieldType.SIMPLE);
calculatedField.setEntityId(deviceWithCf.getId());
SimpleCalculatedFieldConfiguration config = new SimpleCalculatedFieldConfiguration();