Browse Source

added CF config and converter for it

pull/12009/head
IrynaMatveieva 2 years ago
parent
commit
889d6ec41b
  1. 27
      application/src/main/java/org/thingsboard/server/controller/CalculatedFieldController.java
  2. 38
      application/src/test/java/org/thingsboard/server/controller/CalculatedFieldControllerTest.java
  3. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/calculated_field/CalculatedFieldService.java
  4. 7
      common/data/src/main/java/org/thingsboard/server/common/data/calculated_field/CalculatedField.java
  5. 43
      common/data/src/main/java/org/thingsboard/server/common/data/calculated_field/CalculatedFieldConfig.java
  6. 8
      common/data/src/main/java/org/thingsboard/server/common/data/calculated_field/CalculatedFieldLink.java
  7. 4
      dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java
  8. 35
      dao/src/main/java/org/thingsboard/server/dao/calculated_field/BaseCalculatedFieldService.java
  9. 117
      dao/src/main/java/org/thingsboard/server/dao/calculated_field/CalculatedFieldConfigUtil.java
  10. 2
      dao/src/main/java/org/thingsboard/server/dao/calculated_field/CalculatedFieldDao.java
  11. 4
      dao/src/main/java/org/thingsboard/server/dao/calculated_field/CalculatedFieldLinkDao.java
  12. 4
      dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java
  13. 4
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java
  14. 6
      dao/src/main/java/org/thingsboard/server/dao/model/sql/CalculatedFieldEntity.java
  15. 6
      dao/src/main/java/org/thingsboard/server/dao/model/sql/CalculatedFieldLinkEntity.java
  16. 2
      dao/src/main/java/org/thingsboard/server/dao/sql/calculated_field/CalculatedFieldLinkRepository.java
  17. 2
      dao/src/main/java/org/thingsboard/server/dao/sql/calculated_field/CalculatedFieldRepository.java
  18. 5
      dao/src/main/java/org/thingsboard/server/dao/sql/calculated_field/JpaCalculatedFieldDao.java
  19. 6
      dao/src/main/java/org/thingsboard/server/dao/sql/calculated_field/JpaCalculatedFieldLinkDao.java
  20. 40
      dao/src/test/java/org/thingsboard/server/dao/service/AssetServiceTest.java
  21. 52
      dao/src/test/java/org/thingsboard/server/dao/service/CalculatedFieldServiceTest.java
  22. 57
      dao/src/test/java/org/thingsboard/server/dao/service/CustomerServiceTest.java
  23. 34
      dao/src/test/java/org/thingsboard/server/dao/service/DeviceServiceTest.java

27
application/src/main/java/org/thingsboard/server/controller/CalculatedFieldController.java

@ -27,14 +27,24 @@ import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.calculated_field.CalculatedField;
import org.thingsboard.server.common.data.calculated_field.CalculatedFieldConfig;
import org.thingsboard.server.common.data.exception.ThingsboardException;
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.config.annotations.ApiOperation;
import org.thingsboard.server.dao.calculated_field.CalculatedFieldService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
import java.util.EnumSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import static org.thingsboard.server.controller.ControllerConstants.TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH;
import static org.thingsboard.server.controller.ControllerConstants.UUID_WIKI_LINK;
@ -46,6 +56,9 @@ import static org.thingsboard.server.controller.ControllerConstants.UUID_WIKI_LI
@Slf4j
public class CalculatedFieldController extends BaseController {
private static final Set<EntityType> supportedEntityTypesForReferencedEntities = EnumSet.of(
EntityType.TENANT, EntityType.CUSTOMER, EntityType.ASSET, EntityType.DEVICE);
private final CalculatedFieldService calculatedFieldService;
public static final String CALCULATED_FIELD_ID = "calculatedFieldId";
@ -64,6 +77,7 @@ public class CalculatedFieldController extends BaseController {
@RequestBody CalculatedField calculatedField) throws Exception {
calculatedField.setTenantId(getTenantId());
checkEntityId(calculatedField.getEntityId(), Operation.WRITE_CALCULATED_FIELD);
checkReferencedEntities(calculatedField.getConfiguration());
return calculatedFieldService.save(calculatedField);
}
@ -97,4 +111,17 @@ public class CalculatedFieldController extends BaseController {
calculatedFieldService.deleteCalculatedField(getTenantId(), calculatedFieldId);
}
private void checkReferencedEntities(CalculatedFieldConfig calculatedFieldConfig) throws ThingsboardException {
List<EntityId> referencedEntityIds = calculatedFieldConfig.getArguments().values().stream()
.map(CalculatedFieldConfig.Argument::getEntityId)
.filter(Objects::nonNull)
.toList();
for (EntityId referencedEntityId : referencedEntityIds) {
if (!supportedEntityTypesForReferencedEntities.contains(referencedEntityId.getEntityType())) {
throw new IllegalArgumentException("Calculated fields do not support entity type '" + referencedEntityId.getEntityType() + "' for referenced entities.");
}
checkEntityId(referencedEntityId, Operation.READ);
}
}
}

38
application/src/test/java/org/thingsboard/server/controller/CalculatedFieldControllerTest.java

@ -18,15 +18,18 @@ package org.thingsboard.server.controller;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.calculated_field.CalculatedField;
import org.thingsboard.server.common.data.calculated_field.CalculatedFieldConfig;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.service.DaoSqlTest;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ -75,7 +78,7 @@ public class CalculatedFieldControllerTest extends AbstractControllerTest {
assertThat(savedCalculatedField.getEntityId()).isEqualTo(calculatedField.getEntityId());
assertThat(savedCalculatedField.getType()).isEqualTo(calculatedField.getType());
assertThat(savedCalculatedField.getName()).isEqualTo(calculatedField.getName());
assertThat(savedCalculatedField.getConfiguration()).isEqualTo(calculatedField.getConfiguration());
assertThat(savedCalculatedField.getConfiguration()).isEqualTo(getCalculatedFieldConfig(testDevice.getId()));
assertThat(savedCalculatedField.getVersion()).isEqualTo(calculatedField.getVersion());
savedCalculatedField.setName("Test CF");
@ -123,19 +126,28 @@ public class CalculatedFieldControllerTest extends AbstractControllerTest {
calculatedField.setType("Simple");
calculatedField.setName("Test Calculated Field");
calculatedField.setConfigurationVersion(1);
calculatedField.setConfiguration(JacksonUtil.toJsonNode("{\n" +
" \"T\": {\n" +
" \"key\": \"temperature\",\n" +
" \"type\": \"TIME_SERIES\"\n" +
" },\n" +
" \"H\": {\n" +
" \"key\": \"humidity\",\n" +
" \"type\": \"TIME_SERIES\",\n" +
" \"defaultValue\": 50\n" +
" }\n" +
" }\n"));
calculatedField.setConfiguration(getCalculatedFieldConfig(null));
calculatedField.setVersion(1L);
return calculatedField;
}
private CalculatedFieldConfig getCalculatedFieldConfig(EntityId referencedEntityId) {
CalculatedFieldConfig config = new CalculatedFieldConfig();
CalculatedFieldConfig.Argument argument = new CalculatedFieldConfig.Argument();
argument.setEntityId(referencedEntityId);
argument.setType("TIME_SERIES");
argument.setKey("temperature");
config.setArguments(Map.of("T", argument));
CalculatedFieldConfig.Output output = new CalculatedFieldConfig.Output();
output.setType("TIME_SERIES");
output.setExpression("T - (100 - H) / 5");
config.setOutput(output);
return config;
}
}

2
common/dao-api/src/main/java/org/thingsboard/server/dao/calculated_field/CalculatedFieldService.java

@ -36,4 +36,6 @@ public interface CalculatedFieldService extends EntityDaoService {
boolean existsByEntityId(TenantId tenantId, EntityId entityId);
boolean referencedInAnyCalculatedField(TenantId tenantId, EntityId referencedEntityId);
}

7
common/data/src/main/java/org/thingsboard/server/common/data/calculated_field/CalculatedField.java

@ -47,8 +47,8 @@ public class CalculatedField extends BaseData<CalculatedFieldId> implements HasN
private String name;
@Schema(description = "Version of calculated field configuration.", example = "0")
private int configurationVersion;
@Schema(description = "JSON with the calculated field configuration.", implementation = com.fasterxml.jackson.databind.JsonNode.class)
private transient JsonNode configuration;
@Schema
private transient CalculatedFieldConfig configuration;
@Getter
@Setter
private Long version;
@ -64,8 +64,7 @@ public class CalculatedField extends BaseData<CalculatedFieldId> implements HasN
super(id);
}
public CalculatedField(TenantId tenantId, EntityId entityId, String type, String name, int configurationVersion, JsonNode configuration, Long version, CalculatedFieldId externalId) {
super();
public CalculatedField(TenantId tenantId, EntityId entityId, String type, String name, int configurationVersion, CalculatedFieldConfig configuration, Long version, CalculatedFieldId externalId) {
this.tenantId = tenantId;
this.entityId = entityId;
this.type = type;

43
common/data/src/main/java/org/thingsboard/server/common/data/calculated_field/CalculatedFieldConfig.java

@ -0,0 +1,43 @@
/**
* 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.calculated_field;
import lombok.Data;
import org.thingsboard.server.common.data.id.EntityId;
import java.util.Map;
@Data
public class CalculatedFieldConfig {
private Map<String, Argument> arguments;
private Output output;
@Data
public static class Argument {
private EntityId entityId;
private String key;
private String type;
private int defaultValue;
}
@Data
public static class Output {
private String type;
private String expression;
}
}

8
common/data/src/main/java/org/thingsboard/server/common/data/calculated_field/CalculatedFieldLink.java

@ -37,8 +37,8 @@ public class CalculatedFieldLink extends BaseData<CalculatedFieldLinkId> {
@Schema(description = "JSON object with the Calculated Field Id. ", accessMode = Schema.AccessMode.READ_ONLY)
private CalculatedFieldId calculatedFieldId;
@Schema(description = "JSON with the calculated field link configuration.", implementation = com.fasterxml.jackson.databind.JsonNode.class)
private transient JsonNode configuration;
@Schema
private transient CalculatedFieldConfig configuration;
public CalculatedFieldLink() {
super();
@ -48,11 +48,11 @@ public class CalculatedFieldLink extends BaseData<CalculatedFieldLinkId> {
super(id);
}
public CalculatedFieldLink(TenantId tenantId, EntityId entityId, JsonNode configuration, CalculatedFieldId calculatedFieldId) {
public CalculatedFieldLink(TenantId tenantId, EntityId entityId, CalculatedFieldId calculatedFieldId, CalculatedFieldConfig configuration) {
this.tenantId = tenantId;
this.entityId = entityId;
this.configuration = configuration;
this.calculatedFieldId = calculatedFieldId;
this.configuration = configuration;
}
@Override

4
dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java

@ -222,8 +222,8 @@ public class BaseAssetService extends AbstractCachedEntityService<AssetCacheKey,
@Override
@Transactional
public void deleteEntity(TenantId tenantId, EntityId id, boolean force) {
if (!force && (entityViewService.existsByTenantIdAndEntityId(tenantId, id) || calculatedFieldService.existsByEntityId(tenantId, id))) {
throw new DataValidationException("Can't delete asset that has entity views or calculated fields!");
if (!force && (entityViewService.existsByTenantIdAndEntityId(tenantId, id) || calculatedFieldService.referencedInAnyCalculatedField(tenantId, id))) {
throw new DataValidationException("Can't delete asset that has entity views or is referenced in calculated fields!");
}
Asset asset = assetDao.findById(tenantId, id.getId());

35
dao/src/main/java/org/thingsboard/server/dao/calculated_field/BaseCalculatedFieldService.java

@ -20,6 +20,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.calculated_field.CalculatedField;
import org.thingsboard.server.common.data.calculated_field.CalculatedFieldConfig;
import org.thingsboard.server.common.data.calculated_field.CalculatedFieldLink;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.AssetProfileId;
@ -119,6 +120,16 @@ public class BaseCalculatedFieldService implements CalculatedFieldService {
return calculatedFieldDao.existsByTenantIdAndEntityId(tenantId, entityId);
}
@Override
public boolean referencedInAnyCalculatedField(TenantId tenantId, EntityId referencedEntityId) {
return calculatedFieldDao.findAllByTenantId(tenantId).stream()
.filter(calculatedField -> !referencedEntityId.equals(calculatedField.getEntityId()))
.map(CalculatedField::getConfiguration)
.map(CalculatedFieldConfig::getArguments)
.flatMap(arguments -> arguments.values().stream())
.anyMatch(argument -> referencedEntityId.equals(argument.getEntityId()));
}
@Override
public Optional<HasId<?>> findEntity(TenantId tenantId, EntityId entityId) {
return Optional.ofNullable(findById(tenantId, new CalculatedFieldId(entityId.getId())));
@ -142,22 +153,26 @@ public class BaseCalculatedFieldService implements CalculatedFieldService {
Optional.ofNullable(deviceProfileService.findDeviceProfileById(tenantId, (DeviceProfileId) entityId))
.orElseThrow(() -> new IllegalArgumentException("Device Profile with id [" + entityId.getId() + "] does not exist."));
default ->
throw new IllegalArgumentException("Entity type '" + entityId.getEntityType() + "' is not supported.");
throw new IllegalArgumentException("Entity type '" + entityId.getEntityType() + "' does not support calculated fields.");
}
}
private void createOrUpdateCalculatedFieldLink(TenantId tenantId, CalculatedField calculatedField) {
CalculatedFieldLink calculatedFieldLink = calculatedFieldLinkDao.findCalculatedFieldLinkByEntityId(tenantId.getId(), calculatedField.getEntityId().getId());
saveCalculatedFieldLink(tenantId, Objects.requireNonNullElseGet(calculatedFieldLink, () -> createCalculatedFieldLink(tenantId, calculatedField)));
CalculatedFieldLink existingLink = (calculatedField.getId() != null)
? calculatedFieldLinkDao.findCalculatedFieldLinkByCalculatedFieldId(tenantId, calculatedField.getId())
: null;
CalculatedFieldLink updatedLink = buildCalculatedFieldLink(tenantId, calculatedField, existingLink);
saveCalculatedFieldLink(tenantId, updatedLink);
}
private CalculatedFieldLink createCalculatedFieldLink(TenantId tenantId, CalculatedField calculatedField) {
CalculatedFieldLink calculatedFieldLink = new CalculatedFieldLink();
calculatedFieldLink.setTenantId(tenantId);
calculatedFieldLink.setEntityId(calculatedField.getEntityId());
calculatedFieldLink.setCalculatedFieldId(calculatedField.getId());
calculatedFieldLink.setConfiguration(calculatedField.getConfiguration());
return calculatedFieldLink;
private CalculatedFieldLink buildCalculatedFieldLink(TenantId tenantId, CalculatedField calculatedField, CalculatedFieldLink existingLink) {
CalculatedFieldLink link = (existingLink != null) ? existingLink : new CalculatedFieldLink();
link.setTenantId(tenantId);
link.setEntityId(calculatedField.getEntityId());
link.setCalculatedFieldId(calculatedField.getId());
link.setConfiguration(calculatedField.getConfiguration());
return link;
}
}

117
dao/src/main/java/org/thingsboard/server/dao/calculated_field/CalculatedFieldConfigUtil.java

@ -0,0 +1,117 @@
/**
* 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.dao.calculated_field;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.calculated_field.CalculatedFieldConfig;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class CalculatedFieldConfigUtil {
public static CalculatedFieldConfig toCalculatedFieldConfig(JsonNode config, EntityType entityType, UUID entityId) {
if (config == null) {
return null;
}
try {
CalculatedFieldConfig calculatedFieldConfig = new CalculatedFieldConfig();
Map<String, CalculatedFieldConfig.Argument> arguments = new HashMap<>();
JsonNode argumentsNode = config.get("arguments");
if (argumentsNode != null && argumentsNode.isObject()) {
argumentsNode.fields().forEachRemaining(entry -> {
String key = entry.getKey();
JsonNode argumentNode = entry.getValue();
CalculatedFieldConfig.Argument argument = new CalculatedFieldConfig.Argument();
if (argumentNode.has("entityType") && argumentNode.has("entityId")) {
String referencedEntityType = argumentNode.get("entityType").asText();
UUID referencedEntityId = UUID.fromString(argumentNode.get("entityId").asText());
argument.setEntityId(EntityIdFactory.getByTypeAndUuid(referencedEntityType, referencedEntityId));
} else {
argument.setEntityId(EntityIdFactory.getByTypeAndUuid(entityType, entityId));
}
argument.setKey(argumentNode.get("key").asText());
argument.setType(argumentNode.get("type").asText());
if (argumentNode.has("defaultValue")) {
argument.setDefaultValue(argumentNode.get("defaultValue").asInt());
}
arguments.put(key, argument);
});
}
calculatedFieldConfig.setArguments(arguments);
JsonNode outputNode = config.get("output");
if (outputNode != null) {
CalculatedFieldConfig.Output output = new CalculatedFieldConfig.Output();
output.setType(outputNode.get("type").asText());
output.setExpression(outputNode.get("expression").asText());
calculatedFieldConfig.setOutput(output);
}
return calculatedFieldConfig;
} catch (Exception e) {
throw new IllegalArgumentException("Failed to convert JsonNode to CalculatedFieldConfig", e);
}
}
public static JsonNode calculatedFieldConfigToJson(CalculatedFieldConfig calculatedFieldConfig, EntityType entityType, UUID entityId) {
if (calculatedFieldConfig == null) {
return null;
}
try {
ObjectNode configNode = JacksonUtil.newObjectNode();
ObjectNode argumentsNode = configNode.putObject("arguments");
calculatedFieldConfig.getArguments().forEach((key, argument) -> {
ObjectNode argumentNode = argumentsNode.putObject(key);
EntityId referencedEntityId = argument.getEntityId();
if (referencedEntityId != null) {
argumentNode.put("entityType", referencedEntityId.getEntityType().name());
argumentNode.put("entityId", referencedEntityId.getId().toString());
} else {
argumentNode.put("entityType", entityType.name());
argumentNode.put("entityId", entityId.toString());
}
argumentNode.put("key", argument.getKey());
argumentNode.put("type", argument.getType());
argumentNode.put("defaultValue", argument.getDefaultValue());
});
if (calculatedFieldConfig.getOutput() != null) {
ObjectNode outputNode = configNode.putObject("output");
outputNode.put("type", calculatedFieldConfig.getOutput().getType());
outputNode.put("expression", calculatedFieldConfig.getOutput().getExpression());
}
return configNode;
} catch (Exception e) {
throw new IllegalArgumentException("Failed to convert CalculatedFieldConfig to JsonNode", e);
}
}
}

2
dao/src/main/java/org/thingsboard/server/dao/calculated_field/CalculatedFieldDao.java

@ -26,6 +26,8 @@ public interface CalculatedFieldDao extends Dao<CalculatedField> {
boolean existsByTenantIdAndEntityId(TenantId tenantId, EntityId entityId);
List<CalculatedField> findAllByTenantId(TenantId tenantId);
List<CalculatedField> removeAllByEntityId(TenantId tenantId, EntityId entityId);
}

4
dao/src/main/java/org/thingsboard/server/dao/calculated_field/CalculatedFieldLinkDao.java

@ -16,12 +16,14 @@
package org.thingsboard.server.dao.calculated_field;
import org.thingsboard.server.common.data.calculated_field.CalculatedFieldLink;
import org.thingsboard.server.common.data.id.CalculatedFieldId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.Dao;
import java.util.UUID;
public interface CalculatedFieldLinkDao extends Dao<CalculatedFieldLink> {
CalculatedFieldLink findCalculatedFieldLinkByEntityId(UUID tenantId, UUID entityId);
CalculatedFieldLink findCalculatedFieldLinkByCalculatedFieldId(TenantId tenantId, CalculatedFieldId calculatedFieldId);
}

4
dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java

@ -184,6 +184,10 @@ public class CustomerServiceImpl extends AbstractCachedEntityService<CustomerCac
@Transactional
@Override
public void deleteEntity(TenantId tenantId, EntityId id, boolean force) {
if (!force && calculatedFieldService.referencedInAnyCalculatedField(tenantId, id)) {
throw new DataValidationException("Can't delete customer that is referenced in calculated fields!");
}
CustomerId customerId = (CustomerId) id;
Customer customer = findCustomerById(tenantId, customerId);
if (customer == null) {

4
dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java

@ -337,8 +337,8 @@ public class DeviceServiceImpl extends CachedVersionedEntityService<DeviceCacheK
@Override
@Transactional
public void deleteEntity(TenantId tenantId, EntityId id, boolean force) {
if (!force && (entityViewService.existsByTenantIdAndEntityId(tenantId, id) || calculatedFieldService.existsByEntityId(tenantId, id))) {
throw new DataValidationException("Can't delete device that has entity views or calculated fields!");
if (!force && (entityViewService.existsByTenantIdAndEntityId(tenantId, id) || calculatedFieldService.referencedInAnyCalculatedField(tenantId, id))) {
throw new DataValidationException("Can't delete device that has entity views or is referenced in calculated fields!");
}
Device device = deviceDao.findById(tenantId, id.getId());

6
dao/src/main/java/org/thingsboard/server/dao/model/sql/CalculatedFieldEntity.java

@ -33,6 +33,8 @@ import org.thingsboard.server.dao.util.mapping.JsonConverter;
import java.util.UUID;
import static org.thingsboard.server.dao.calculated_field.CalculatedFieldConfigUtil.calculatedFieldConfigToJson;
import static org.thingsboard.server.dao.calculated_field.CalculatedFieldConfigUtil.toCalculatedFieldConfig;
import static org.thingsboard.server.dao.model.ModelConstants.CALCULATED_FIELD_CONFIGURATION;
import static org.thingsboard.server.dao.model.ModelConstants.CALCULATED_FIELD_CONFIGURATION_VERSION;
import static org.thingsboard.server.dao.model.ModelConstants.CALCULATED_FIELD_ENTITY_ID;
@ -91,7 +93,7 @@ public class CalculatedFieldEntity extends BaseSqlEntity<CalculatedField> implem
this.type = calculatedField.getType();
this.name = calculatedField.getName();
this.configurationVersion = calculatedField.getConfigurationVersion();
this.configuration = calculatedField.getConfiguration();
this.configuration = calculatedFieldConfigToJson(calculatedField.getConfiguration(), entityType, entityId);
this.version = calculatedField.getVersion();
if (calculatedField.getExternalId() != null) {
this.externalId = calculatedField.getExternalId().getId();
@ -107,7 +109,7 @@ public class CalculatedFieldEntity extends BaseSqlEntity<CalculatedField> implem
calculatedField.setType(type);
calculatedField.setName(name);
calculatedField.setConfigurationVersion(configurationVersion);
calculatedField.setConfiguration(configuration);
calculatedField.setConfiguration(toCalculatedFieldConfig(configuration, entityType, entityId));
calculatedField.setVersion(version);
if (externalId != null) {
calculatedField.setExternalId(new CalculatedFieldId(externalId));

6
dao/src/main/java/org/thingsboard/server/dao/model/sql/CalculatedFieldLinkEntity.java

@ -34,6 +34,8 @@ import org.thingsboard.server.dao.util.mapping.JsonConverter;
import java.util.UUID;
import static org.thingsboard.server.dao.calculated_field.CalculatedFieldConfigUtil.calculatedFieldConfigToJson;
import static org.thingsboard.server.dao.calculated_field.CalculatedFieldConfigUtil.toCalculatedFieldConfig;
import static org.thingsboard.server.dao.model.ModelConstants.CALCULATED_FIELD_LINK_CALCULATED_FIELD_ID;
import static org.thingsboard.server.dao.model.ModelConstants.CALCULATED_FIELD_LINK_CONFIGURATION;
import static org.thingsboard.server.dao.model.ModelConstants.CALCULATED_FIELD_LINK_ENTITY_ID;
@ -75,7 +77,7 @@ public class CalculatedFieldLinkEntity extends BaseSqlEntity<CalculatedFieldLink
this.entityType = calculatedFieldLink.getEntityId().getEntityType();
this.entityId = calculatedFieldLink.getEntityId().getId();
this.calculatedFieldId = calculatedFieldLink.getCalculatedFieldId().getId();
this.configuration = calculatedFieldLink.getConfiguration();
this.configuration = calculatedFieldConfigToJson(calculatedFieldLink.getConfiguration(), entityType, entityId);
}
@Override
@ -85,7 +87,7 @@ public class CalculatedFieldLinkEntity extends BaseSqlEntity<CalculatedFieldLink
calculatedFieldLink.setTenantId(TenantId.fromUUID(tenantId));
calculatedFieldLink.setEntityId(EntityIdFactory.getByTypeAndUuid(entityType, entityId));
calculatedFieldLink.setCalculatedFieldId(new CalculatedFieldId(calculatedFieldId));
calculatedFieldLink.setConfiguration(configuration);
calculatedFieldLink.setConfiguration(toCalculatedFieldConfig(configuration, entityType, entityId));
return calculatedFieldLink;
}

2
dao/src/main/java/org/thingsboard/server/dao/sql/calculated_field/CalculatedFieldLinkRepository.java

@ -22,6 +22,6 @@ import java.util.UUID;
public interface CalculatedFieldLinkRepository extends JpaRepository<CalculatedFieldLinkEntity, UUID> {
CalculatedFieldLinkEntity findByTenantIdAndEntityId(UUID tenantId, UUID entityId);
CalculatedFieldLinkEntity findByTenantIdAndCalculatedFieldId(UUID tenantId, UUID calculatedFieldId);
}

2
dao/src/main/java/org/thingsboard/server/dao/sql/calculated_field/CalculatedFieldRepository.java

@ -25,6 +25,8 @@ public interface CalculatedFieldRepository extends JpaRepository<CalculatedField
boolean existsByTenantIdAndEntityId(UUID tenantId, UUID entityId);
List<CalculatedFieldEntity> findAllByTenantId(UUID tenantId);
List<CalculatedFieldEntity> removeAllByTenantIdAndEntityId(UUID tenantId, UUID entityId);
}

5
dao/src/main/java/org/thingsboard/server/dao/sql/calculated_field/JpaCalculatedFieldDao.java

@ -45,6 +45,11 @@ public class JpaCalculatedFieldDao extends JpaAbstractDao<CalculatedFieldEntity,
return calculatedFieldRepository.existsByTenantIdAndEntityId(tenantId.getId(), entityId.getId());
}
@Override
public List<CalculatedField> findAllByTenantId(TenantId tenantId) {
return DaoUtil.convertDataList(calculatedFieldRepository.findAllByTenantId(tenantId.getId()));
}
@Override
@Transactional
public List<CalculatedField> removeAllByEntityId(TenantId tenantId, EntityId entityId) {

6
dao/src/main/java/org/thingsboard/server/dao/sql/calculated_field/JpaCalculatedFieldLinkDao.java

@ -20,6 +20,8 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.calculated_field.CalculatedFieldLink;
import org.thingsboard.server.common.data.id.CalculatedFieldId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.calculated_field.CalculatedFieldLinkDao;
import org.thingsboard.server.dao.model.sql.CalculatedFieldLinkEntity;
@ -37,8 +39,8 @@ public class JpaCalculatedFieldLinkDao extends JpaAbstractDao<CalculatedFieldLin
private final CalculatedFieldLinkRepository calculatedFieldLinkRepository;
@Override
public CalculatedFieldLink findCalculatedFieldLinkByEntityId(UUID tenantId, UUID entityId) {
return DaoUtil.getData(calculatedFieldLinkRepository.findByTenantIdAndEntityId(tenantId, entityId));
public CalculatedFieldLink findCalculatedFieldLinkByCalculatedFieldId(TenantId tenantId, CalculatedFieldId calculatedFieldId) {
return DaoUtil.getData(calculatedFieldLinkRepository.findByTenantIdAndCalculatedFieldId(tenantId.getId(), calculatedFieldId.getId()));
}
@Override

40
dao/src/test/java/org/thingsboard/server/dao/service/AssetServiceTest.java

@ -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.calculated_field.CalculatedField;
import org.thingsboard.server.common.data.calculated_field.CalculatedFieldConfig;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
@ -48,6 +49,7 @@ import org.thingsboard.server.dao.relation.RelationService;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
@ -247,7 +249,9 @@ public class AssetServiceTest extends AbstractServiceTest {
Assert.assertEquals("typeB", assetTypes.get(1).getType());
Assert.assertEquals("typeC", assetTypes.get(2).getType());
} finally {
assets.forEach((asset) -> { assetService.deleteAsset(tenantId, asset.getId()); });
assets.forEach((asset) -> {
assetService.deleteAsset(tenantId, asset.getId());
});
}
}
@ -854,23 +858,49 @@ public class AssetServiceTest extends AbstractServiceTest {
}
@Test
public void testDeleteAssetIfCalculatedFieldExists() {
public void testDeleteAssetIfReferencedInCalculatedField() {
Asset asset = new Asset();
asset.setTenantId(tenantId);
asset.setName("My asset");
asset.setType("default");
Asset savedAsset = assetService.saveAsset(asset);
Asset assetWithCf = new Asset();
assetWithCf.setTenantId(tenantId);
assetWithCf.setName("Asset with CF");
assetWithCf.setType("default");
Asset savedAssetWithCf = assetService.saveAsset(assetWithCf);
CalculatedField calculatedField = new CalculatedField();
calculatedField.setTenantId(tenantId);
calculatedField.setName("Test CF");
calculatedField.setType("Simple");
calculatedField.setEntityId(savedAsset.getId());
calculatedFieldService.save(calculatedField);
calculatedField.setEntityId(savedAssetWithCf.getId());
CalculatedFieldConfig config = new CalculatedFieldConfig();
CalculatedFieldConfig.Argument argument = new CalculatedFieldConfig.Argument();
argument.setEntityId(savedAsset.getId());
argument.setType("TIME_SERIES");
argument.setKey("temperature");
config.setArguments(Map.of("T", argument));
CalculatedFieldConfig.Output output = new CalculatedFieldConfig.Output();
output.setType("TIME_SERIES");
output.setExpression("T - (100 - H) / 5");
config.setOutput(output);
calculatedField.setConfiguration(config);
CalculatedField savedCalculatedField = calculatedFieldService.save(calculatedField);
assertThatThrownBy(() -> assetService.deleteAsset(tenantId, savedAsset.getId()))
.isInstanceOf(DataValidationException.class)
.hasMessage("Can't delete asset that has entity views or calculated fields!");
.hasMessage("Can't delete asset that has entity views or is referenced in calculated fields!");
calculatedFieldService.deleteCalculatedField(tenantId, savedCalculatedField.getId());
}
}

52
dao/src/test/java/org/thingsboard/server/dao/service/CalculatedFieldServiceTest.java

@ -21,17 +21,19 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.calculated_field.CalculatedField;
import org.thingsboard.server.common.data.calculated_field.CalculatedFieldConfig;
import org.thingsboard.server.common.data.calculated_field.CalculatedFieldLink;
import org.thingsboard.server.common.data.id.CalculatedFieldId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.dao.calculated_field.CalculatedFieldService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.exception.DataValidationException;
import java.util.Map;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
@ -60,7 +62,7 @@ public class CalculatedFieldServiceTest extends AbstractServiceTest {
@Test
public void testSaveCalculatedField() {
Device device = createTestDevice();
CalculatedField calculatedField = getCalculatedField(device.getId());
CalculatedField calculatedField = getCalculatedField(device.getId(), device.getId());
CalculatedField savedCalculatedField = calculatedFieldService.save(calculatedField);
assertThat(savedCalculatedField).isNotNull();
@ -84,7 +86,8 @@ public class CalculatedFieldServiceTest extends AbstractServiceTest {
@Test
public void testSaveCalculatesFieldWithNonExistingDeviceId() {
CalculatedField calculatedField = getCalculatedField(new DeviceId(UUID.fromString("038f8668-c9fd-4f00-8501-ce20f2f93c22")));
DeviceId deviceId = new DeviceId(UUID.fromString("038f8668-c9fd-4f00-8501-ce20f2f93c22"));
CalculatedField calculatedField = getCalculatedField(deviceId, deviceId);
assertThatThrownBy(() -> calculatedFieldService.save(calculatedField))
.isInstanceOf(IllegalArgumentException.class)
@ -94,7 +97,7 @@ public class CalculatedFieldServiceTest extends AbstractServiceTest {
@Test
public void testSaveCalculatedFieldWithExistingName() {
Device device = createTestDevice();
CalculatedField calculatedField = getCalculatedField(device.getId());
CalculatedField calculatedField = getCalculatedField(device.getId(), device.getId());
calculatedFieldService.save(calculatedField);
assertThatThrownBy(() -> calculatedFieldService.save(calculatedField))
@ -105,7 +108,7 @@ public class CalculatedFieldServiceTest extends AbstractServiceTest {
@Test
public void testSaveCalculatedFieldWithExistingExternalId() {
Device device = createTestDevice();
CalculatedField calculatedField = getCalculatedField(device.getId());
CalculatedField calculatedField = getCalculatedField(device.getId(), device.getId());
calculatedField.setExternalId(new CalculatedFieldId(UUID.fromString("2ef69d0a-89cf-4868-86f8-c50551d87ebe")));
calculatedFieldService.save(calculatedField);
@ -147,28 +150,18 @@ public class CalculatedFieldServiceTest extends AbstractServiceTest {
private CalculatedField saveValidCalculatedField() {
Device device = createTestDevice();
CalculatedField calculatedField = getCalculatedField(device.getId());
CalculatedField calculatedField = getCalculatedField(device.getId(), device.getId());
return calculatedFieldService.save(calculatedField);
}
private CalculatedField getCalculatedField(DeviceId deviceId) {
private CalculatedField getCalculatedField(EntityId entityId, EntityId referencedEntityId) {
CalculatedField calculatedField = new CalculatedField();
calculatedField.setTenantId(tenantId);
calculatedField.setEntityId(deviceId);
calculatedField.setEntityId(entityId);
calculatedField.setType("Simple");
calculatedField.setName("Test Calculated Field");
calculatedField.setConfigurationVersion(1);
calculatedField.setConfiguration(JacksonUtil.toJsonNode("{\n" +
" \"T\": {\n" +
" \"key\": \"temperature\",\n" +
" \"type\": \"TIME_SERIES\"\n" +
" },\n" +
" \"H\": {\n" +
" \"key\": \"humidity\",\n" +
" \"type\": \"TIME_SERIES\",\n" +
" \"defaultValue\": 50\n" +
" }\n" +
" }\n"));
calculatedField.setConfiguration(getCalculatedFieldConfig(referencedEntityId));
calculatedField.setVersion(1L);
return calculatedField;
}
@ -177,11 +170,30 @@ public class CalculatedFieldServiceTest extends AbstractServiceTest {
CalculatedFieldLink calculatedFieldLink = new CalculatedFieldLink();
calculatedFieldLink.setTenantId(tenantId);
calculatedFieldLink.setEntityId(calculatedField.getEntityId());
calculatedFieldLink.setConfiguration(calculatedField.getConfiguration());
// calculatedFieldLink.setConfiguration(calculatedField.getConfiguration());
calculatedFieldLink.setCalculatedFieldId(calculatedField.getId());
return calculatedFieldLink;
}
private CalculatedFieldConfig getCalculatedFieldConfig(EntityId referencedEntityId) {
CalculatedFieldConfig config = new CalculatedFieldConfig();
CalculatedFieldConfig.Argument argument = new CalculatedFieldConfig.Argument();
argument.setEntityId(referencedEntityId);
argument.setType("TIME_SERIES");
argument.setKey("temperature");
config.setArguments(Map.of("T", argument));
CalculatedFieldConfig.Output output = new CalculatedFieldConfig.Output();
output.setType("TIME_SERIES");
output.setExpression("T - (100 - H) / 5");
config.setOutput(output);
return config;
}
private Device createTestDevice() {
Device device = new Device();
device.setTenantId(tenantId);

57
dao/src/test/java/org/thingsboard/server/dao/service/CustomerServiceTest.java

@ -30,14 +30,20 @@ import org.testcontainers.shaded.org.awaitility.Awaitility;
import org.thingsboard.common.util.ThingsBoardExecutors;
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.calculated_field.CalculatedField;
import org.thingsboard.server.common.data.calculated_field.CalculatedFieldConfig;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.calculated_field.CalculatedFieldService;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.exception.DataValidationException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
@ -45,13 +51,17 @@ import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@DaoSqlTest
public class CustomerServiceTest extends AbstractServiceTest {
@Autowired
CustomerService customerService;
@Autowired
CalculatedFieldService calculatedFieldService;
@Autowired
AssetService assetService;
static final int TIMEOUT = 30;
@ -343,4 +353,49 @@ public class CustomerServiceTest extends AbstractServiceTest {
}
}
@Test
public void testDeleteCustomerIfReferencedInCalculatedField() {
Customer customer = new Customer();
customer.setTenantId(tenantId);
customer.setTitle("My customer");
Customer savedCustomer = customerService.saveCustomer(customer);
Asset asset = new Asset();
asset.setTenantId(tenantId);
asset.setName("My asset");
asset.setType("default");
Asset savedAsset = assetService.saveAsset(asset);
CalculatedField calculatedField = new CalculatedField();
calculatedField.setTenantId(tenantId);
calculatedField.setName("Test CF");
calculatedField.setType("Simple");
calculatedField.setEntityId(savedAsset.getId());
CalculatedFieldConfig config = new CalculatedFieldConfig();
CalculatedFieldConfig.Argument argument = new CalculatedFieldConfig.Argument();
argument.setEntityId(savedCustomer.getId());
argument.setType("TIME_SERIES");
argument.setKey("temperature");
config.setArguments(Map.of("T", argument));
CalculatedFieldConfig.Output output = new CalculatedFieldConfig.Output();
output.setType("TIME_SERIES");
output.setExpression("T - (100 - H) / 5");
config.setOutput(output);
calculatedField.setConfiguration(config);
CalculatedField savedCalculatedField = calculatedFieldService.save(calculatedField);
assertThatThrownBy(() -> customerService.deleteCustomer(tenantId, savedCustomer.getId()))
.isInstanceOf(DataValidationException.class)
.hasMessage("Can't delete customer that is referenced in calculated fields!");
calculatedFieldService.deleteCalculatedField(tenantId, savedCalculatedField.getId());
}
}

34
dao/src/test/java/org/thingsboard/server/dao/service/DeviceServiceTest.java

@ -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.calculated_field.CalculatedField;
import org.thingsboard.server.common.data.calculated_field.CalculatedFieldConfig;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.OtaPackageId;
@ -66,6 +67,7 @@ import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
@ -1203,19 +1205,41 @@ public class DeviceServiceTest extends AbstractServiceTest {
}
@Test
public void testDeleteDeviceIfCalculatedFieldExists() {
Device device = saveDevice(tenantId, "Test");
public void testDeleteAssetIfReferencedInCalculatedField() {
Device device = saveDevice(tenantId, "Test Device");
Device deviceWithCf = saveDevice(tenantId, "Device with CF");
CalculatedField calculatedField = new CalculatedField();
calculatedField.setTenantId(tenantId);
calculatedField.setName("Test CF");
calculatedField.setType("Simple");
calculatedField.setEntityId(device.getId());
calculatedFieldService.save(calculatedField);
calculatedField.setEntityId(deviceWithCf.getId());
CalculatedFieldConfig config = new CalculatedFieldConfig();
CalculatedFieldConfig.Argument argument = new CalculatedFieldConfig.Argument();
argument.setEntityId(device.getId());
argument.setType("TIME_SERIES");
argument.setKey("temperature");
config.setArguments(Map.of("T", argument));
CalculatedFieldConfig.Output output = new CalculatedFieldConfig.Output();
output.setType("TIME_SERIES");
output.setExpression("T - (100 - H) / 5");
config.setOutput(output);
calculatedField.setConfiguration(config);
CalculatedField savedCalculatedField = calculatedFieldService.save(calculatedField);
assertThatThrownBy(() -> deviceService.deleteDevice(tenantId, device.getId()))
.isInstanceOf(DataValidationException.class)
.hasMessage("Can't delete device that has entity views or calculated fields!");
.hasMessage("Can't delete device that has entity views or is referenced in calculated fields!");
calculatedFieldService.deleteCalculatedField(tenantId, savedCalculatedField.getId());
}
}

Loading…
Cancel
Save