added calculated field link entity and its dao

This commit is contained in:
IrynaMatveieva 2024-11-04 15:57:37 +02:00
parent e2d17a822d
commit 03ff7c17ac
22 changed files with 702 additions and 41 deletions

View File

@ -50,7 +50,8 @@ public enum Resource {
NOTIFICATION(EntityType.NOTIFICATION_TARGET, EntityType.NOTIFICATION_TEMPLATE,
EntityType.NOTIFICATION_REQUEST, EntityType.NOTIFICATION_RULE),
MOBILE_APP_SETTINGS,
CALCULATED_FIELD(EntityType.CALCULATED_FIELD);
CALCULATED_FIELD(EntityType.CALCULATED_FIELD),
CALCULATED_FIELD_LINK(EntityType.CALCULATED_FIELD_LINK);
private final Set<EntityType> entityTypes;

View File

@ -19,6 +19,7 @@ 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;
@ -26,18 +27,13 @@ import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.service.DaoSqlTest;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@DaoSqlTest
public class CalculatedFieldControllerTest extends AbstractControllerTest {
private final DeviceId DEVICE_ID = new DeviceId(UUID.fromString("9e408b94-dc05-47e2-a21c-1a6c0d7bd90a"));
private Tenant savedTenant;
private User tenantAdmin;
@Before
public void beforeTest() throws Exception {
@ -48,14 +44,14 @@ public class CalculatedFieldControllerTest extends AbstractControllerTest {
savedTenant = saveTenant(tenant);
assertThat(savedTenant).isNotNull();
tenantAdmin = new User();
User tenantAdmin = new User();
tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
tenantAdmin.setTenantId(savedTenant.getId());
tenantAdmin.setEmail("tenant2@thingsboard.org");
tenantAdmin.setFirstName("Joe");
tenantAdmin.setLastName("Downs");
tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
createUserAndLogin(tenantAdmin, "testPassword1");
}
@After
@ -67,7 +63,8 @@ public class CalculatedFieldControllerTest extends AbstractControllerTest {
@Test
public void testSaveCalculatedField() throws Exception {
CalculatedField calculatedField = getCalculatedField();
Device testDevice = createDevice("Test device", "1234567890");
CalculatedField calculatedField = getCalculatedField(testDevice.getId());
CalculatedField savedCalculatedField = doPost("/api/calculatedField", calculatedField, CalculatedField.class);
@ -93,7 +90,8 @@ public class CalculatedFieldControllerTest extends AbstractControllerTest {
@Test
public void testGetCalculatedFieldById() throws Exception {
CalculatedField calculatedField = getCalculatedField();
Device testDevice = createDevice("Test device", "1234567890");
CalculatedField calculatedField = getCalculatedField(testDevice.getId());
CalculatedField savedCalculatedField = doPost("/api/calculatedField", calculatedField, CalculatedField.class);
CalculatedField fetchedCalculatedField = doGet("/api/calculatedField/" + savedCalculatedField.getId().getId(), CalculatedField.class);
@ -107,7 +105,8 @@ public class CalculatedFieldControllerTest extends AbstractControllerTest {
@Test
public void testDeleteCalculatedField() throws Exception {
CalculatedField calculatedField = getCalculatedField();
Device testDevice = createDevice("Test device", "1234567890");
CalculatedField calculatedField = getCalculatedField(testDevice.getId());
CalculatedField savedCalculatedField = doPost("/api/calculatedField", calculatedField, CalculatedField.class);
@ -119,9 +118,9 @@ public class CalculatedFieldControllerTest extends AbstractControllerTest {
}
private CalculatedField getCalculatedField() {
private CalculatedField getCalculatedField(DeviceId deviceId) {
CalculatedField calculatedField = new CalculatedField();
calculatedField.setEntityId(DEVICE_ID);
calculatedField.setEntityId(deviceId);
calculatedField.setType("Simple");
calculatedField.setName("Test Calculated Field");
calculatedField.setConfigurationVersion(1);

View File

@ -16,6 +16,7 @@
package org.thingsboard.server.dao.calculated_field;
import org.thingsboard.server.common.data.calculated_field.CalculatedField;
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.entity.EntityDaoService;
@ -28,4 +29,6 @@ public interface CalculatedFieldService extends EntityDaoService {
void deleteCalculatedField(TenantId tenantId, CalculatedFieldId calculatedFieldId);
CalculatedFieldLink saveCalculatedFieldLink(TenantId tenantId, CalculatedFieldLink calculatedFieldLink);
}

View File

@ -61,7 +61,8 @@ public enum EntityType {
OAUTH2_CLIENT(35),
DOMAIN(36),
MOBILE_APP(37),
CALCULATED_FIELD(38);
CALCULATED_FIELD(38),
CALCULATED_FIELD_LINK(39);
@Getter
private final int protoNumber; // Corresponds to EntityTypeProto

View File

@ -0,0 +1,71 @@
/**
* 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 com.fasterxml.jackson.databind.JsonNode;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.id.CalculatedFieldId;
import org.thingsboard.server.common.data.id.CalculatedFieldLinkId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
@Schema
@Data
@EqualsAndHashCode(callSuper = true)
public class CalculatedFieldLink extends BaseData<CalculatedFieldLinkId> {
private static final long serialVersionUID = 6492846246722091530L;
private TenantId tenantId;
private EntityId entityId;
@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;
public CalculatedFieldLink() {
super();
}
public CalculatedFieldLink(CalculatedFieldLinkId id) {
super(id);
}
public CalculatedFieldLink(TenantId tenantId, EntityId entityId, JsonNode configuration, CalculatedFieldId calculatedFieldId) {
this.tenantId = tenantId;
this.entityId = entityId;
this.configuration = configuration;
this.calculatedFieldId = calculatedFieldId;
}
@Override
public String toString() {
return new StringBuilder()
.append("CalculatedFieldLink[")
.append("tenantId=").append(tenantId)
.append(", entityId=").append(entityId)
.append(", calculatedFieldId=").append(calculatedFieldId)
.append(", configuration=").append(configuration)
.append(", createdTime=").append(createdTime)
.append(", id=").append(id).append(']')
.toString();
}
}

View File

@ -0,0 +1,45 @@
/**
* 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.id;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import org.thingsboard.server.common.data.EntityType;
import java.util.UUID;
@Schema
public class CalculatedFieldLinkId extends UUIDBased implements EntityId {
private static final long serialVersionUID = 1L;
@JsonCreator
public CalculatedFieldLinkId(@JsonProperty("id") UUID id) {
super(id);
}
public static CalculatedFieldLinkId fromString(String calculatedFieldLinkId) {
return new CalculatedFieldLinkId(UUID.fromString(calculatedFieldLinkId));
}
@Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "string", example = "CALCULATED_FIELD_LINK", allowableValues = "CALCULATED_FIELD_LINK")
@Override
public EntityType getEntityType() {
return EntityType.CALCULATED_FIELD_LINK;
}
}

View File

@ -113,6 +113,8 @@ public class EntityIdFactory {
return new DomainId(uuid);
case CALCULATED_FIELD:
return new CalculatedFieldId(uuid);
case CALCULATED_FIELD_LINK:
return new CalculatedFieldLinkId(uuid);
}
throw new IllegalArgumentException("EntityType " + type + " is not supported!");
}

View File

@ -59,6 +59,7 @@ enum EntityTypeProto {
DOMAIN = 36;
MOBILE_APP = 37;
CALCULATED_FIELD = 38;
CALCULATED_FIELD_LINK = 39;
}
/**

View File

@ -20,13 +20,21 @@ 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.CalculatedFieldLink;
import org.thingsboard.server.common.data.id.AssetId;
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.common.data.id.HasId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.service.DataValidator;
import java.util.Objects;
import java.util.Optional;
import static org.thingsboard.server.dao.entity.AbstractEntityService.checkConstraintViolation;
import static org.thingsboard.server.dao.service.Validator.validateId;
@Service("CalculatedFieldDaoService")
@ -38,11 +46,28 @@ public class BaseCalculatedFieldService implements CalculatedFieldService {
public static final String INCORRECT_CALCULATED_FIELD_ID = "Incorrect calculatedFieldId ";
private final CalculatedFieldDao calculatedFieldDao;
private final CalculatedFieldLinkDao calculatedFieldLinkDao;
private final DeviceService deviceService;
private final AssetService assetService;
private final DataValidator<CalculatedField> calculatedFieldDataValidator;
private final DataValidator<CalculatedFieldLink> calculatedFieldLinkDataValidator;
@Override
public CalculatedField save(CalculatedField calculatedField) {
log.trace("Executing save, [{}]", calculatedField);
return calculatedFieldDao.save(calculatedField.getTenantId(), calculatedField);
calculatedFieldDataValidator.validate(calculatedField, CalculatedField::getTenantId);
try {
TenantId tenantId = calculatedField.getTenantId();
checkEntityExistence(tenantId, calculatedField.getEntityId());
log.trace("Executing save calculated field, [{}]", calculatedField);
CalculatedField savedCalculatedField = calculatedFieldDao.save(tenantId, calculatedField);
createOrUpdateCalculatedFieldLink(tenantId, savedCalculatedField);
return savedCalculatedField;
} catch (Exception e) {
checkConstraintViolation(e,
"calculated_field_unq_key", "Calculated Field with such name is already in exists!",
"calculated_field_external_id_unq_key", "Calculated Field with such external id already exists!");
throw e;
}
}
@Override
@ -61,6 +86,18 @@ public class BaseCalculatedFieldService implements CalculatedFieldService {
calculatedFieldDao.removeById(tenantId, calculatedFieldId.getId());
}
@Override
public CalculatedFieldLink saveCalculatedFieldLink(TenantId tenantId, CalculatedFieldLink calculatedFieldLink) {
calculatedFieldLinkDataValidator.validate(calculatedFieldLink, CalculatedFieldLink::getTenantId);
try {
log.trace("Executing save calculated field link, [{}]", calculatedFieldLink);
return calculatedFieldLinkDao.save(tenantId, calculatedFieldLink);
} catch (Exception e) {
checkConstraintViolation(e, "calculated_field_link_unq_key", "Calculated Field for such entity id is already exists!");
throw e;
}
}
@Override
public Optional<HasId<?>> findEntity(TenantId tenantId, EntityId entityId) {
return Optional.ofNullable(findById(tenantId, new CalculatedFieldId(entityId.getId())));
@ -71,4 +108,29 @@ public class BaseCalculatedFieldService implements CalculatedFieldService {
return EntityType.CALCULATED_FIELD;
}
private void checkEntityExistence(TenantId tenantId, EntityId entityId) {
switch (entityId.getEntityType()) {
case ASSET -> Optional.ofNullable(assetService.findAssetById(tenantId, (AssetId) entityId))
.orElseThrow(() -> new IllegalArgumentException("Asset with id [" + entityId.getId() + "] does not exist."));
case DEVICE -> Optional.ofNullable(deviceService.findDeviceById(tenantId, (DeviceId) entityId))
.orElseThrow(() -> new IllegalArgumentException("Device with id [" + entityId.getId() + "] does not exist."));
default ->
throw new IllegalArgumentException("Entity type '" + entityId.getEntityType() + "' is not supported.");
}
}
private void createOrUpdateCalculatedFieldLink(TenantId tenantId, CalculatedField calculatedField) {
CalculatedFieldLink calculatedFieldLink = calculatedFieldLinkDao.findCalculatedFieldLinkByEntityId(tenantId.getId(), calculatedField.getEntityId().getId());
saveCalculatedFieldLink(tenantId, Objects.requireNonNullElseGet(calculatedFieldLink, () -> createCalculatedFieldLink(tenantId, calculatedField)));
}
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;
}
}

View File

@ -0,0 +1,27 @@
/**
* 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 org.thingsboard.server.common.data.calculated_field.CalculatedFieldLink;
import org.thingsboard.server.dao.Dao;
import java.util.UUID;
public interface CalculatedFieldLinkDao extends Dao<CalculatedFieldLink> {
CalculatedFieldLink findCalculatedFieldLinkByEntityId(UUID tenantId, UUID entityId);
}

View File

@ -705,6 +705,16 @@ public class ModelConstants {
public static final String CALCULATED_FIELD_VERSION = "version";
public static final String CALCULATED_FIELD_EXTERNAL_ID = "external_id";
/**
* Calculated field links constants.
*/
public static final String CALCULATED_FIELD_LINK_TABLE_NAME = "calculated_field_link";
public static final String CALCULATED_FIELD_LINK_TENANT_ID_COLUMN = TENANT_ID_COLUMN;
public static final String CALCULATED_FIELD_LINK_ENTITY_TYPE = ENTITY_TYPE_COLUMN;
public static final String CALCULATED_FIELD_LINK_ENTITY_ID = ENTITY_ID_COLUMN;
public static final String CALCULATED_FIELD_LINK_CALCULATED_FIELD_ID = "calculated_field_id";
public static final String CALCULATED_FIELD_LINK_CONFIGURATION = "configuration";
protected static final String[] NONE_AGGREGATION_COLUMNS = new String[]{LONG_VALUE_COLUMN, DOUBLE_VALUE_COLUMN, BOOLEAN_VALUE_COLUMN, STRING_VALUE_COLUMN, JSON_VALUE_COLUMN, KEY_COLUMN, TS_COLUMN};
protected static final String[] COUNT_AGGREGATION_COLUMNS = new String[]{count(LONG_VALUE_COLUMN), count(DOUBLE_VALUE_COLUMN), count(BOOLEAN_VALUE_COLUMN), count(STRING_VALUE_COLUMN), count(JSON_VALUE_COLUMN), max(TS_COLUMN)};

View File

@ -0,0 +1,92 @@
/**
* 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.model.sql;
import com.fasterxml.jackson.databind.JsonNode;
import jakarta.persistence.Column;
import jakarta.persistence.Convert;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.calculated_field.CalculatedFieldLink;
import org.thingsboard.server.common.data.id.CalculatedFieldId;
import org.thingsboard.server.common.data.id.CalculatedFieldLinkId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.model.BaseEntity;
import org.thingsboard.server.dao.model.BaseSqlEntity;
import org.thingsboard.server.dao.util.mapping.JsonConverter;
import java.util.UUID;
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;
import static org.thingsboard.server.dao.model.ModelConstants.CALCULATED_FIELD_LINK_ENTITY_TYPE;
import static org.thingsboard.server.dao.model.ModelConstants.CALCULATED_FIELD_LINK_TABLE_NAME;
import static org.thingsboard.server.dao.model.ModelConstants.CALCULATED_FIELD_LINK_TENANT_ID_COLUMN;
@Data
@EqualsAndHashCode(callSuper = true)
@Entity
@Table(name = CALCULATED_FIELD_LINK_TABLE_NAME)
public class CalculatedFieldLinkEntity extends BaseSqlEntity<CalculatedFieldLink> implements BaseEntity<CalculatedFieldLink> {
@Column(name = CALCULATED_FIELD_LINK_TENANT_ID_COLUMN)
private UUID tenantId;
@Column(name = CALCULATED_FIELD_LINK_ENTITY_TYPE)
private EntityType entityType;
@Column(name = CALCULATED_FIELD_LINK_ENTITY_ID)
private UUID entityId;
@Column(name = CALCULATED_FIELD_LINK_CALCULATED_FIELD_ID)
private UUID calculatedFieldId;
@Convert(converter = JsonConverter.class)
@Column(name = CALCULATED_FIELD_LINK_CONFIGURATION)
private JsonNode configuration;
public CalculatedFieldLinkEntity() {
super();
}
public CalculatedFieldLinkEntity(CalculatedFieldLink calculatedFieldLink) {
this.setUuid(calculatedFieldLink.getUuidId());
this.createdTime = calculatedFieldLink.getCreatedTime();
this.tenantId = calculatedFieldLink.getTenantId().getId();
this.entityType = calculatedFieldLink.getEntityId().getEntityType();
this.entityId = calculatedFieldLink.getEntityId().getId();
this.calculatedFieldId = calculatedFieldLink.getCalculatedFieldId().getId();
this.configuration = calculatedFieldLink.getConfiguration();
}
@Override
public CalculatedFieldLink toData() {
CalculatedFieldLink calculatedFieldLink = new CalculatedFieldLink(new CalculatedFieldLinkId(id));
calculatedFieldLink.setCreatedTime(createdTime);
calculatedFieldLink.setTenantId(TenantId.fromUUID(tenantId));
calculatedFieldLink.setEntityId(EntityIdFactory.getByTypeAndUuid(entityType, entityId));
calculatedFieldLink.setCalculatedFieldId(new CalculatedFieldId(calculatedFieldId));
calculatedFieldLink.setConfiguration(configuration);
return calculatedFieldLink;
}
}

View File

@ -0,0 +1,41 @@
/**
* 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.service.validator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.calculated_field.CalculatedField;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.calculated_field.CalculatedFieldDao;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator;
@Component
public class CalculatedFieldDataValidator extends DataValidator<CalculatedField> {
@Autowired
private CalculatedFieldDao calculatedFieldDao;
@Override
protected CalculatedField validateUpdate(TenantId tenantId, CalculatedField calculatedField) {
CalculatedField old = calculatedFieldDao.findById(calculatedField.getTenantId(), calculatedField.getId().getId());
if (old == null) {
throw new DataValidationException("Can't update non existing calculated field!");
}
return old;
}
}

View File

@ -0,0 +1,41 @@
/**
* 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.service.validator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.calculated_field.CalculatedFieldLink;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.calculated_field.CalculatedFieldLinkDao;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator;
@Component
public class CalculatedFieldLinkDataValidator extends DataValidator<CalculatedFieldLink> {
@Autowired
private CalculatedFieldLinkDao calculatedFieldLinkDao;
@Override
protected CalculatedFieldLink validateUpdate(TenantId tenantId, CalculatedFieldLink calculatedFieldLink) {
CalculatedFieldLink old = calculatedFieldLinkDao.findById(calculatedFieldLink.getTenantId(), calculatedFieldLink.getId().getId());
if (old == null) {
throw new DataValidationException("Can't update non existing calculated field link!");
}
return old;
}
}

View File

@ -0,0 +1,27 @@
/**
* 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.sql.calculated_field;
import org.springframework.data.jpa.repository.JpaRepository;
import org.thingsboard.server.dao.model.sql.CalculatedFieldLinkEntity;
import java.util.UUID;
public interface CalculatedFieldLinkRepository extends JpaRepository<CalculatedFieldLinkEntity, UUID> {
CalculatedFieldLinkEntity findByTenantIdAndEntityId(UUID tenantId, UUID entityId);
}

View File

@ -0,0 +1,54 @@
/**
* 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.sql.calculated_field;
import lombok.AllArgsConstructor;
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.dao.DaoUtil;
import org.thingsboard.server.dao.calculated_field.CalculatedFieldLinkDao;
import org.thingsboard.server.dao.model.sql.CalculatedFieldLinkEntity;
import org.thingsboard.server.dao.sql.JpaAbstractDao;
import org.thingsboard.server.dao.util.SqlDao;
import java.util.UUID;
@Slf4j
@Component
@AllArgsConstructor
@SqlDao
public class JpaCalculatedFieldLinkDao extends JpaAbstractDao<CalculatedFieldLinkEntity, CalculatedFieldLink> implements CalculatedFieldLinkDao {
private final CalculatedFieldLinkRepository calculatedFieldLinkRepository;
@Override
public CalculatedFieldLink findCalculatedFieldLinkByEntityId(UUID tenantId, UUID entityId) {
return DaoUtil.getData(calculatedFieldLinkRepository.findByTenantIdAndEntityId(tenantId, entityId));
}
@Override
protected Class<CalculatedFieldLinkEntity> getEntityClass() {
return CalculatedFieldLinkEntity.class;
}
@Override
protected JpaRepository<CalculatedFieldLinkEntity, UUID> getRepository() {
return calculatedFieldLinkRepository;
}
}

View File

@ -919,15 +919,15 @@ CREATE TABLE IF NOT EXISTS calculated_field (
CONSTRAINT calculated_field_external_id_unq_key UNIQUE (tenant_id, external_id)
);
-- CREATE TABLE IF NOT EXISTS calculated_field_link (
-- id uuid NOT NULL CONSTRAINT calculated_field_pkey PRIMARY KEY,
-- created_time bigint NOT NULL,
-- tenant_id uuid NOT NULL,
-- entity_id uuid NOT NULL,
-- -- target_id uuid NOT NULL,
-- calculated_field_id uuid NOT NULL,
-- configuration varchar(10000),
-- CONSTRAINT calculated_field_link_unq_key UNIQUE (entity_id, calculated_field_id),
-- CONSTRAINT calculated_field_external_id_unq_key UNIQUE (tenant_id, external_id),
-- CONSTRAINT fk_calculated_field_id FOREIGN KEY (calculated_field_id) REFERENCES calculated_field(id) ON DELETE CASCADE
-- );
CREATE TABLE IF NOT EXISTS calculated_field_link (
id uuid NOT NULL CONSTRAINT calculated_field_link_pkey PRIMARY KEY,
created_time bigint NOT NULL,
tenant_id uuid NOT NULL,
entity_type VARCHAR(32),
entity_id uuid NOT NULL,
-- target_id uuid NOT NULL,
calculated_field_id uuid NOT NULL,
configuration varchar(1000000),
CONSTRAINT calculated_field_link_unq_key UNIQUE (entity_id, calculated_field_id),
CONSTRAINT fk_calculated_field_id FOREIGN KEY (calculated_field_id) REFERENCES calculated_field(id) ON DELETE CASCADE
);

View File

@ -23,22 +23,28 @@ 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.CalculatedFieldLink;
import org.thingsboard.server.common.data.id.CalculatedFieldId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.AbstractServiceTest;
import org.thingsboard.server.dao.service.DaoSqlTest;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@DaoSqlTest
public class CalculatedFieldServiceTest extends AbstractServiceTest {
private final DeviceId DEVICE_ID = new DeviceId(UUID.fromString("71c73816-361e-4e57-82ab-e1deaa8b7d66"));
@Autowired
private CalculatedFieldService calculatedFieldService;
@Autowired
private DeviceService deviceService;
private ListeningExecutorService executor;
@ -54,7 +60,8 @@ public class CalculatedFieldServiceTest extends AbstractServiceTest {
@Test
public void testSaveCalculatedField() {
CalculatedField calculatedField = getCalculatedField();
Device device = createTestDevice();
CalculatedField calculatedField = getCalculatedField(device.getId());
CalculatedField savedCalculatedField = calculatedFieldService.save(calculatedField);
assertThat(savedCalculatedField).isNotNull();
@ -77,10 +84,42 @@ public class CalculatedFieldServiceTest extends AbstractServiceTest {
}
@Test
public void testFindCalculatedFieldById() {
CalculatedField calculatedField = getCalculatedField();
CalculatedField savedCalculatedField = calculatedFieldService.save(calculatedField);
public void testSaveCalculatesFieldWithNonExistingDeviceId() {
CalculatedField calculatedField = getCalculatedField(new DeviceId(UUID.fromString("038f8668-c9fd-4f00-8501-ce20f2f93c22")));
assertThatThrownBy(() -> calculatedFieldService.save(calculatedField))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Device with id [" + calculatedField.getEntityId().getId() + "] does not exist.");
}
@Test
public void testSaveCalculatedFieldWithExistingName() {
Device device = createTestDevice();
CalculatedField calculatedField = getCalculatedField(device.getId());
calculatedFieldService.save(calculatedField);
assertThatThrownBy(() -> calculatedFieldService.save(calculatedField))
.isInstanceOf(DataValidationException.class)
.hasMessage("Calculated Field with such name is already in exists!");
}
@Test
public void testSaveCalculatedFieldWithExistingExternalId() {
Device device = createTestDevice();
CalculatedField calculatedField = getCalculatedField(device.getId());
calculatedField.setExternalId(new CalculatedFieldId(UUID.fromString("2ef69d0a-89cf-4868-86f8-c50551d87ebe")));
calculatedFieldService.save(calculatedField);
calculatedField.setName("Test 2");
assertThatThrownBy(() -> calculatedFieldService.save(calculatedField))
.isInstanceOf(DataValidationException.class)
.hasMessage("Calculated Field with such external id already exists!");
}
@Test
public void testFindCalculatedFieldById() {
CalculatedField savedCalculatedField = saveValidCalculatedField();
CalculatedField fetchedCalculatedField = calculatedFieldService.findById(tenantId, savedCalculatedField.getId());
assertThat(fetchedCalculatedField).isEqualTo(savedCalculatedField);
@ -90,18 +129,33 @@ public class CalculatedFieldServiceTest extends AbstractServiceTest {
@Test
public void testDeleteCalculatedField() {
CalculatedField calculatedField = getCalculatedField();
CalculatedField savedCalculatedField = calculatedFieldService.save(calculatedField);
CalculatedField savedCalculatedField = saveValidCalculatedField();
calculatedFieldService.deleteCalculatedField(tenantId, savedCalculatedField.getId());
assertThat(calculatedFieldService.findById(tenantId, savedCalculatedField.getId())).isNull();
}
private CalculatedField getCalculatedField() {
@Test
public void testSaveCalculatedFieldLinkIfCalculatedFieldForSuchEntityExists() {
CalculatedField savedCalculatedField = saveValidCalculatedField();
CalculatedFieldLink calculatedFieldLink = getCalculatedFieldLink(savedCalculatedField);
assertThatThrownBy(() -> calculatedFieldService.saveCalculatedFieldLink(tenantId, calculatedFieldLink))
.isInstanceOf(DataValidationException.class)
.hasMessage("Calculated Field for such entity id is already exists!");
}
private CalculatedField saveValidCalculatedField() {
Device device = createTestDevice();
CalculatedField calculatedField = getCalculatedField(device.getId());
return calculatedFieldService.save(calculatedField);
}
private CalculatedField getCalculatedField(DeviceId deviceId) {
CalculatedField calculatedField = new CalculatedField();
calculatedField.setTenantId(tenantId);
calculatedField.setEntityId(DEVICE_ID);
calculatedField.setEntityId(deviceId);
calculatedField.setType("Simple");
calculatedField.setName("Test Calculated Field");
calculatedField.setConfigurationVersion(1);
@ -120,4 +174,20 @@ public class CalculatedFieldServiceTest extends AbstractServiceTest {
return calculatedField;
}
private CalculatedFieldLink getCalculatedFieldLink(CalculatedField calculatedField) {
CalculatedFieldLink calculatedFieldLink = new CalculatedFieldLink();
calculatedFieldLink.setTenantId(tenantId);
calculatedFieldLink.setEntityId(calculatedField.getEntityId());
calculatedFieldLink.setConfiguration(calculatedField.getConfiguration());
calculatedFieldLink.setCalculatedFieldId(calculatedField.getId());
return calculatedFieldLink;
}
private Device createTestDevice() {
Device device = new Device();
device.setTenantId(tenantId);
device.setName("Test");
return deviceService.saveDevice(device);
}
}

View File

@ -0,0 +1,57 @@
/**
* 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.service.validator;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.thingsboard.server.common.data.calculated_field.CalculatedField;
import org.thingsboard.server.common.data.id.CalculatedFieldId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.calculated_field.CalculatedFieldDao;
import org.thingsboard.server.dao.exception.DataValidationException;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.BDDMockito.given;
@SpringBootTest(classes = CalculatedFieldDataValidator.class)
public class CalculatedFieldDataValidatorTest {
private final TenantId TENANT_ID = TenantId.fromUUID(UUID.fromString("7b5229e9-166e-41a9-a257-3b1dafad1b04"));
private final CalculatedFieldId CALCULATED_FIELD_ID = new CalculatedFieldId(UUID.fromString("060fbe45-fbb2-4549-abf3-f72a6be3cb9f"));
@MockBean
private CalculatedFieldDao calculatedFieldDao;
@SpyBean
private CalculatedFieldDataValidator validator;
@Test
public void testUpdateNonExistingCalculatedField() {
CalculatedField calculatedField = new CalculatedField(CALCULATED_FIELD_ID);
calculatedField.setType("Simple");
calculatedField.setName("Test");
given(calculatedFieldDao.findById(TENANT_ID, CALCULATED_FIELD_ID.getId())).willReturn(null);
assertThatThrownBy(() -> validator.validateUpdate(TENANT_ID, calculatedField))
.isInstanceOf(DataValidationException.class)
.hasMessage("Can't update non existing calculated field!");
}
}

View File

@ -0,0 +1,57 @@
/**
* 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.service.validator;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.thingsboard.server.common.data.calculated_field.CalculatedFieldLink;
import org.thingsboard.server.common.data.id.CalculatedFieldId;
import org.thingsboard.server.common.data.id.CalculatedFieldLinkId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.calculated_field.CalculatedFieldLinkDao;
import org.thingsboard.server.dao.exception.DataValidationException;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.BDDMockito.given;
@SpringBootTest(classes = CalculatedFieldLinkDataValidator.class)
public class CalculatedFieldLinkDataValidatorTest {
private final TenantId TENANT_ID = TenantId.fromUUID(UUID.fromString("2ba09d99-6143-43dc-b645-381fc0c43ebe"));
private final CalculatedFieldLinkId CALCULATED_FIELD_LINK_ID = new CalculatedFieldLinkId(UUID.fromString("a5609ef4-cb42-43ce-9b23-e090a4878d1c"));
@MockBean
private CalculatedFieldLinkDao calculatedFieldLinkDao;
@SpyBean
private CalculatedFieldLinkDataValidator validator;
@Test
public void testUpdateNonExistingCalculatedField() {
CalculatedFieldLink calculatedFieldLink = new CalculatedFieldLink(CALCULATED_FIELD_LINK_ID);
calculatedFieldLink.setCalculatedFieldId(new CalculatedFieldId(UUID.fromString("136477af-fd07-4498-b9c9-54fe50e82992")));
given(calculatedFieldLinkDao.findById(TENANT_ID, CALCULATED_FIELD_LINK_ID.getId())).willReturn(null);
assertThatThrownBy(() -> validator.validateUpdate(TENANT_ID, calculatedFieldLink))
.isInstanceOf(DataValidationException.class)
.hasMessage("Can't update non existing calculated field link!");
}
}