Browse Source

Refactor cf vc

pull/12707/head
Andrii Landiak 1 year ago
parent
commit
f467fc009e
  1. 7
      application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java
  2. 18
      application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/AssetExportService.java
  3. 17
      application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/AssetProfileExportService.java
  4. 51
      application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/BaseCalculatedFieldsExportService.java
  5. 21
      application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DefaultEntityExportService.java
  6. 11
      application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DeviceExportService.java
  7. 17
      application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DeviceProfileExportService.java
  8. 22
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/AssetImportService.java
  9. 22
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/AssetProfileImportService.java
  10. 123
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/BaseCalculatedFieldsImportService.java
  11. 57
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/BaseEntityImportService.java
  12. 40
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/DeviceImportService.java
  13. 22
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/DeviceProfileImportService.java
  14. 6
      application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntitiesImportCtx.java
  15. 2
      application/src/test/java/org/thingsboard/server/service/sync/ie/ExportImportServiceSqlTest.java
  16. 6
      application/src/test/java/org/thingsboard/server/service/sync/vc/VersionControlTest.java
  17. 37
      common/data/src/main/java/org/thingsboard/server/common/data/cf/CalculatedField.java
  18. 3
      common/data/src/main/java/org/thingsboard/server/common/data/id/CalculatedFieldId.java
  19. 4
      common/data/src/main/java/org/thingsboard/server/common/data/sync/JsonTbEntity.java
  20. 28
      common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/AssetExportData.java
  21. 28
      common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/AssetProfileExportData.java
  22. 56
      common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/CalculatedFieldExportData.java
  23. 2
      common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/DeviceExportData.java
  24. 28
      common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/DeviceProfileExportData.java
  25. 14
      common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/EntityExportData.java
  26. 3
      dao/src/main/java/org/thingsboard/server/dao/cf/CalculatedFieldDao.java
  27. 1
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  28. 10
      dao/src/main/java/org/thingsboard/server/dao/model/sql/CalculatedFieldEntity.java
  29. 11
      dao/src/main/java/org/thingsboard/server/dao/sql/cf/CalculatedFieldRepository.java
  30. 1
      dao/src/main/java/org/thingsboard/server/dao/sql/cf/DefaultNativeCalculatedFieldRepository.java
  31. 20
      dao/src/main/java/org/thingsboard/server/dao/sql/cf/JpaCalculatedFieldDao.java
  32. 4
      dao/src/main/resources/sql/schema-entities.sql
  33. 14
      dao/src/test/java/org/thingsboard/server/dao/service/CalculatedFieldServiceTest.java

7
application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java

@ -23,6 +23,7 @@ import org.thingsboard.server.cache.limits.RateLimitService;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.ExportableEntity;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.cf.CalculatedField;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.EntityId;
@ -129,10 +130,10 @@ public class DefaultEntitiesExportImportService implements EntitiesExportImportS
relation, ctx.getUser(), ActionType.RELATION_ADD_OR_UPDATE, null, relation);
}
ctx.getCalculatedFields().forEach((calculatedField, created) -> {
for (CalculatedField calculatedField : ctx.getCalculatedFields()) {
var savedCalculatedField = calculatedFieldService.save(calculatedField);
logEntityActionService.logEntityAction(ctx.getTenantId(), savedCalculatedField.getId(), savedCalculatedField, created ? ActionType.ADDED : ActionType.UPDATED, ctx.getUser());
});
logEntityActionService.logEntityAction(ctx.getTenantId(), savedCalculatedField.getId(), savedCalculatedField, ActionType.ADDED, ctx.getUser());
}
}
@Override

18
application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/AssetExportService.java

@ -15,12 +15,12 @@
*/
package org.thingsboard.server.service.sync.ie.exporting.impl;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.sync.ie.AssetExportData;
import org.thingsboard.server.dao.cf.CalculatedFieldService;
import org.thingsboard.server.common.data.sync.ie.EntityExportData;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.sync.vc.data.EntitiesExportCtx;
@ -28,22 +28,18 @@ import java.util.Set;
@Service
@TbCoreComponent
public class AssetExportService extends BaseCalculatedFieldsExportService<AssetId, Asset, AssetExportData> {
protected AssetExportService(CalculatedFieldService calculatedFieldService) {
super(calculatedFieldService);
}
@RequiredArgsConstructor
public class AssetExportService extends BaseEntityExportService<AssetId, Asset, EntityExportData<Asset>> {
@Override
protected void setRelatedEntities(EntitiesExportCtx<?> ctx, Asset asset, AssetExportData exportData) {
protected void setRelatedEntities(EntitiesExportCtx<?> ctx, Asset asset, EntityExportData<Asset> exportData) {
asset.setCustomerId(getExternalIdOrElseInternal(ctx, asset.getCustomerId()));
asset.setAssetProfileId(getExternalIdOrElseInternal(ctx, asset.getAssetProfileId()));
setCalculatedFields(ctx, asset, exportData);
}
@Override
protected AssetExportData newExportData() {
return new AssetExportData();
protected EntityExportData<Asset> newExportData() {
return new EntityExportData<>();
}
@Override

17
application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/AssetProfileExportService.java

@ -19,8 +19,7 @@ import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.sync.ie.AssetProfileExportData;
import org.thingsboard.server.dao.cf.CalculatedFieldService;
import org.thingsboard.server.common.data.sync.ie.EntityExportData;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.sync.vc.data.EntitiesExportCtx;
@ -28,23 +27,13 @@ import java.util.Set;
@Service
@TbCoreComponent
public class AssetProfileExportService extends BaseCalculatedFieldsExportService<AssetProfileId, AssetProfile, AssetProfileExportData> {
protected AssetProfileExportService(CalculatedFieldService calculatedFieldService) {
super(calculatedFieldService);
}
public class AssetProfileExportService extends BaseEntityExportService<AssetProfileId, AssetProfile, EntityExportData<AssetProfile>> {
@Override
protected void setRelatedEntities(EntitiesExportCtx<?> ctx, AssetProfile assetProfile, AssetProfileExportData exportData) {
protected void setRelatedEntities(EntitiesExportCtx<?> ctx, AssetProfile assetProfile, EntityExportData<AssetProfile> exportData) {
assetProfile.setDefaultDashboardId(getExternalIdOrElseInternal(ctx, assetProfile.getDefaultDashboardId()));
assetProfile.setDefaultRuleChainId(getExternalIdOrElseInternal(ctx, assetProfile.getDefaultRuleChainId()));
assetProfile.setDefaultEdgeRuleChainId(getExternalIdOrElseInternal(ctx, assetProfile.getDefaultEdgeRuleChainId()));
setCalculatedFields(ctx, assetProfile, exportData);
}
@Override
protected AssetProfileExportData newExportData() {
return new AssetProfileExportData();
}
@Override

51
application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/BaseCalculatedFieldsExportService.java

@ -1,51 +0,0 @@
/**
* Copyright © 2016-2025 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.sync.ie.exporting.impl;
import org.thingsboard.server.common.data.ExportableEntity;
import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.cf.CalculatedField;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.sync.ie.CalculatedFieldExportData;
import org.thingsboard.server.dao.cf.CalculatedFieldService;
import org.thingsboard.server.service.sync.vc.data.EntitiesExportCtx;
import java.util.List;
public abstract class BaseCalculatedFieldsExportService<ID extends EntityId, E extends ExportableEntity<ID> & HasTenantId, D extends CalculatedFieldExportData<E>> extends BaseEntityExportService<ID, E, D> {
protected final CalculatedFieldService calculatedFieldService;
protected BaseCalculatedFieldsExportService(CalculatedFieldService calculatedFieldService) {
this.calculatedFieldService = calculatedFieldService;
}
protected void setCalculatedFields(EntitiesExportCtx<?> ctx, E entity, D exportData) {
if (ctx.getSettings().isExportCalculatedFields()) {
List<CalculatedField> calculatedFields = calculatedFieldService.findCalculatedFieldsByEntityId(ctx.getTenantId(), entity.getId());
calculatedFields.forEach(calculatedField -> {
calculatedField.getConfiguration().getArguments().values().forEach(argument -> {
if (argument.getRefEntityId() != null) {
EntityId externalId = getExternalIdOrElseInternal(ctx, argument.getRefEntityId());
argument.setRefEntityId(externalId);
}
});
});
exportData.setCalculatedFields(calculatedFields);
}
}
}

21
application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DefaultEntityExportService.java

@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.ExportableEntity;
import org.thingsboard.server.common.data.HasVersion;
import org.thingsboard.server.common.data.cf.CalculatedField;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
@ -31,6 +32,7 @@ import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.sync.ie.AttributeExportData;
import org.thingsboard.server.common.data.sync.ie.EntityExportData;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.cf.CalculatedFieldService;
import org.thingsboard.server.dao.relation.RelationDao;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.sync.ie.exporting.EntityExportService;
@ -59,6 +61,8 @@ public class DefaultEntityExportService<I extends EntityId, E extends Exportable
private RelationDao relationDao;
@Autowired
private AttributesService attributesService;
@Autowired
private CalculatedFieldService calculatedFieldService;
@Override
public final D getExportData(EntitiesExportCtx<?> ctx, I entityId) throws ThingsboardException {
@ -98,6 +102,10 @@ public class DefaultEntityExportService<I extends EntityId, E extends Exportable
Map<String, List<AttributeExportData>> attributes = exportAttributes(ctx, entity);
exportData.setAttributes(attributes);
}
if (ctx.getSettings().isExportCalculatedFields()) {
List<CalculatedField> calculatedFields = exportCalculatedFields(ctx, entity.getId());
exportData.setCalculatedFields(calculatedFields);
}
}
private List<EntityRelation> exportRelations(EntitiesExportCtx<?> ctx, E entity) throws ThingsboardException {
@ -141,6 +149,19 @@ public class DefaultEntityExportService<I extends EntityId, E extends Exportable
return attributes;
}
private List<CalculatedField> exportCalculatedFields(EntitiesExportCtx<?> ctx, EntityId entityId) {
List<CalculatedField> calculatedFields = calculatedFieldService.findCalculatedFieldsByEntityId(ctx.getTenantId(), entityId);
calculatedFields.forEach(calculatedField -> {
calculatedField.setEntityId(getExternalIdOrElseInternal(ctx, entityId));
calculatedField.getConfiguration().getArguments().values().forEach(argument -> {
if (argument.getRefEntityId() != null) {
argument.setRefEntityId(getExternalIdOrElseInternal(ctx, argument.getRefEntityId()));
}
});
});
return calculatedFields;
}
protected <ID extends EntityId> ID getExternalIdOrElseInternal(EntitiesExportCtx<?> ctx, ID internalId) {
if (internalId == null || internalId.isNullUid()) return internalId;
var result = ctx.getExternalId(internalId);

11
application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DeviceExportService.java

@ -15,12 +15,12 @@
*/
package org.thingsboard.server.service.sync.ie.exporting.impl;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.sync.ie.DeviceExportData;
import org.thingsboard.server.dao.cf.CalculatedFieldService;
import org.thingsboard.server.dao.device.DeviceCredentialsService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.sync.vc.data.EntitiesExportCtx;
@ -29,15 +29,11 @@ import java.util.Set;
@Service
@TbCoreComponent
public class DeviceExportService extends BaseCalculatedFieldsExportService<DeviceId, Device, DeviceExportData> {
@RequiredArgsConstructor
public class DeviceExportService extends BaseEntityExportService<DeviceId, Device, DeviceExportData> {
private final DeviceCredentialsService deviceCredentialsService;
public DeviceExportService(CalculatedFieldService calculatedFieldService, DeviceCredentialsService deviceCredentialsService) {
super(calculatedFieldService);
this.deviceCredentialsService = deviceCredentialsService;
}
@Override
protected void setRelatedEntities(EntitiesExportCtx<?> ctx, Device device, DeviceExportData exportData) {
device.setCustomerId(getExternalIdOrElseInternal(ctx, device.getCustomerId()));
@ -48,7 +44,6 @@ public class DeviceExportService extends BaseCalculatedFieldsExportService<Devic
credentials.setDeviceId(null);
exportData.setCredentials(credentials);
}
setCalculatedFields(ctx, device, exportData);
}
@Override

17
application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DeviceProfileExportService.java

@ -19,8 +19,7 @@ import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.sync.ie.DeviceProfileExportData;
import org.thingsboard.server.dao.cf.CalculatedFieldService;
import org.thingsboard.server.common.data.sync.ie.EntityExportData;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.sync.vc.data.EntitiesExportCtx;
@ -28,23 +27,13 @@ import java.util.Set;
@Service
@TbCoreComponent
public class DeviceProfileExportService extends BaseCalculatedFieldsExportService<DeviceProfileId, DeviceProfile, DeviceProfileExportData> {
protected DeviceProfileExportService(CalculatedFieldService calculatedFieldService) {
super(calculatedFieldService);
}
public class DeviceProfileExportService extends BaseEntityExportService<DeviceProfileId, DeviceProfile, EntityExportData<DeviceProfile>> {
@Override
protected void setRelatedEntities(EntitiesExportCtx<?> ctx, DeviceProfile deviceProfile, DeviceProfileExportData exportData) {
protected void setRelatedEntities(EntitiesExportCtx<?> ctx, DeviceProfile deviceProfile, EntityExportData<DeviceProfile> exportData) {
deviceProfile.setDefaultDashboardId(getExternalIdOrElseInternal(ctx, deviceProfile.getDefaultDashboardId()));
deviceProfile.setDefaultRuleChainId(getExternalIdOrElseInternal(ctx, deviceProfile.getDefaultRuleChainId()));
deviceProfile.setDefaultEdgeRuleChainId(getExternalIdOrElseInternal(ctx, deviceProfile.getDefaultEdgeRuleChainId()));
setCalculatedFields(ctx, deviceProfile, exportData);
}
@Override
protected DeviceProfileExportData newExportData() {
return new DeviceProfileExportData();
}
@Override

22
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/AssetImportService.java

@ -15,28 +15,24 @@
*/
package org.thingsboard.server.service.sync.ie.importing.impl;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.sync.ie.AssetExportData;
import org.thingsboard.server.common.data.sync.ie.EntityExportData;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.cf.CalculatedFieldService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx;
@Service
@TbCoreComponent
public class AssetImportService extends BaseCalculatedFieldsImportService<AssetId, Asset, AssetExportData> {
@RequiredArgsConstructor
public class AssetImportService extends BaseEntityImportService<AssetId, Asset, EntityExportData<Asset>> {
private final AssetService assetService;
public AssetImportService(CalculatedFieldService calculatedFieldService, AssetService assetService) {
super(calculatedFieldService);
this.assetService = assetService;
}
@Override
protected void setOwner(TenantId tenantId, Asset asset, IdProvider idProvider) {
asset.setTenantId(tenantId);
@ -44,14 +40,18 @@ public class AssetImportService extends BaseCalculatedFieldsImportService<AssetI
}
@Override
protected Asset prepare(EntitiesImportCtx ctx, Asset asset, Asset old, AssetExportData exportData, IdProvider idProvider) {
protected Asset prepare(EntitiesImportCtx ctx, Asset asset, Asset old, EntityExportData<Asset> exportData, IdProvider idProvider) {
asset.setAssetProfileId(idProvider.getInternalId(asset.getAssetProfileId()));
return asset;
}
@Override
protected Asset saveOrUpdate(EntitiesImportCtx ctx, Asset asset, AssetExportData exportData, IdProvider idProvider) {
return saveOrUpdateEntity(ctx, asset, exportData, idProvider, assetService::saveAsset);
protected Asset saveOrUpdate(EntitiesImportCtx ctx, Asset asset, EntityExportData<Asset> exportData, IdProvider idProvider) {
Asset savedAsset = assetService.saveAsset(asset);
if (ctx.isFinalImportAttempt() || ctx.getCurrentImportResult().isUpdatedAllExternalIds()) {
importCalculatedFields(ctx, savedAsset, exportData, idProvider);
}
return savedAsset;
}
@Override

22
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/AssetProfileImportService.java

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.service.sync.ie.importing.impl;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.User;
@ -22,30 +23,25 @@ import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.sync.ie.AssetProfileExportData;
import org.thingsboard.server.common.data.sync.ie.EntityExportData;
import org.thingsboard.server.dao.asset.AssetProfileService;
import org.thingsboard.server.dao.cf.CalculatedFieldService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx;
@Service
@TbCoreComponent
public class AssetProfileImportService extends BaseCalculatedFieldsImportService<AssetProfileId, AssetProfile, AssetProfileExportData> {
@RequiredArgsConstructor
public class AssetProfileImportService extends BaseEntityImportService<AssetProfileId, AssetProfile, EntityExportData<AssetProfile>> {
private final AssetProfileService assetProfileService;
public AssetProfileImportService(CalculatedFieldService calculatedFieldService, AssetProfileService assetProfileService) {
super(calculatedFieldService);
this.assetProfileService = assetProfileService;
}
@Override
protected void setOwner(TenantId tenantId, AssetProfile assetProfile, IdProvider idProvider) {
assetProfile.setTenantId(tenantId);
}
@Override
protected AssetProfile prepare(EntitiesImportCtx ctx, AssetProfile assetProfile, AssetProfile old, AssetProfileExportData exportData, IdProvider idProvider) {
protected AssetProfile prepare(EntitiesImportCtx ctx, AssetProfile assetProfile, AssetProfile old, EntityExportData<AssetProfile> exportData, IdProvider idProvider) {
assetProfile.setDefaultRuleChainId(idProvider.getInternalId(assetProfile.getDefaultRuleChainId()));
assetProfile.setDefaultDashboardId(idProvider.getInternalId(assetProfile.getDefaultDashboardId()));
assetProfile.setDefaultEdgeRuleChainId(idProvider.getInternalId(assetProfile.getDefaultEdgeRuleChainId()));
@ -53,8 +49,12 @@ public class AssetProfileImportService extends BaseCalculatedFieldsImportService
}
@Override
protected AssetProfile saveOrUpdate(EntitiesImportCtx ctx, AssetProfile assetProfile, AssetProfileExportData exportData, IdProvider idProvider) {
return saveOrUpdateEntity(ctx, assetProfile, exportData, idProvider, assetProfileService::saveAssetProfile);
protected AssetProfile saveOrUpdate(EntitiesImportCtx ctx, AssetProfile assetProfile, EntityExportData<AssetProfile> exportData, IdProvider idProvider) {
AssetProfile saved = assetProfileService.saveAssetProfile(assetProfile);
if (ctx.isFinalImportAttempt() || ctx.getCurrentImportResult().isUpdatedAllExternalIds()) {
importCalculatedFields(ctx, saved, exportData, idProvider);
}
return saved;
}
@Override

123
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/BaseCalculatedFieldsImportService.java

@ -1,123 +0,0 @@
/**
* Copyright © 2016-2025 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.sync.ie.importing.impl;
import org.thingsboard.server.common.data.ExportableEntity;
import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.cf.CalculatedField;
import org.thingsboard.server.common.data.id.CalculatedFieldId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.sync.ie.CalculatedFieldExportData;
import org.thingsboard.server.dao.cf.CalculatedFieldService;
import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
public abstract class BaseCalculatedFieldsImportService<ID extends EntityId, E extends ExportableEntity<ID> & HasTenantId, D extends CalculatedFieldExportData<E>> extends BaseEntityImportService<ID, E, D> {
private final CalculatedFieldService calculatedFieldService;
protected BaseCalculatedFieldsImportService(CalculatedFieldService calculatedFieldService) {
this.calculatedFieldService = calculatedFieldService;
}
protected E saveOrUpdateEntity(EntitiesImportCtx ctx, E entity, D exportData, IdProvider idProvider, Function<E, E> saveFunction) {
E savedEntity = saveFunction.apply(entity);
if (ctx.isFinalImportAttempt() || ctx.getCurrentImportResult().isUpdatedAllExternalIds()) {
saveCalculatedFields(ctx, savedEntity, exportData, idProvider);
}
return savedEntity;
}
protected void saveCalculatedFields(EntitiesImportCtx ctx, E savedEntity, D exportData, IdProvider idProvider) {
if (exportData.getCalculatedFields() == null || !ctx.isSaveCalculatedFields()) {
return;
}
exportData.getCalculatedFields().forEach(calculatedField -> {
calculatedField.setTenantId(savedEntity.getTenantId());
calculatedField.setExternalId(calculatedField.getId());
calculatedField.setId(idProvider.getInternalId(calculatedField.getId(), false));
calculatedField.setEntityId(savedEntity.getId());
calculatedField.getConfiguration().getArguments().values().forEach(argument -> {
if (argument.getRefEntityId() != null) {
argument.setRefEntityId(idProvider.getInternalId(argument.getRefEntityId(), false));
}
});
calculatedFieldService.save(calculatedField);
});
}
@Override
protected boolean updateRelatedEntitiesIfUnmodified(EntitiesImportCtx ctx, E prepared, D exportData, IdProvider idProvider) {
boolean updated = super.updateRelatedEntitiesIfUnmodified(ctx, prepared, exportData, idProvider);
updated |= updateCalculatedFields(ctx, prepared, exportData, idProvider);
return updated;
}
private boolean updateCalculatedFields(EntitiesImportCtx ctx, E prepared, D exportData, IdProvider idProvider) {
var calculatedFields = exportData.getCalculatedFields();
if (calculatedFields == null || !ctx.isSaveCalculatedFields()) {
return false;
}
Map<CalculatedFieldId, CalculatedField> calculatedFieldMap = calculatedFields.stream()
.peek(newField -> {
newField.setTenantId(ctx.getTenantId());
newField.setExternalId(newField.getId());
newField.setId(idProvider.getInternalId(newField.getId(), false));
newField.setEntityId(prepared.getId());
newField.getConfiguration().getArguments().values().forEach(argument -> {
argument.setRefEntityId(idProvider.getInternalId(argument.getRefEntityId(), false));
});
})
.collect(Collectors.toMap(CalculatedField::getId, field -> field));
List<CalculatedField> existingFields = calculatedFieldService.findCalculatedFieldsByEntityId(ctx.getTenantId(), prepared.getId());
boolean updated = false;
Map<CalculatedField, Boolean> result = new LinkedHashMap<>();
for (CalculatedField existingField : existingFields) {
if (calculatedFieldMap.containsKey(existingField.getId())) {
CalculatedField newField = calculatedFieldMap.get(existingField.getId());
if (!newField.equals(existingField)) {
result.put(newField, false);
}
calculatedFieldMap.remove(existingField.getId());
} else {
updated = true;
calculatedFieldService.deleteCalculatedField(ctx.getTenantId(), existingField.getId());
}
}
for (CalculatedField newField : calculatedFieldMap.values()) {
result.put(newField, true);
}
if (!result.isEmpty()) {
updated = true;
ctx.addCalculatedFields(result);
}
return updated;
}
}

57
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/BaseEntityImportService.java

@ -32,6 +32,7 @@ import org.thingsboard.server.common.data.HasDefaultOption;
import org.thingsboard.server.common.data.HasVersion;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.cf.CalculatedField;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
@ -50,6 +51,7 @@ import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.sync.ie.AttributeExportData;
import org.thingsboard.server.common.data.sync.ie.EntityExportData;
import org.thingsboard.server.common.data.sync.ie.EntityImportResult;
import org.thingsboard.server.dao.cf.CalculatedFieldService;
import org.thingsboard.server.dao.relation.RelationDao;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.service.action.EntityActionService;
@ -78,6 +80,8 @@ public abstract class BaseEntityImportService<I extends EntityId, E extends Expo
@Lazy
private ExportableEntitiesService entitiesService;
@Autowired
private CalculatedFieldService calculatedFieldService;
@Autowired
private RelationService relationService;
@Autowired
private RelationDao relationDao;
@ -134,7 +138,7 @@ public abstract class BaseEntityImportService<I extends EntityId, E extends Expo
}
protected boolean updateRelatedEntitiesIfUnmodified(EntitiesImportCtx ctx, E prepared, D exportData, IdProvider idProvider) {
return false;
return importCalculatedFields(ctx, prepared, exportData, idProvider);
}
@Override
@ -278,12 +282,59 @@ public abstract class BaseEntityImportService<I extends EntityId, E extends Expo
});
}
protected boolean importCalculatedFields(EntitiesImportCtx ctx, E savedEntity, D exportData, IdProvider idProvider) {
if (exportData.getCalculatedFields() == null || !ctx.isSaveCalculatedFields()) {
return false;
}
boolean updated = false;
List<CalculatedField> existing = calculatedFieldService.findCalculatedFieldsByEntityId(ctx.getTenantId(), savedEntity.getId());
List<CalculatedField> fieldsToSave = exportData.getCalculatedFields().stream()
.peek(calculatedField -> {
calculatedField.setTenantId(ctx.getTenantId());
calculatedField.setEntityId(savedEntity.getId());
calculatedField.getConfiguration().getArguments().values().forEach(argument -> {
if (argument.getRefEntityId() != null) {
argument.setRefEntityId(idProvider.getInternalId(argument.getRefEntityId(), false));
}
});
}).toList();
for (CalculatedField existingField : existing) {
boolean found = fieldsToSave.stream().anyMatch(importedField -> compareCalculatedFields(existingField, importedField));
if (!found) {
calculatedFieldService.deleteCalculatedField(ctx.getTenantId(), existingField.getId());
updated = true;
}
}
for (CalculatedField calculatedField : fieldsToSave) {
boolean found = existing.stream().anyMatch(existingField -> compareCalculatedFields(existingField, calculatedField));
if (!found) {
calculatedFieldService.save(calculatedField);
updated = true;
}
}
return updated;
}
private boolean compareCalculatedFields(CalculatedField existingField, CalculatedField newField) {
CalculatedField oldCopy = new CalculatedField(existingField);
CalculatedField newCopy = new CalculatedField(newField);
oldCopy.setId(null);
newCopy.setId(null);
oldCopy.setVersion(null);
newCopy.setVersion(null);
oldCopy.setCreatedTime(0);
newCopy.setCreatedTime(0);
return oldCopy.equals(newCopy);
}
protected void onEntitySaved(User user, E savedEntity, E oldEntity) throws ThingsboardException {
logEntityActionService.logEntityAction(user.getTenantId(), savedEntity.getId(), savedEntity, null,
oldEntity == null ? ActionType.ADDED : ActionType.UPDATED, user);
}
@SuppressWarnings("unchecked")
protected E findExistingEntity(EntitiesImportCtx ctx, E entity, IdProvider idProvider) {
return (E) Optional.ofNullable(entitiesService.findEntityByTenantIdAndExternalId(ctx.getTenantId(), entity.getId()))
@ -313,10 +364,10 @@ public abstract class BaseEntityImportService<I extends EntityId, E extends Expo
.orElseThrow(() -> new MissingEntityException(externalId));
}
@SuppressWarnings("unchecked")
@RequiredArgsConstructor
protected class IdProvider {
private final EntitiesImportCtx ctx;
private final EntityImportResult<E> importResult;

40
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/DeviceImportService.java

@ -15,13 +15,13 @@
*/
package org.thingsboard.server.service.sync.ie.importing.impl;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.sync.ie.DeviceExportData;
import org.thingsboard.server.dao.cf.CalculatedFieldService;
import org.thingsboard.server.dao.device.DeviceCredentialsService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.queue.util.TbCoreComponent;
@ -29,17 +29,12 @@ import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx;
@Service
@TbCoreComponent
public class DeviceImportService extends BaseCalculatedFieldsImportService<DeviceId, Device, DeviceExportData> {
@RequiredArgsConstructor
public class DeviceImportService extends BaseEntityImportService<DeviceId, Device, DeviceExportData> {
private final DeviceService deviceService;
private final DeviceCredentialsService credentialsService;
public DeviceImportService(CalculatedFieldService calculatedFieldService, DeviceService deviceService, DeviceCredentialsService credentialsService) {
super(calculatedFieldService);
this.deviceService = deviceService;
this.credentialsService = credentialsService;
}
@Override
protected void setOwner(TenantId tenantId, Device device, IdProvider idProvider) {
device.setTenantId(tenantId);
@ -78,7 +73,7 @@ public class DeviceImportService extends BaseCalculatedFieldsImportService<Devic
savedDevice = deviceService.saveDevice(device);
}
if (ctx.isFinalImportAttempt() || ctx.getCurrentImportResult().isUpdatedAllExternalIds()) {
saveCalculatedFields(ctx, savedDevice, exportData, idProvider);
importCalculatedFields(ctx, savedDevice, exportData, idProvider);
}
return savedDevice;
}
@ -86,25 +81,18 @@ public class DeviceImportService extends BaseCalculatedFieldsImportService<Devic
@Override
protected boolean updateRelatedEntitiesIfUnmodified(EntitiesImportCtx ctx, Device prepared, DeviceExportData exportData, IdProvider idProvider) {
boolean updated = super.updateRelatedEntitiesIfUnmodified(ctx, prepared, exportData, idProvider);
updated |= updateCredentials(ctx, prepared, exportData);
return updated;
}
private boolean updateCredentials(EntitiesImportCtx ctx, Device prepared, DeviceExportData exportData) {
var credentials = exportData.getCredentials();
if (credentials == null || !ctx.isSaveCredentials()) {
return false;
if (credentials != null && ctx.isSaveCredentials()) {
var existing = credentialsService.findDeviceCredentialsByDeviceId(ctx.getTenantId(), prepared.getId());
credentials.setId(existing.getId());
credentials.setDeviceId(prepared.getId());
if (!existing.equals(credentials)) {
credentialsService.updateDeviceCredentials(ctx.getTenantId(), credentials);
updated = true;
}
}
var existing = credentialsService.findDeviceCredentialsByDeviceId(ctx.getTenantId(), prepared.getId());
credentials.setId(existing.getId());
credentials.setDeviceId(prepared.getId());
if (!existing.equals(credentials)) {
credentialsService.updateDeviceCredentials(ctx.getTenantId(), credentials);
return true;
}
return false;
updated |= importCalculatedFields(ctx, prepared, exportData, idProvider);
return updated;
}
@Override

22
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/DeviceProfileImportService.java

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.service.sync.ie.importing.impl;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EntityType;
@ -22,30 +23,25 @@ import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.sync.ie.DeviceProfileExportData;
import org.thingsboard.server.dao.cf.CalculatedFieldService;
import org.thingsboard.server.common.data.sync.ie.EntityExportData;
import org.thingsboard.server.dao.device.DeviceProfileService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx;
@Service
@TbCoreComponent
public class DeviceProfileImportService extends BaseCalculatedFieldsImportService<DeviceProfileId, DeviceProfile, DeviceProfileExportData> {
@RequiredArgsConstructor
public class DeviceProfileImportService extends BaseEntityImportService<DeviceProfileId, DeviceProfile, EntityExportData<DeviceProfile>> {
private final DeviceProfileService deviceProfileService;
public DeviceProfileImportService(CalculatedFieldService calculatedFieldService, DeviceProfileService deviceProfileService) {
super(calculatedFieldService);
this.deviceProfileService = deviceProfileService;
}
@Override
protected void setOwner(TenantId tenantId, DeviceProfile deviceProfile, IdProvider idProvider) {
deviceProfile.setTenantId(tenantId);
}
@Override
protected DeviceProfile prepare(EntitiesImportCtx ctx, DeviceProfile deviceProfile, DeviceProfile old, DeviceProfileExportData exportData, IdProvider idProvider) {
protected DeviceProfile prepare(EntitiesImportCtx ctx, DeviceProfile deviceProfile, DeviceProfile old, EntityExportData<DeviceProfile> exportData, IdProvider idProvider) {
deviceProfile.setDefaultRuleChainId(idProvider.getInternalId(deviceProfile.getDefaultRuleChainId()));
deviceProfile.setDefaultEdgeRuleChainId(idProvider.getInternalId(deviceProfile.getDefaultEdgeRuleChainId()));
deviceProfile.setDefaultDashboardId(idProvider.getInternalId(deviceProfile.getDefaultDashboardId()));
@ -55,8 +51,12 @@ public class DeviceProfileImportService extends BaseCalculatedFieldsImportServic
}
@Override
protected DeviceProfile saveOrUpdate(EntitiesImportCtx ctx, DeviceProfile deviceProfile, DeviceProfileExportData exportData, IdProvider idProvider) {
return saveOrUpdateEntity(ctx, deviceProfile, exportData, idProvider, deviceProfileService::saveDeviceProfile);
protected DeviceProfile saveOrUpdate(EntitiesImportCtx ctx, DeviceProfile deviceProfile, EntityExportData<DeviceProfile> exportData, IdProvider idProvider) {
DeviceProfile saved = deviceProfileService.saveDeviceProfile(deviceProfile);
if (ctx.isFinalImportAttempt() || ctx.getCurrentImportResult().isUpdatedAllExternalIds()) {
importCalculatedFields(ctx, saved, exportData, idProvider);
}
return saved;
}
@Override

6
application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntitiesImportCtx.java

@ -56,7 +56,7 @@ public class EntitiesImportCtx {
private final Set<EntityId> notFoundIds = new HashSet<>();
private final Set<EntityRelation> relations = new LinkedHashSet<>();
private final Map<CalculatedField, Boolean> calculatedFields = new LinkedHashMap<>();
private final Set<CalculatedField> calculatedFields = new LinkedHashSet<>();
private boolean finalImportAttempt = false;
private EntityImportSettings settings;
@ -127,8 +127,8 @@ public class EntitiesImportCtx {
relations.addAll(values);
}
public void addCalculatedFields(Map<CalculatedField, Boolean> calculatedFieldMap) {
calculatedFields.putAll(calculatedFieldMap);
public void addCalculatedFields(Collection<CalculatedField> values) {
calculatedFields.addAll(values);
}
public void addReferenceCallback(EntityId externalId, ThrowingRunnable tr) {

2
application/src/test/java/org/thingsboard/server/service/sync/ie/ExportImportServiceSqlTest.java

@ -288,7 +288,6 @@ public class ExportImportServiceSqlTest extends AbstractControllerTest {
assertThat(calculatedFields.size()).isOne();
var importedCalculatedField = calculatedFields.get(0);
assertThat(importedCalculatedField.getName()).isEqualTo(calculatedField.getName());
assertThat(importedCalculatedField.getExternalId()).isEqualTo(calculatedField.getId());
EntityExportData<Device> updatedDeviceEntity = getAndClone(entitiesExportData, EntityType.DEVICE);
updatedDeviceEntity.getEntity().setLabel("t" + updatedDeviceEntity.getEntity().getLabel());
@ -303,7 +302,6 @@ public class ExportImportServiceSqlTest extends AbstractControllerTest {
calculatedFields = calculatedFieldService.findCalculatedFieldsByEntityId(tenantId2, importedDevice.getId());
assertThat(calculatedFields.size()).isOne();
importedCalculatedField = calculatedFields.get(0);
assertThat(importedCalculatedField.getExternalId()).isEqualTo(calculatedField.getId());
assertThat(importedCalculatedField.getName()).startsWith("t_");
}

6
application/src/test/java/org/thingsboard/server/service/sync/vc/VersionControlTest.java

@ -575,7 +575,7 @@ public class VersionControlTest extends AbstractControllerTest {
public void testVcWithCalculatedFields_betweenTenants() throws Exception {
Asset asset = createAsset(null, null, "Asset 1");
Device device = createDevice(null, null, "Device 1", "test1");
CalculatedField calculatedField = createCalculatedField("CalculatedField1", device.getId(), asset.getId());
createCalculatedField("CalculatedField1", device.getId(), asset.getId());
String versionId = createVersion("calculated fields of asset and device", EntityType.ASSET, EntityType.DEVICE, EntityType.DEVICE_PROFILE, EntityType.ASSET_PROFILE);
loginTenant2();
@ -588,13 +588,13 @@ public class VersionControlTest extends AbstractControllerTest {
CalculatedField importedCalculatedField = findCalculatedFieldByEntityId(importedDevice.getId());
checkImportedEntity(tenantId1, device, tenantId2, importedDevice);
checkImportedEntity(tenantId1, asset, tenantId2, importedAsset);
checkImportedEntity(tenantId1, calculatedField, tenantId2, importedCalculatedField);
List<CalculatedField> importedCalculatedFields = findCalculatedFieldsByEntityId(importedDevice.getId());
assertThat(importedCalculatedFields).size().isOne();
assertThat(importedCalculatedFields.get(0)).satisfies(importedField -> {
assertThat(importedField.getName()).isEqualTo(importedCalculatedField.getName());
assertThat(importedField.getType()).isEqualTo(importedCalculatedField.getType());
assertThat(importedField.getId()).isNotEqualTo(importedCalculatedField.getId());
});
}
@ -606,7 +606,7 @@ public class VersionControlTest extends AbstractControllerTest {
loadVersion(versionId, EntityType.ASSET);
CalculatedField importedCalculatedField = findCalculatedFieldByEntityId(asset.getId());
checkImportedEntity(tenantId1, calculatedField, tenantId1, importedCalculatedField);
assertThat(importedCalculatedField.getId()).isEqualTo(calculatedField.getId());
assertThat(importedCalculatedField.getName()).isEqualTo(calculatedField.getName());
assertThat(importedCalculatedField.getConfiguration()).isEqualTo(calculatedField.getConfiguration());
assertThat(importedCalculatedField.getType()).isEqualTo(calculatedField.getType());

37
common/data/src/main/java/org/thingsboard/server/common/data/cf/CalculatedField.java

@ -23,7 +23,6 @@ import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.ExportableEntity;
import org.thingsboard.server.common.data.HasDebugSettings;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.HasTenantId;
@ -42,7 +41,7 @@ import java.io.Serial;
@Schema
@Data
@EqualsAndHashCode(callSuper = true)
public class CalculatedField extends BaseData<CalculatedFieldId> implements HasName, HasTenantId, HasVersion, ExportableEntity<CalculatedFieldId>, HasDebugSettings {
public class CalculatedField extends BaseData<CalculatedFieldId> implements HasName, HasTenantId, HasVersion, HasDebugSettings {
@Serial
private static final long serialVersionUID = 4491966747773381420L;
@ -69,9 +68,6 @@ public class CalculatedField extends BaseData<CalculatedFieldId> implements HasN
@Getter
@Setter
private Long version;
@Getter
@Setter
private CalculatedFieldId externalId;
public CalculatedField() {
super();
@ -81,21 +77,7 @@ public class CalculatedField extends BaseData<CalculatedFieldId> implements HasN
super(id);
}
public CalculatedField(CalculatedField other) {
super(other);
this.tenantId = other.tenantId;
this.entityId = other.entityId;
this.type = other.type;
this.name = other.name;
this.configurationVersion = other.configurationVersion;
this.configuration = other.configuration;
this.version = other.version;
this.externalId = other.externalId;
this.debugMode = other.debugMode;
this.debugSettings = other.debugSettings;
}
public CalculatedField(TenantId tenantId, EntityId entityId, CalculatedFieldType 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) {
this.tenantId = tenantId;
this.entityId = entityId;
this.type = type;
@ -103,7 +85,19 @@ public class CalculatedField extends BaseData<CalculatedFieldId> implements HasN
this.configurationVersion = configurationVersion;
this.configuration = configuration;
this.version = version;
this.externalId = externalId;
}
public CalculatedField(CalculatedField calculatedField) {
super(calculatedField);
this.tenantId = calculatedField.tenantId;
this.entityId = calculatedField.entityId;
this.type = calculatedField.type;
this.name = calculatedField.name;
this.debugMode = calculatedField.debugMode;
this.debugSettings = calculatedField.debugSettings;
this.configurationVersion = calculatedField.configurationVersion;
this.configuration = calculatedField.configuration;
this.version = calculatedField.version;
}
@Schema(description = "JSON object with the Calculated Field Id. Referencing non-existing Calculated Field Id will cause error.")
@ -129,7 +123,6 @@ public class CalculatedField extends BaseData<CalculatedFieldId> implements HasN
.append(", configurationVersion=").append(configurationVersion)
.append(", configuration=").append(configuration)
.append(", version=").append(version)
.append(", externalId=").append(externalId)
.append(", createdTime=").append(createdTime)
.append(", id=").append(id).append(']')
.toString();

3
common/data/src/main/java/org/thingsboard/server/common/data/id/CalculatedFieldId.java

@ -20,11 +20,13 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import org.thingsboard.server.common.data.EntityType;
import java.io.Serial;
import java.util.UUID;
@Schema
public class CalculatedFieldId extends UUIDBased implements EntityId {
@Serial
private static final long serialVersionUID = 1L;
@JsonCreator
@ -41,4 +43,5 @@ public class CalculatedFieldId extends UUIDBased implements EntityId {
public EntityType getEntityType() {
return EntityType.CALCULATED_FIELD;
}
}

4
common/data/src/main/java/org/thingsboard/server/common/data/sync/JsonTbEntity.java

@ -28,7 +28,6 @@ import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.cf.CalculatedField;
import org.thingsboard.server.common.data.notification.rule.NotificationRule;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
@ -59,8 +58,7 @@ import java.lang.annotation.Target;
@Type(name = "NOTIFICATION_TEMPLATE", value = NotificationTemplate.class),
@Type(name = "NOTIFICATION_TARGET", value = NotificationTarget.class),
@Type(name = "NOTIFICATION_RULE", value = NotificationRule.class),
@Type(name = "TB_RESOURCE", value = TbResource.class),
@Type(name = "CALCULATED_FIELD", value = CalculatedField.class)
@Type(name = "TB_RESOURCE", value = TbResource.class)
})
@JsonIgnoreProperties(value = {"tenantId", "createdTime", "version"}, ignoreUnknown = true)
public @interface JsonTbEntity {

28
common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/AssetExportData.java

@ -1,28 +0,0 @@
/**
* Copyright © 2016-2025 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.sync.ie;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.thingsboard.server.common.data.asset.Asset;
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Data
public class AssetExportData extends CalculatedFieldExportData<Asset> {
}

28
common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/AssetProfileExportData.java

@ -1,28 +0,0 @@
/**
* Copyright © 2016-2025 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.sync.ie;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.thingsboard.server.common.data.asset.AssetProfile;
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Data
public class AssetProfileExportData extends CalculatedFieldExportData<AssetProfile> {
}

56
common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/CalculatedFieldExportData.java

@ -1,56 +0,0 @@
/**
* Copyright © 2016-2025 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.sync.ie;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.thingsboard.server.common.data.ExportableEntity;
import org.thingsboard.server.common.data.cf.CalculatedField;
import java.util.Comparator;
import java.util.List;
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class CalculatedFieldExportData<E extends ExportableEntity<?>> extends EntityExportData<E> {
public static final Comparator<CalculatedField> calculatedFieldsComparator = Comparator.comparing(CalculatedField::getName);
@JsonProperty(index = 102)
@JsonIgnoreProperties({"entityId", "createdTime", "version"})
private List<CalculatedField> calculatedFields;
@JsonIgnore
@Override
public boolean hasCalculatedFields() {
return calculatedFields != null;
}
@Override
public CalculatedFieldExportData<E> sort() {
super.sort();
if (calculatedFields != null && !calculatedFields.isEmpty()) {
calculatedFields.sort(calculatedFieldsComparator);
}
return this;
}
}

2
common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/DeviceExportData.java

@ -27,7 +27,7 @@ import org.thingsboard.server.common.data.security.DeviceCredentials;
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Data
public class DeviceExportData extends CalculatedFieldExportData<Device> {
public class DeviceExportData extends EntityExportData<Device> {
@JsonProperty(index = 3)
@JsonIgnoreProperties({"id", "deviceId", "createdTime", "version"})

28
common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/DeviceProfileExportData.java

@ -1,28 +0,0 @@
/**
* Copyright © 2016-2025 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.sync.ie;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.thingsboard.server.common.data.DeviceProfile;
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Data
public class DeviceProfileExportData extends CalculatedFieldExportData<DeviceProfile> {
}

14
common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/EntityExportData.java

@ -26,6 +26,7 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
import lombok.Data;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.ExportableEntity;
import org.thingsboard.server.common.data.cf.CalculatedField;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.sync.JsonTbEntity;
@ -37,10 +38,7 @@ import java.util.Map;
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "entityType", include = As.EXISTING_PROPERTY, visible = true, defaultImpl = EntityExportData.class)
@JsonSubTypes({
@Type(name = "DEVICE_PROFILE", value = DeviceProfileExportData.class),
@Type(name = "ASSET_PROFILE", value = AssetProfileExportData.class),
@Type(name = "DEVICE", value = DeviceExportData.class),
@Type(name = "ASSET", value = AssetExportData.class),
@Type(name = "RULE_CHAIN", value = RuleChainExportData.class),
@Type(name = "WIDGET_TYPE", value = WidgetTypeExportData.class),
@Type(name = "WIDGETS_BUNDLE", value = WidgetsBundleExportData.class)
@ -58,6 +56,8 @@ public class EntityExportData<E extends ExportableEntity<? extends EntityId>> {
public static final Comparator<AttributeExportData> attrComparator = Comparator
.comparing(AttributeExportData::getKey).thenComparing(AttributeExportData::getLastUpdateTs);
public static final Comparator<CalculatedField> calculatedFieldsComparator = Comparator.comparing(CalculatedField::getName);
@JsonProperty(index = 2)
@JsonTbEntity
private E entity;
@ -68,6 +68,9 @@ public class EntityExportData<E extends ExportableEntity<? extends EntityId>> {
private List<EntityRelation> relations;
@JsonProperty(index = 101)
private Map<String, List<AttributeExportData>> attributes;
@JsonProperty(index = 102)
@JsonIgnoreProperties({"id", "entityId", "createdTime", "version"})
private List<CalculatedField> calculatedFields;
public EntityExportData<E> sort() {
if (relations != null && !relations.isEmpty()) {
@ -76,6 +79,9 @@ public class EntityExportData<E extends ExportableEntity<? extends EntityId>> {
if (attributes != null && !attributes.isEmpty()) {
attributes.values().forEach(list -> list.sort(attrComparator));
}
if (calculatedFields != null && !calculatedFields.isEmpty()) {
calculatedFields.sort(calculatedFieldsComparator);
}
return this;
}
@ -101,7 +107,7 @@ public class EntityExportData<E extends ExportableEntity<? extends EntityId>> {
@JsonIgnore
public boolean hasCalculatedFields() {
return false;
return calculatedFields != null;
}
}

3
dao/src/main/java/org/thingsboard/server/dao/cf/CalculatedFieldDao.java

@ -22,11 +22,10 @@ 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.Dao;
import org.thingsboard.server.dao.ExportableEntityDao;
import java.util.List;
public interface CalculatedFieldDao extends Dao<CalculatedField>, ExportableEntityDao<CalculatedFieldId, CalculatedField> {
public interface CalculatedFieldDao extends Dao<CalculatedField> {
List<CalculatedField> findAllByTenantId(TenantId tenantId);

1
dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java

@ -729,7 +729,6 @@ public class ModelConstants {
public static final String CALCULATED_FIELD_CONFIGURATION_VERSION = "configuration_version";
public static final String CALCULATED_FIELD_CONFIGURATION = "configuration";
public static final String CALCULATED_FIELD_VERSION = "version";
public static final String CALCULATED_FIELD_EXTERNAL_ID = "external_id";
/**
* Calculated field links constants.

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

@ -40,7 +40,6 @@ import static org.thingsboard.server.dao.model.ModelConstants.CALCULATED_FIELD_C
import static org.thingsboard.server.dao.model.ModelConstants.CALCULATED_FIELD_CONFIGURATION_VERSION;
import static org.thingsboard.server.dao.model.ModelConstants.CALCULATED_FIELD_ENTITY_ID;
import static org.thingsboard.server.dao.model.ModelConstants.CALCULATED_FIELD_ENTITY_TYPE;
import static org.thingsboard.server.dao.model.ModelConstants.CALCULATED_FIELD_EXTERNAL_ID;
import static org.thingsboard.server.dao.model.ModelConstants.CALCULATED_FIELD_NAME;
import static org.thingsboard.server.dao.model.ModelConstants.CALCULATED_FIELD_TABLE_NAME;
import static org.thingsboard.server.dao.model.ModelConstants.CALCULATED_FIELD_TENANT_ID_COLUMN;
@ -82,9 +81,6 @@ public class CalculatedFieldEntity extends BaseVersionedEntity<CalculatedField>
@Column(name = DEBUG_SETTINGS)
private String debugSettings;
@Column(name = CALCULATED_FIELD_EXTERNAL_ID)
private UUID externalId;
public CalculatedFieldEntity() {
super();
}
@ -101,9 +97,6 @@ public class CalculatedFieldEntity extends BaseVersionedEntity<CalculatedField>
this.configuration = JacksonUtil.valueToTree(calculatedField.getConfiguration());
this.version = calculatedField.getVersion();
this.debugSettings = JacksonUtil.toString(calculatedField.getDebugSettings());
if (calculatedField.getExternalId() != null) {
this.externalId = calculatedField.getExternalId().getId();
}
}
@Override
@ -118,9 +111,6 @@ public class CalculatedFieldEntity extends BaseVersionedEntity<CalculatedField>
calculatedField.setConfiguration(JacksonUtil.treeToValue(configuration, CalculatedFieldConfiguration.class));
calculatedField.setVersion(version);
calculatedField.setDebugSettings(JacksonUtil.fromString(debugSettings, DebugSettings.class));
if (externalId != null) {
calculatedField.setExternalId(new CalculatedFieldId(externalId));
}
return calculatedField;
}

11
dao/src/main/java/org/thingsboard/server/dao/sql/cf/CalculatedFieldRepository.java

@ -18,16 +18,13 @@ package org.thingsboard.server.dao.sql.cf;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import org.thingsboard.server.common.data.id.CalculatedFieldId;
import org.thingsboard.server.dao.ExportableEntityRepository;
import org.thingsboard.server.dao.model.sql.CalculatedFieldEntity;
import java.util.List;
import java.util.UUID;
@Repository
public interface CalculatedFieldRepository extends JpaRepository<CalculatedFieldEntity, UUID>, ExportableEntityRepository<CalculatedFieldEntity> {
public interface CalculatedFieldRepository extends JpaRepository<CalculatedFieldEntity, UUID> {
boolean existsByTenantIdAndEntityId(UUID tenantId, UUID entityId);
@ -37,16 +34,10 @@ public interface CalculatedFieldRepository extends JpaRepository<CalculatedField
Page<CalculatedFieldEntity> findAllByTenantIdAndEntityId(UUID tenantId, UUID entityId, Pageable pageable);
Page<CalculatedFieldEntity> findByTenantId(UUID tenantId, Pageable pageable);
List<CalculatedFieldEntity> findAllByTenantId(UUID tenantId);
List<CalculatedFieldEntity> removeAllByTenantIdAndEntityId(UUID tenantId, UUID entityId);
long countByTenantIdAndEntityId(UUID tenantId, UUID entityId);
CalculatedFieldId findExternalIdById(UUID id);
CalculatedFieldEntity findByTenantIdAndName(UUID tenantId, String name);
}

1
dao/src/main/java/org/thingsboard/server/dao/sql/cf/DefaultNativeCalculatedFieldRepository.java

@ -92,7 +92,6 @@ public class DefaultNativeCalculatedFieldRepository implements NativeCalculatedF
calculatedField.setConfiguration(JacksonUtil.treeToValue(configuration, CalculatedFieldConfiguration.class));
calculatedField.setVersion(version);
calculatedField.setDebugSettings(JacksonUtil.fromString(debugSettings, DebugSettings.class));
calculatedField.setExternalId(externalIdObj != null ? new CalculatedFieldId(UUID.fromString((String) externalIdObj)) : null);
return calculatedField;
}).collect(Collectors.toList());

20
dao/src/main/java/org/thingsboard/server/dao/sql/cf/JpaCalculatedFieldDao.java

@ -88,26 +88,6 @@ public class JpaCalculatedFieldDao extends JpaAbstractDao<CalculatedFieldEntity,
return calculatedFieldRepository.countByTenantIdAndEntityId(tenantId.getId(), entityId.getId());
}
@Override
public PageData<CalculatedField> findByTenantId(UUID tenantId, PageLink pageLink) {
return DaoUtil.toPageData(calculatedFieldRepository.findByTenantId(tenantId, DaoUtil.toPageable(pageLink)));
}
@Override
public CalculatedField findByTenantIdAndName(UUID tenantId, String name) {
return DaoUtil.getData(calculatedFieldRepository.findByTenantIdAndName(tenantId, name));
}
@Override
public CalculatedField findByTenantIdAndExternalId(UUID tenantId, UUID externalId) {
return DaoUtil.getData(calculatedFieldRepository.findByTenantIdAndExternalId(tenantId, externalId));
}
@Override
public CalculatedFieldId getExternalIdByInternal(CalculatedFieldId internalId) {
return calculatedFieldRepository.findExternalIdById(internalId.getId());
}
@Override
protected Class<CalculatedFieldEntity> getEntityClass() {
return CalculatedFieldEntity.class;

4
dao/src/main/resources/sql/schema-entities.sql

@ -936,9 +936,7 @@ CREATE TABLE IF NOT EXISTS calculated_field (
configuration varchar(1000000),
version BIGINT DEFAULT 1,
debug_settings varchar(1024),
external_id UUID,
CONSTRAINT calculated_field_unq_key UNIQUE (entity_id, name),
CONSTRAINT calculated_field_external_id_unq_key UNIQUE (tenant_id, external_id)
CONSTRAINT calculated_field_unq_key UNIQUE (entity_id, name)
);
CREATE TABLE IF NOT EXISTS calculated_field_link (

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

@ -101,20 +101,6 @@ public class CalculatedFieldServiceTest extends AbstractServiceTest {
.hasMessage("Calculated Field with such name is already in exists!");
}
@Test
public void testSaveCalculatedFieldWithExistingExternalId() {
Device device = createTestDevice();
CalculatedField calculatedField = getCalculatedField(device.getId(), 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();

Loading…
Cancel
Save