Browse Source

added onCalculatedFieldAdded implementation

pull/12038/head
IrynaMatveieva 2 years ago
parent
commit
86dd8e725b
  1. 3
      application/src/main/java/org/thingsboard/server/service/entitiy/AbstractTbEntityService.java
  2. 31
      application/src/main/java/org/thingsboard/server/service/entitiy/cf/CalculatedFieldCtx.java
  3. 30
      application/src/main/java/org/thingsboard/server/service/entitiy/cf/CalculatedFieldState.java
  4. 281
      application/src/main/java/org/thingsboard/server/service/entitiy/cf/DefaultTbCalculatedFieldService.java
  5. 8
      application/src/main/java/org/thingsboard/server/service/entitiy/cf/TbCalculatedFieldService.java
  6. 2
      application/src/test/java/org/thingsboard/server/controller/CalculatedFieldControllerTest.java
  7. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/asset/AssetService.java
  8. 10
      common/dao-api/src/main/java/org/thingsboard/server/dao/cf/CalculatedFieldService.java
  9. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java
  10. 3
      common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityService.java
  11. 158
      common/data/src/main/java/org/thingsboard/server/common/data/cf/BaseCalculatedFieldConfiguration.java
  12. 8
      common/data/src/main/java/org/thingsboard/server/common/data/cf/CalculatedField.java
  13. 48
      common/data/src/main/java/org/thingsboard/server/common/data/cf/CalculatedFieldConfiguration.java
  14. 4
      common/data/src/main/java/org/thingsboard/server/common/data/cf/CalculatedFieldLink.java
  15. 29
      common/data/src/main/java/org/thingsboard/server/common/data/cf/CalculatedFiledLinkConfiguration.java
  16. 39
      common/data/src/main/java/org/thingsboard/server/common/data/cf/SimpleCalculatedFieldConfiguration.java
  17. 21
      common/proto/src/main/proto/queue.proto
  18. 10
      dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java
  19. 9
      dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java
  20. 60
      dao/src/main/java/org/thingsboard/server/dao/cf/BaseCalculatedFieldService.java
  21. 172
      dao/src/main/java/org/thingsboard/server/dao/cf/CalculatedFieldConfigUtil.java
  22. 4
      dao/src/main/java/org/thingsboard/server/dao/cf/CalculatedFieldDao.java
  23. 6
      dao/src/main/java/org/thingsboard/server/dao/cf/CalculatedFieldLinkDao.java
  24. 12
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java
  25. 9
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java
  26. 5
      dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java
  27. 18
      dao/src/main/java/org/thingsboard/server/dao/model/sql/CalculatedFieldEntity.java
  28. 9
      dao/src/main/java/org/thingsboard/server/dao/model/sql/CalculatedFieldLinkEntity.java
  29. 10
      dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java
  30. 11
      dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java
  31. 3
      dao/src/main/java/org/thingsboard/server/dao/sql/cf/CalculatedFieldLinkRepository.java
  32. 146
      dao/src/main/java/org/thingsboard/server/dao/sql/cf/DefaultNativeCalculatedFieldRepository.java
  33. 11
      dao/src/main/java/org/thingsboard/server/dao/sql/cf/JpaCalculatedFieldDao.java
  34. 14
      dao/src/main/java/org/thingsboard/server/dao/sql/cf/JpaCalculatedFieldLinkDao.java
  35. 29
      dao/src/main/java/org/thingsboard/server/dao/sql/cf/NativeCalculatedFieldRepository.java
  36. 8
      dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java
  37. 11
      dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java
  38. 2
      dao/src/test/java/org/thingsboard/server/dao/service/AssetServiceTest.java
  39. 2
      dao/src/test/java/org/thingsboard/server/dao/service/CalculatedFieldServiceTest.java
  40. 2
      dao/src/test/java/org/thingsboard/server/dao/service/CustomerServiceTest.java
  41. 2
      dao/src/test/java/org/thingsboard/server/dao/service/DeviceServiceTest.java

3
application/src/main/java/org/thingsboard/server/service/entitiy/AbstractTbEntityService.java

@ -42,6 +42,7 @@ import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.device.DeviceProfileService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.service.executors.DbCallbackExecutorService;
@ -99,6 +100,8 @@ public abstract class AbstractTbEntityService {
protected AssetProfileService assetProfileService;
@Autowired
protected DeviceProfileService deviceProfileService;
@Autowired
protected EntityService entityService;
protected boolean isTestProfile() {
return Set.of(this.env.getActiveProfiles()).contains("test");

31
application/src/main/java/org/thingsboard/server/service/entitiy/cf/CalculatedFieldCtx.java

@ -0,0 +1,31 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.entitiy.cf;
import lombok.Builder;
import lombok.Data;
import org.thingsboard.server.common.data.id.CalculatedFieldId;
import org.thingsboard.server.common.data.id.EntityId;
@Data
@Builder
public class CalculatedFieldCtx {
private final CalculatedFieldId calculatedFieldId;
private final EntityId entityId;
private final CalculatedFieldState state;
}

30
application/src/main/java/org/thingsboard/server/service/entitiy/cf/CalculatedFieldState.java

@ -0,0 +1,30 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.entitiy.cf;
import lombok.Builder;
import lombok.Data;
import java.util.Map;
@Data
@Builder
public class CalculatedFieldState {
Map<String, String> arguments;
String result;
}

281
application/src/main/java/org/thingsboard/server/service/entitiy/cf/DefaultTbCalculatedFieldService.java

@ -15,36 +15,64 @@
*/
package org.thingsboard.server.service.entitiy.cf;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.ListeningScheduledExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.cf.BaseCalculatedFieldConfiguration;
import org.thingsboard.server.common.data.cf.CalculatedField;
import org.thingsboard.server.common.data.cf.CalculatedFieldConfig;
import org.thingsboard.server.common.data.cf.CalculatedFieldConfiguration;
import org.thingsboard.server.common.data.cf.CalculatedFieldLink;
import org.thingsboard.server.common.data.cf.CalculatedFiledLinkConfiguration;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.id.CalculatedFieldId;
import org.thingsboard.server.common.data.id.CalculatedFieldLinkId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
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.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.cf.CalculatedFieldService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.AbstractTbEntityService;
import org.thingsboard.server.service.profile.TbAssetProfileCache;
import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.Operation;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static org.thingsboard.server.dao.service.Validator.validateEntityId;
@ -52,21 +80,213 @@ import static org.thingsboard.server.dao.service.Validator.validateEntityId;
@TbCoreComponent
@Service
@Slf4j
@RequiredArgsConstructor
public class DefaultTbCalculatedFieldService extends AbstractTbEntityService implements TbCalculatedFieldService {
private final Map<CalculatedFieldId, CalculatedField> calculatedFields;
private final Map<CalculatedFieldLinkId, CalculatedFieldLink> calculatedFieldLinks;
private final CalculatedFieldService calculatedFieldService;
private final TbDeviceProfileCache deviceProfileCache;
private final TbAssetProfileCache assetProfileCache;
private final AttributesService attributesService;
private final TimeseriesService timeseriesService;
private ListeningScheduledExecutorService scheduledExecutor;
public DefaultTbCalculatedFieldService(CalculatedFieldService calculatedFieldService) {
this.calculatedFields = calculatedFieldService.findAll().stream().collect(Collectors.toMap(CalculatedField::getId, cf -> cf));
this.calculatedFieldLinks = calculatedFieldService.findAllCalculatedFieldLinks().stream().collect(Collectors.toMap(CalculatedFieldLink::getId, cfl -> cfl));
this.calculatedFieldService = calculatedFieldService;
private ListeningExecutorService calculatedFieldExecutor;
private ListeningExecutorService calculatedFieldCallbackExecutor;
private final ConcurrentMap<CalculatedFieldId, CalculatedField> calculatedFields = new ConcurrentHashMap<>();
private final ConcurrentMap<CalculatedFieldId, List<CalculatedFieldLink>> calculatedFieldLinks = new ConcurrentHashMap<>();
private final ConcurrentMap<CalculatedFieldId, CalculatedFieldCtx> states = new ConcurrentHashMap<>();
@Value("${state.initFetchPackSize:50000}")
@Getter
private int initFetchPackSize;
@Value("10")
@Getter
private int defaultCalculatedFieldCheckIntervalInSec;
@PostConstruct
public void init() {
// from AbstractPartitionBasedService
scheduledExecutor = MoreExecutors.listeningDecorator(Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("calculated-field-scheduled")));
///
calculatedFieldExecutor = MoreExecutors.listeningDecorator(ThingsBoardExecutors.newWorkStealingPool(
Math.max(4, Runtime.getRuntime().availableProcessors()), "calculated-field"));
calculatedFieldCallbackExecutor = MoreExecutors.listeningDecorator(ThingsBoardExecutors.newWorkStealingPool(
Math.max(4, Runtime.getRuntime().availableProcessors()), "calculated-field-callback"));
scheduledExecutor.scheduleWithFixedDelay(this::fetchCalculatedFields, new Random().nextInt(defaultCalculatedFieldCheckIntervalInSec), defaultCalculatedFieldCheckIntervalInSec, TimeUnit.SECONDS);
}
@PreDestroy
public void stop() {
// from AbstractPartitionBasedService
if (scheduledExecutor != null) {
scheduledExecutor.shutdown();
}
///
if (calculatedFieldExecutor != null) {
calculatedFieldExecutor.shutdownNow();
}
if (calculatedFieldCallbackExecutor != null) {
calculatedFieldCallbackExecutor.shutdownNow();
}
}
private ListenableFuture<List<AttributeKvEntry>> fetchAttributesForEntity(TenantId tenantId, EntityId entityId, List<String> keys) {
return attributesService.find(tenantId, entityId, AttributeScope.SERVER_SCOPE, keys);
}
private ListenableFuture<List<TsKvEntry>> fetchTimeSeries(TenantId tenantId, EntityId entityId, List<String> keys) {
return timeseriesService.findLatest(tenantId, entityId, keys);
}
private ListenableFuture<Void> initializeStateFromFutures(TenantId tenantId, EntityId entityId, CalculatedField calculatedField, List<String> attributeKeys, List<String> timeSeriesKeys) {
ListenableFuture<List<AttributeKvEntry>> attributesFuture = fetchAttributesForEntity(tenantId, entityId, attributeKeys);
ListenableFuture<List<TsKvEntry>> timeSeriesFuture = fetchTimeSeries(tenantId, entityId, timeSeriesKeys);
ListenableFuture<List<Object>> combinedFuture = Futures.allAsList(attributesFuture, timeSeriesFuture);
return Futures.transform(combinedFuture, results -> {
List<AttributeKvEntry> attributes = (List<AttributeKvEntry>) results.get(0);
List<TsKvEntry> timeSeries = (List<TsKvEntry>) results.get(1);
initializeState(calculatedField, attributes, timeSeries);
return null;
}, MoreExecutors.directExecutor());
}
private void initializeState(CalculatedField calculatedField, List<AttributeKvEntry> attributes, List<TsKvEntry> timeSeries) {
CalculatedFieldCtx calculatedFieldCtx = states.computeIfAbsent(calculatedField.getId(),
ctx -> new CalculatedFieldCtx(calculatedField.getId(), calculatedField.getEntityId(), null));
CalculatedFieldState state = calculatedFieldCtx.getState();
if (state != null) {
String calculation = performCalculation(state.getArguments());
Map<String, String> updatedArguments = state.getArguments();
state = CalculatedFieldState.builder()
.arguments(updatedArguments)
.result(calculation)
.build();
} else {
// initial calculation
Map<String, BaseCalculatedFieldConfiguration.Argument> arguments = calculatedField.getConfiguration().getArguments();
Map<String, String> argumentValues = arguments.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
entry -> resolveArgumentValue(entry.getKey(), entry.getValue(), attributes, timeSeries)
));
String calculation = performCalculation(argumentValues);
state = CalculatedFieldState.builder()
.arguments(argumentValues)
.result(calculation)
.build();
}
calculatedFieldCtx = new CalculatedFieldCtx(calculatedField.getId(), calculatedField.getEntityId(), state);
states.put(calculatedField.getId(), calculatedFieldCtx);
}
private String resolveArgumentValue(String key, BaseCalculatedFieldConfiguration.Argument argument,
List<AttributeKvEntry> attributes, List<TsKvEntry> timeSeries) {
String type = argument.getType();
String value = null;
if ("ATTRIBUTES".equals(type)) {
value = attributes.stream()
.filter(attribute -> attribute.getKey().equals(key))
.map(AttributeKvEntry::getValueAsString)
.findFirst()
.orElse(null);
} else if ("TIME_SERIES".equals(type)) {
value = timeSeries.stream()
.filter(tsKvEntry -> tsKvEntry.getKey().equals(key))
.map(TsKvEntry::getValueAsString)
.findFirst()
.orElse(null);
}
return value != null ? value : argument.getDefaultValue();
}
@Override
public void onCalculatedFieldAdded(TransportProtos.CalculatedFieldAddMsgProto proto, TbCallback callback) {
try {
TenantId tenantId = TenantId.fromUUID(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB()));
CalculatedFieldId calculatedFieldId = new CalculatedFieldId(new UUID(proto.getCalculatedFieldIdMSB(), proto.getCalculatedFieldIdLSB()));
CalculatedField cf = calculatedFieldService.findById(tenantId, calculatedFieldId);
List<CalculatedFieldLink> links = calculatedFieldService.findAllCalculatedFieldLinksById(tenantId, calculatedFieldId);
if (cf != null) {
EntityId entityId = cf.getEntityId();
calculatedFields.put(calculatedFieldId, cf);
calculatedFieldLinks.put(calculatedFieldId, links);
switch (entityId.getEntityType()) {
case ASSET, DEVICE: {
for (CalculatedFieldLink link : links) {
CalculatedFiledLinkConfiguration configuration = link.getConfiguration();
initializeStateFromFutures(tenantId, link.getEntityId(), cf, configuration.getAttributes(), configuration.getTimeSeries());
}
}
case ASSET_PROFILE: {
PageDataIterable<AssetId> assetIds = new PageDataIterable<>(pageLink ->
assetService.findAssetIdsByTenantIdAndAssetProfileId(tenantId, (AssetProfileId) entityId, pageLink), initFetchPackSize);
for (AssetId assetId : assetIds) {
for (CalculatedFieldLink link : links) {
CalculatedFiledLinkConfiguration configuration = link.getConfiguration();
initializeStateFromFutures(tenantId, assetId, cf, configuration.getAttributes(), configuration.getTimeSeries());
}
}
}
case DEVICE_PROFILE: {
PageDataIterable<DeviceId> deviceIds = new PageDataIterable<>(pageLink ->
deviceService.findDeviceIdsByTenantIdAndDeviceProfileId(tenantId, (DeviceProfileId) entityId, pageLink), initFetchPackSize);
for (DeviceId deviceId : deviceIds) {
for (CalculatedFieldLink link : links) {
CalculatedFiledLinkConfiguration configuration = link.getConfiguration();
initializeStateFromFutures(tenantId, deviceId, cf, configuration.getAttributes(), configuration.getTimeSeries());
}
}
}
default: throw new IllegalArgumentException("Entity type '" + calculatedFieldId.getEntityType() + "' does not support calculated fields.");
}
} else {
//Calculated field or entity was probably deleted while message was in queue;
callback.onSuccess();
}
} catch (Exception e) {
log.trace("Failed to process calculated field add msg: [{}]", proto, e);
callback.onFailure(e);
}
}
@Override
public void onMsg() {
public void onCalculatedFieldUpdated(TransportProtos.CalculatedFieldUpdateMsgProto proto, TbCallback callback) {
try {
TenantId tenantId = TenantId.fromUUID(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB()));
CalculatedFieldId calculatedFieldId = new CalculatedFieldId(new UUID(proto.getCalculatedFieldIdMSB(), proto.getCalculatedFieldIdLSB()));
} catch (Exception e) {
log.trace("Failed to process calculated field update msg: [{}]", proto, e);
callback.onFailure(e);
}
}
@Override
public void onCalculatedFieldDeleted(TransportProtos.CalculatedFieldDeleteMsgProto proto, TbCallback callback) {
try {
CalculatedFieldId calculatedFieldId = new CalculatedFieldId(new UUID(proto.getCalculatedFieldIdMSB(), proto.getCalculatedFieldIdLSB()));
calculatedFieldLinks.remove(calculatedFieldId);
calculatedFields.remove(calculatedFieldId);
states.remove(calculatedFieldId);
} catch (Exception e) {
log.trace("Failed to process calculated field delete msg: [{}]", proto, e);
callback.onFailure(e);
}
}
@Override
@ -105,28 +325,26 @@ public class DefaultTbCalculatedFieldService extends AbstractTbEntityService imp
}
}
private void fetchCalculatedFields() {
PageDataIterable<CalculatedField> cfs = new PageDataIterable<>(calculatedFieldService::findAllCalculatedFields, initFetchPackSize);
cfs.forEach(cf -> calculatedFields.putIfAbsent(cf.getId(), cf));
PageDataIterable<CalculatedFieldLink> cfls = new PageDataIterable<>(calculatedFieldService::findAllCalculatedFieldLinks, initFetchPackSize);
cfls.forEach(link -> calculatedFieldLinks.computeIfAbsent(link.getCalculatedFieldId(), id -> new ArrayList<>()).add(link));
// TODO: read all states(CalculatedFieldCtx)
states.keySet().removeIf(calculatedFieldId -> !calculatedFields.containsKey(calculatedFieldId));
}
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."));
case ASSET_PROFILE ->
Optional.ofNullable(assetProfileService.findAssetProfileById(tenantId, (AssetProfileId) entityId))
.orElseThrow(() -> new IllegalArgumentException("Asset Profile with id [" + entityId.getId() + "] does not exist."));
case DEVICE_PROFILE ->
Optional.ofNullable(deviceProfileService.findDeviceProfileById(tenantId, (DeviceProfileId) entityId))
.orElseThrow(() -> new IllegalArgumentException("Device Profile with id [" + entityId.getId() + "] does not exist."));
case ASSET, DEVICE, ASSET_PROFILE, DEVICE_PROFILE -> Optional.ofNullable(entityService.fetchEntity(tenantId, entityId))
.orElseThrow(() -> new IllegalArgumentException(entityId.getEntityType().getNormalName() + " with id [" + entityId.getId() + "] does not exist."));
default ->
throw new IllegalArgumentException("Entity type '" + entityId.getEntityType() + "' does not support calculated fields.");
}
}
private <E extends HasId<I> & HasTenantId, I extends EntityId> void checkReferencedEntities(CalculatedFieldConfig calculatedFieldConfig, SecurityUser user) throws ThingsboardException {
List<EntityId> referencedEntityIds = calculatedFieldConfig.getArguments().values().stream()
.map(CalculatedFieldConfig.Argument::getEntityId)
.filter(Objects::nonNull)
.toList();
private <E extends HasId<I> & HasTenantId, I extends EntityId> void checkReferencedEntities(CalculatedFieldConfiguration calculatedFieldConfig, SecurityUser user) throws ThingsboardException {
List<EntityId> referencedEntityIds = calculatedFieldConfig.getReferencedEntities();
for (EntityId referencedEntityId : referencedEntityIds) {
validateEntityId(referencedEntityId, id -> "Invalid entity id " + id);
E entity = findEntity(user.getTenantId(), referencedEntityId);
@ -137,13 +355,14 @@ public class DefaultTbCalculatedFieldService extends AbstractTbEntityService imp
}
private <E extends HasId<I> & HasTenantId, I extends EntityId> E findEntity(TenantId tenantId, EntityId entityId) {
return (E) switch (entityId.getEntityType()) {
case TENANT -> tenantService.findTenantById((TenantId) entityId);
case CUSTOMER -> customerService.findCustomerById(tenantId, (CustomerId) entityId);
case ASSET -> assetService.findAssetById(tenantId, (AssetId) entityId);
case DEVICE -> deviceService.findDeviceById(tenantId, (DeviceId) entityId);
return switch (entityId.getEntityType()) {
case TENANT, CUSTOMER, ASSET, DEVICE -> (E) entityService.fetchEntity(tenantId, entityId).orElse(null);
default -> throw new IllegalArgumentException("Calculated fields do not support entity type '" + entityId.getEntityType() + "' for referenced entities.");
};
}
private String performCalculation(Map<String, String> argumentValues) {
return "calculation";
}
}

8
application/src/main/java/org/thingsboard/server/service/entitiy/cf/TbCalculatedFieldService.java

@ -18,11 +18,17 @@ package org.thingsboard.server.service.entitiy.cf;
import org.thingsboard.server.common.data.cf.CalculatedField;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.CalculatedFieldId;
import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.service.security.model.SecurityUser;
public interface TbCalculatedFieldService {
void onMsg();
void onCalculatedFieldAdded(TransportProtos.CalculatedFieldAddMsgProto proto, TbCallback callback);
void onCalculatedFieldUpdated(TransportProtos.CalculatedFieldUpdateMsgProto proto, TbCallback callback);
void onCalculatedFieldDeleted(TransportProtos.CalculatedFieldDeleteMsgProto proto, TbCallback callback);
CalculatedField save(CalculatedField calculatedField, SecurityUser user) throws ThingsboardException;

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

@ -126,7 +126,7 @@ public class CalculatedFieldControllerTest extends AbstractControllerTest {
calculatedField.setType("Simple");
calculatedField.setName("Test Calculated Field");
calculatedField.setConfigurationVersion(1);
calculatedField.setConfiguration(getCalculatedFieldConfig(null));
// calculatedField.setConfiguration(getCalculatedFieldConfig(null));
calculatedField.setVersion(1L);
return calculatedField;
}

2
common/dao-api/src/main/java/org/thingsboard/server/dao/asset/AssetService.java

@ -63,6 +63,8 @@ public interface AssetService extends EntityDaoService {
PageData<AssetInfo> findAssetInfosByTenantIdAndAssetProfileId(TenantId tenantId, AssetProfileId assetProfileId, PageLink pageLink);
PageData<AssetId> findAssetIdsByTenantIdAndAssetProfileId(TenantId tenantId, AssetProfileId assetProfileId, PageLink pageLink);
ListenableFuture<List<Asset>> findAssetsByTenantIdAndIdsAsync(TenantId tenantId, List<AssetId> assetIds);
void deleteAssetsByTenantId(TenantId tenantId);

10
common/dao-api/src/main/java/org/thingsboard/server/dao/cf/CalculatedFieldService.java

@ -21,6 +21,8 @@ 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;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.entity.EntityDaoService;
import java.util.List;
@ -31,7 +33,9 @@ public interface CalculatedFieldService extends EntityDaoService {
CalculatedField findById(TenantId tenantId, CalculatedFieldId calculatedFieldId);
List<CalculatedField> findAll();
List<CalculatedField> findAllCalculatedFields();
PageData<CalculatedField> findAllCalculatedFields(PageLink pageLink);
void deleteCalculatedField(TenantId tenantId, CalculatedFieldId calculatedFieldId);
@ -43,6 +47,10 @@ public interface CalculatedFieldService extends EntityDaoService {
List<CalculatedFieldLink> findAllCalculatedFieldLinks();
List<CalculatedFieldLink> findAllCalculatedFieldLinksById(TenantId tenantId, CalculatedFieldId calculatedFieldId);
PageData<CalculatedFieldLink> findAllCalculatedFieldLinks(PageLink pageLink);
boolean existsByEntityId(TenantId tenantId, EntityId entityId);
boolean referencedInAnyCalculatedField(TenantId tenantId, EntityId referencedEntityId);

2
common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java

@ -75,6 +75,8 @@ public interface DeviceService extends EntityDaoService {
PageData<Device> findDevicesByTenantIdAndType(TenantId tenantId, String type, PageLink pageLink);
PageData<DeviceId> findDeviceIdsByTenantIdAndDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId, PageLink pageLink);
PageData<Device> findDevicesByTenantIdAndTypeAndEmptyOtaPackage(TenantId tenantId, DeviceProfileId deviceProfileId, OtaPackageType type, PageLink pageLink);
long countDevicesByTenantIdAndDeviceProfileIdAndEmptyOtaPackage(TenantId tenantId, DeviceProfileId deviceProfileId, OtaPackageType otaPackageType);

3
common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityService.java

@ -17,6 +17,7 @@ package org.thingsboard.server.dao.entity;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.HasId;
import org.thingsboard.server.common.data.id.NameLabelAndCustomerDetails;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
@ -34,6 +35,8 @@ public interface EntityService {
Optional<CustomerId> fetchEntityCustomerId(TenantId tenantId, EntityId entityId);
Optional<HasId<?>> fetchEntity(TenantId tenantId, EntityId entityId);
Optional<NameLabelAndCustomerDetails> fetchNameLabelAndCustomerDetails(TenantId tenantId, EntityId entityId);
long countEntitiesByQuery(TenantId tenantId, CustomerId customerId, EntityCountQuery query);

158
common/data/src/main/java/org/thingsboard/server/common/data/cf/BaseCalculatedFieldConfiguration.java

@ -0,0 +1,158 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.cf;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.Data;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
@Data
public abstract class BaseCalculatedFieldConfiguration implements CalculatedFieldConfiguration {
@JsonIgnore
private final ObjectMapper mapper = new ObjectMapper();
protected Map<String, Argument> arguments;
protected SimpleCalculatedFieldConfiguration.Output output;
public BaseCalculatedFieldConfiguration() {
}
public BaseCalculatedFieldConfiguration(JsonNode config, EntityType entityType, UUID entityId) {
BaseCalculatedFieldConfiguration calculatedFieldConfig = toCalculatedFieldConfig(config, entityType, entityId);
this.arguments = calculatedFieldConfig.getArguments();
this.output = calculatedFieldConfig.getOutput();
}
@Override
public List<EntityId> getReferencedEntities() {
return arguments.values().stream()
.map(SimpleCalculatedFieldConfiguration.Argument::getEntityId)
.collect(Collectors.toList());
}
@Override
public CalculatedFiledLinkConfiguration getReferencedEntityConfig(EntityId entityId) {
CalculatedFiledLinkConfiguration linkConfiguration = new CalculatedFiledLinkConfiguration();
arguments.values().stream()
.filter(argument -> argument.getEntityId().equals(entityId))
.forEach(argument -> {
switch (argument.getType()) {
case "ATTRIBUTES":
linkConfiguration.getAttributes().add(argument.getKey());
break;
case "TIME_SERIES":
linkConfiguration.getTimeSeries().add(argument.getKey());
break;
}
});
return linkConfiguration;
}
@Override
public JsonNode calculatedFieldConfigToJson(EntityType entityType, UUID entityId) {
ObjectNode configNode = mapper.createObjectNode();
ObjectNode argumentsNode = configNode.putObject("arguments");
arguments.forEach((key, argument) -> {
ObjectNode argumentNode = argumentsNode.putObject(key);
EntityId referencedEntityId = argument.getEntityId();
if (referencedEntityId != null) {
argumentNode.put("entityType", referencedEntityId.getEntityType().name());
argumentNode.put("entityId", referencedEntityId.getId().toString());
} else {
argumentNode.put("entityType", entityType.name());
argumentNode.put("entityId", entityId.toString());
}
argumentNode.put("key", argument.getKey());
argumentNode.put("type", argument.getType());
argumentNode.put("defaultValue", argument.getDefaultValue());
});
if (output != null) {
ObjectNode outputNode = configNode.putObject("output");
outputNode.put("type", output.getType());
outputNode.put("expression", output.getExpression());
}
return configNode;
}
@Data
public static class Argument {
private EntityId entityId;
private String key;
private String type;
private String defaultValue;
}
@Data
public static class Output {
private String type;
private String expression;
}
private BaseCalculatedFieldConfiguration toCalculatedFieldConfig(JsonNode config, EntityType entityType, UUID entityId) {
if (config == null || !config.isObject()) {
return null;
}
Map<String, Argument> arguments = new HashMap<>();
JsonNode argumentsNode = config.get("arguments");
if (argumentsNode != null && argumentsNode.isObject()) {
argumentsNode.fields().forEachRemaining(entry -> {
String key = entry.getKey();
JsonNode argumentNode = entry.getValue();
Argument argument = new Argument();
if (argumentNode.hasNonNull("entityType") && argumentNode.hasNonNull("entityId")) {
String referencedEntityType = argumentNode.get("entityType").asText();
UUID referencedEntityId = UUID.fromString(argumentNode.get("entityId").asText());
argument.setEntityId(EntityIdFactory.getByTypeAndUuid(referencedEntityType, referencedEntityId));
} else {
argument.setEntityId(EntityIdFactory.getByTypeAndUuid(entityType, entityId));
}
argument.setKey(argumentNode.get("key").asText());
argument.setType(argumentNode.get("type").asText());
argument.setDefaultValue(argumentNode.get("defaultValue").asText());
arguments.put(key, argument);
});
}
this.setArguments(arguments);
JsonNode outputNode = config.get("output");
if (outputNode != null) {
Output output = new Output();
output.setType(outputNode.get("type").asText());
output.setExpression(outputNode.get("expression").asText());
this.setOutput(output);
}
return this;
}
}

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

@ -27,6 +27,8 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.validation.Length;
import org.thingsboard.server.common.data.validation.NoXss;
import java.io.Serializable;
@Schema
@Data
@EqualsAndHashCode(callSuper = true)
@ -46,8 +48,8 @@ public class CalculatedField extends BaseData<CalculatedFieldId> implements HasN
private String name;
@Schema(description = "Version of calculated field configuration.", example = "0")
private int configurationVersion;
@Schema
private transient CalculatedFieldConfig configuration;
@Schema(implementation = SimpleCalculatedFieldConfiguration.class)
private transient CalculatedFieldConfiguration configuration;
@Getter
@Setter
private Long version;
@ -63,7 +65,7 @@ public class CalculatedField extends BaseData<CalculatedFieldId> implements HasN
super(id);
}
public CalculatedField(TenantId tenantId, EntityId entityId, String type, String name, int configurationVersion, CalculatedFieldConfig configuration, Long version, CalculatedFieldId externalId) {
public CalculatedField(TenantId tenantId, EntityId entityId, String type, String name, int configurationVersion, CalculatedFieldConfiguration configuration, Long version, CalculatedFieldId externalId) {
this.tenantId = tenantId;
this.entityId = entityId;
this.type = type;

48
common/data/src/main/java/org/thingsboard/server/common/data/cf/CalculatedFieldConfiguration.java

@ -0,0 +1,48 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.cf;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.JsonNode;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.EntityId;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type"
)
@JsonSubTypes({
@JsonSubTypes.Type(value = SimpleCalculatedFieldConfiguration.class, name = "SIMPLE")
})
public interface CalculatedFieldConfiguration {
String getType();
Map<String, BaseCalculatedFieldConfiguration.Argument> getArguments();
List<EntityId> getReferencedEntities();
CalculatedFiledLinkConfiguration getReferencedEntityConfig(EntityId entityId);
JsonNode calculatedFieldConfigToJson(EntityType entityType, UUID entityId);
}

4
common/data/src/main/java/org/thingsboard/server/common/data/cf/CalculatedFieldLink.java

@ -37,7 +37,7 @@ public class CalculatedFieldLink extends BaseData<CalculatedFieldLinkId> {
@Schema(description = "JSON object with the Calculated Field Id. ", accessMode = Schema.AccessMode.READ_ONLY)
private CalculatedFieldId calculatedFieldId;
@Schema
private transient CalculatedFieldConfig configuration;
private transient CalculatedFiledLinkConfiguration configuration;
public CalculatedFieldLink() {
super();
@ -47,7 +47,7 @@ public class CalculatedFieldLink extends BaseData<CalculatedFieldLinkId> {
super(id);
}
public CalculatedFieldLink(TenantId tenantId, EntityId entityId, CalculatedFieldId calculatedFieldId, CalculatedFieldConfig configuration) {
public CalculatedFieldLink(TenantId tenantId, EntityId entityId, CalculatedFieldId calculatedFieldId, CalculatedFiledLinkConfiguration configuration) {
this.tenantId = tenantId;
this.entityId = entityId;
this.calculatedFieldId = calculatedFieldId;

29
common/data/src/main/java/org/thingsboard/server/common/data/cf/CalculatedFiledLinkConfiguration.java

@ -0,0 +1,29 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.cf;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
public class CalculatedFiledLinkConfiguration {
private List<String> attributes = new ArrayList<>();
private List<String> timeSeries = new ArrayList<>();
}

39
common/data/src/main/java/org/thingsboard/server/common/data/cf/SimpleCalculatedFieldConfiguration.java

@ -0,0 +1,39 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.cf;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Data;
import org.thingsboard.server.common.data.EntityType;
import java.util.UUID;
@Data
public class SimpleCalculatedFieldConfiguration extends BaseCalculatedFieldConfiguration implements CalculatedFieldConfiguration {
public SimpleCalculatedFieldConfiguration() {
super();
}
public SimpleCalculatedFieldConfiguration(JsonNode config, EntityType entityType, UUID entityId) {
super(config, entityType, entityId);
}
@Override
public String getType() {
return "SIMPLE";
}
}

21
common/proto/src/main/proto/queue.proto

@ -1267,6 +1267,27 @@ message ToDeviceActorNotificationMsgProto {
DeviceDeleteMsgProto deviceDeleteMsg = 8;
}
message CalculatedFieldAddMsgProto {
int64 tenantIdMSB = 1;
int64 tenantIdLSB = 2;
int64 calculatedFieldIdMSB = 3;
int64 calculatedFieldIdLSB = 4;
}
message CalculatedFieldUpdateMsgProto {
int64 tenantIdMSB = 1;
int64 tenantIdLSB = 2;
int64 calculatedFieldIdMSB = 3;
int64 calculatedFieldIdLSB = 4;
}
message CalculatedFieldDeleteMsgProto {
int64 tenantIdMSB = 1;
int64 tenantIdLSB = 2;
int64 calculatedFieldIdMSB = 3;
int64 calculatedFieldIdLSB = 4;
}
/**
TB Core to Version Control Service
*/

10
dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java

@ -103,6 +103,16 @@ public interface AssetDao extends Dao<Asset>, TenantEntityDao, ExportableEntityD
*/
PageData<AssetInfo> findAssetInfosByTenantIdAndAssetProfileId(UUID tenantId, UUID assetProfileId, PageLink pageLink);
/**
* Find asset ids by tenantId, assetProfileId and page link.
*
* @param tenantId the tenantId
* @param assetProfileId the assetProfileId
* @param pageLink the page link
* @return the list of asset objects
*/
PageData<AssetId> findAssetIdsByTenantIdAndAssetProfileId(UUID tenantId, UUID assetProfileId, PageLink pageLink);
/**
* Find assets by tenantId and assets Ids.
*

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

@ -285,6 +285,15 @@ public class BaseAssetService extends AbstractCachedEntityService<AssetCacheKey,
return assetDao.findAssetInfosByTenantIdAndAssetProfileId(tenantId.getId(), assetProfileId.getId(), pageLink);
}
@Override
public PageData<AssetId> findAssetIdsByTenantIdAndAssetProfileId(TenantId tenantId, AssetProfileId assetProfileId, PageLink pageLink) {
log.trace("Executing findAssetIdsByTenantIdAndAssetProfileId, tenantId [{}], assetProfileId [{}]", tenantId, assetProfileId);
validateId(tenantId, id -> INCORRECT_TENANT_ID + id);
validateId(assetProfileId, id -> INCORRECT_ASSET_PROFILE_ID + id);
validatePageLink(pageLink);
return assetDao.findAssetIdsByTenantIdAndAssetProfileId(tenantId.getId(), assetProfileId.getId(), pageLink);
}
@Override
public ListenableFuture<List<Asset>> findAssetsByTenantIdAndIdsAsync(TenantId tenantId, List<AssetId> assetIds) {
log.trace("Executing findAssetsByTenantIdAndIdsAsync, tenantId [{}], assetIds [{}]", tenantId, assetIds);

60
dao/src/main/java/org/thingsboard/server/dao/cf/BaseCalculatedFieldService.java

@ -20,20 +20,24 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.cf.CalculatedField;
import org.thingsboard.server.common.data.cf.CalculatedFieldConfig;
import org.thingsboard.server.common.data.cf.CalculatedFieldConfiguration;
import org.thingsboard.server.common.data.cf.CalculatedFieldLink;
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.HasId;
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.service.DataValidator;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import static org.thingsboard.server.dao.entity.AbstractEntityService.checkConstraintViolation;
import static org.thingsboard.server.dao.service.Validator.validateId;
import static org.thingsboard.server.dao.service.Validator.validatePageLink;
@Service("CalculatedFieldDaoService")
@Slf4j
@ -74,11 +78,18 @@ public class BaseCalculatedFieldService implements CalculatedFieldService {
}
@Override
public List<CalculatedField> findAll() {
public List<CalculatedField> findAllCalculatedFields() {
log.trace("Executing findAll");
return calculatedFieldDao.findAll();
}
@Override
public PageData<CalculatedField> findAllCalculatedFields(PageLink pageLink) {
log.trace("Executing findAll, pageLink [{}]", pageLink);
validatePageLink(pageLink);
return calculatedFieldDao.findAll(pageLink);
}
@Override
public void deleteCalculatedField(TenantId tenantId, CalculatedFieldId calculatedFieldId) {
log.trace("Executing deleteCalculatedField, tenantId [{}], calculatedFieldId [{}]", tenantId, calculatedFieldId);
@ -122,6 +133,19 @@ public class BaseCalculatedFieldService implements CalculatedFieldService {
return calculatedFieldLinkDao.findAll();
}
@Override
public List<CalculatedFieldLink> findAllCalculatedFieldLinksById(TenantId tenantId, CalculatedFieldId calculatedFieldId) {
log.trace("Executing findAllCalculatedFieldLinksById, calculatedFieldId [{}]", calculatedFieldId);
return calculatedFieldLinkDao.findCalculatedFieldLinksByCalculatedFieldId(tenantId, calculatedFieldId);
}
@Override
public PageData<CalculatedFieldLink> findAllCalculatedFieldLinks(PageLink pageLink) {
log.trace("Executing findAllCalculatedFieldLinks, pageLink [{}]", pageLink);
validatePageLink(pageLink);
return calculatedFieldLinkDao.findAll(pageLink);
}
@Override
public boolean existsByEntityId(TenantId tenantId, EntityId entityId) {
return calculatedFieldDao.existsByTenantIdAndEntityId(tenantId, entityId);
@ -132,9 +156,8 @@ public class BaseCalculatedFieldService implements CalculatedFieldService {
return calculatedFieldDao.findAllByTenantId(tenantId).stream()
.filter(calculatedField -> !referencedEntityId.equals(calculatedField.getEntityId()))
.map(CalculatedField::getConfiguration)
.map(CalculatedFieldConfig::getArguments)
.flatMap(arguments -> arguments.values().stream())
.anyMatch(argument -> referencedEntityId.equals(argument.getEntityId()));
.map(CalculatedFieldConfiguration::getReferencedEntities)
.anyMatch(referencedEntities -> referencedEntities.contains(referencedEntityId));
}
@Override
@ -148,21 +171,22 @@ public class BaseCalculatedFieldService implements CalculatedFieldService {
}
private void createOrUpdateCalculatedFieldLink(TenantId tenantId, CalculatedField calculatedField) {
CalculatedFieldLink existingLink = (calculatedField.getId() != null)
? calculatedFieldLinkDao.findCalculatedFieldLinkByCalculatedFieldId(tenantId, calculatedField.getId())
: null;
CalculatedFieldLink updatedLink = buildCalculatedFieldLink(tenantId, calculatedField, existingLink);
saveCalculatedFieldLink(tenantId, updatedLink);
List<CalculatedFieldLink> links = buildCalculatedFieldLinks(tenantId, calculatedField);
links.forEach(link -> saveCalculatedFieldLink(tenantId, link));
}
private CalculatedFieldLink buildCalculatedFieldLink(TenantId tenantId, CalculatedField calculatedField, CalculatedFieldLink existingLink) {
CalculatedFieldLink link = (existingLink != null) ? existingLink : new CalculatedFieldLink();
link.setTenantId(tenantId);
link.setEntityId(calculatedField.getEntityId());
link.setCalculatedFieldId(calculatedField.getId());
link.setConfiguration(calculatedField.getConfiguration());
return link;
private List<CalculatedFieldLink> buildCalculatedFieldLinks(TenantId tenantId, CalculatedField calculatedField) {
CalculatedFieldConfiguration cfConfig = calculatedField.getConfiguration();
return cfConfig.getReferencedEntities().stream()
.map(referencedEntityId -> {
CalculatedFieldLink link = new CalculatedFieldLink();
link.setTenantId(tenantId);
link.setEntityId(referencedEntityId);
link.setCalculatedFieldId(calculatedField.getId());
link.setConfiguration(cfConfig.getReferencedEntityConfig(referencedEntityId));
return link;
})
.collect(Collectors.toList());
}
}

172
dao/src/main/java/org/thingsboard/server/dao/cf/CalculatedFieldConfigUtil.java

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

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

@ -18,6 +18,8 @@ package org.thingsboard.server.dao.cf;
import org.thingsboard.server.common.data.cf.CalculatedField;
import org.thingsboard.server.common.data.id.EntityId;
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 java.util.List;
@ -30,6 +32,8 @@ public interface CalculatedFieldDao extends Dao<CalculatedField> {
List<CalculatedField> findAll();
PageData<CalculatedField> findAll(PageLink pageLink);
List<CalculatedField> removeAllByEntityId(TenantId tenantId, EntityId entityId);
}

6
dao/src/main/java/org/thingsboard/server/dao/cf/CalculatedFieldLinkDao.java

@ -18,14 +18,18 @@ package org.thingsboard.server.dao.cf;
import org.thingsboard.server.common.data.cf.CalculatedFieldLink;
import org.thingsboard.server.common.data.id.CalculatedFieldId;
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 java.util.List;
public interface CalculatedFieldLinkDao extends Dao<CalculatedFieldLink> {
CalculatedFieldLink findCalculatedFieldLinkByCalculatedFieldId(TenantId tenantId, CalculatedFieldId calculatedFieldId);
List<CalculatedFieldLink> findCalculatedFieldLinksByCalculatedFieldId(TenantId tenantId, CalculatedFieldId calculatedFieldId);
List<CalculatedFieldLink> findAll();
PageData<CalculatedFieldLink> findAll(PageLink pageLink);
}

12
dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java

@ -22,7 +22,9 @@ import org.thingsboard.server.common.data.DeviceInfo;
import org.thingsboard.server.common.data.DeviceInfoFilter;
import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.ota.OtaPackageType;
import org.thingsboard.server.common.data.page.PageData;
@ -85,6 +87,16 @@ public interface DeviceDao extends Dao<Device>, TenantEntityDao, ExportableEntit
*/
PageData<Device> findDevicesByTenantIdAndType(UUID tenantId, String type, PageLink pageLink);
/**
* Find device ids by tenantId, type and page link.
*
* @param tenantId the tenantId
* @param deviceProfileId the deviceProfileId
* @param pageLink the page link
* @return the list of device objects
*/
PageData<DeviceId> findDeviceIdsByTenantIdAndDeviceProfileId(UUID tenantId, UUID deviceProfileId, PageLink pageLink);
PageData<Device> findDevicesByTenantIdAndTypeAndEmptyOtaPackage(UUID tenantId,
UUID deviceProfileId,
OtaPackageType type,

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

@ -395,6 +395,15 @@ public class DeviceServiceImpl extends CachedVersionedEntityService<DeviceCacheK
return deviceDao.findDevicesByTenantIdAndType(tenantId.getId(), type, pageLink);
}
@Override
public PageData<DeviceId> findDeviceIdsByTenantIdAndDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId, PageLink pageLink) {
log.trace("Executing findDeviceIdsByTenantIdAndType, tenantId [{}], deviceProfileId [{}], pageLink [{}]", tenantId, deviceProfileId, pageLink);
validateId(tenantId, id -> INCORRECT_TENANT_ID + id);
validateId(deviceProfileId, id -> INCORRECT_DEVICE_PROFILE_ID + id);
validatePageLink(pageLink);
return deviceDao.findDeviceIdsByTenantIdAndDeviceProfileId(tenantId.getId(), deviceProfileId.getId(), pageLink);
}
@Override
public PageData<Device> findDevicesByTenantIdAndTypeAndEmptyOtaPackage(TenantId tenantId,
DeviceProfileId deviceProfileId,

5
dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java

@ -134,6 +134,11 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe
return fetchAndConvert(tenantId, entityId, this::getNameLabelAndCustomerDetails);
}
@Override
public Optional<HasId<?>> fetchEntity(TenantId tenantId, EntityId entityId) {
return fetchAndConvert(tenantId, entityId, Function.identity());
}
private <T> Optional<T> fetchAndConvert(TenantId tenantId, EntityId entityId, Function<HasId<?>, T> converter) {
EntityDaoService entityDaoService = entityServiceRegistry.getServiceByEntityType(entityId.getEntityType());
Optional<HasId<?>> entityOpt = entityDaoService.findEntity(tenantId, entityId);

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

@ -24,6 +24,8 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.cf.CalculatedField;
import org.thingsboard.server.common.data.cf.CalculatedFieldConfiguration;
import org.thingsboard.server.common.data.cf.SimpleCalculatedFieldConfiguration;
import org.thingsboard.server.common.data.id.CalculatedFieldId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.TenantId;
@ -33,8 +35,6 @@ import org.thingsboard.server.dao.util.mapping.JsonConverter;
import java.util.UUID;
import static org.thingsboard.server.dao.cf.CalculatedFieldConfigUtil.calculatedFieldConfigToJson;
import static org.thingsboard.server.dao.cf.CalculatedFieldConfigUtil.toCalculatedFieldConfig;
import static org.thingsboard.server.dao.model.ModelConstants.CALCULATED_FIELD_CONFIGURATION;
import static org.thingsboard.server.dao.model.ModelConstants.CALCULATED_FIELD_CONFIGURATION_VERSION;
import static org.thingsboard.server.dao.model.ModelConstants.CALCULATED_FIELD_ENTITY_ID;
@ -93,7 +93,7 @@ public class CalculatedFieldEntity extends BaseSqlEntity<CalculatedField> implem
this.type = calculatedField.getType();
this.name = calculatedField.getName();
this.configurationVersion = calculatedField.getConfigurationVersion();
this.configuration = calculatedFieldConfigToJson(calculatedField.getConfiguration(), entityType, entityId);
this.configuration = calculatedField.getConfiguration().calculatedFieldConfigToJson(entityType, entityId);
this.version = calculatedField.getVersion();
if (calculatedField.getExternalId() != null) {
this.externalId = calculatedField.getExternalId().getId();
@ -109,7 +109,7 @@ public class CalculatedFieldEntity extends BaseSqlEntity<CalculatedField> implem
calculatedField.setType(type);
calculatedField.setName(name);
calculatedField.setConfigurationVersion(configurationVersion);
calculatedField.setConfiguration(toCalculatedFieldConfig(configuration, entityType, entityId));
calculatedField.setConfiguration(readCalculatedFieldConfiguration(configuration, entityType, entityId));
calculatedField.setVersion(version);
if (externalId != null) {
calculatedField.setExternalId(new CalculatedFieldId(externalId));
@ -117,4 +117,14 @@ public class CalculatedFieldEntity extends BaseSqlEntity<CalculatedField> implem
return calculatedField;
}
private CalculatedFieldConfiguration readCalculatedFieldConfiguration(JsonNode config, EntityType entityType, UUID entityId) {
String type = config.get("type").asText();
switch (type) {
case "SIMPLE":
return new SimpleCalculatedFieldConfiguration(config, entityType, entityId);
default:
throw new IllegalArgumentException("Unsupported calculated field type: " + type + "!");
}
}
}

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

@ -22,8 +22,10 @@ import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.cf.CalculatedFieldLink;
import org.thingsboard.server.common.data.cf.CalculatedFiledLinkConfiguration;
import org.thingsboard.server.common.data.id.CalculatedFieldId;
import org.thingsboard.server.common.data.id.CalculatedFieldLinkId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
@ -34,8 +36,6 @@ import org.thingsboard.server.dao.util.mapping.JsonConverter;
import java.util.UUID;
import static org.thingsboard.server.dao.cf.CalculatedFieldConfigUtil.calculatedFieldConfigToJson;
import static org.thingsboard.server.dao.cf.CalculatedFieldConfigUtil.toCalculatedFieldConfig;
import static org.thingsboard.server.dao.model.ModelConstants.CALCULATED_FIELD_LINK_CALCULATED_FIELD_ID;
import static org.thingsboard.server.dao.model.ModelConstants.CALCULATED_FIELD_LINK_CONFIGURATION;
import static org.thingsboard.server.dao.model.ModelConstants.CALCULATED_FIELD_LINK_ENTITY_ID;
@ -65,7 +65,6 @@ public class CalculatedFieldLinkEntity extends BaseSqlEntity<CalculatedFieldLink
@Column(name = CALCULATED_FIELD_LINK_CONFIGURATION)
private JsonNode configuration;
public CalculatedFieldLinkEntity() {
super();
}
@ -77,7 +76,7 @@ public class CalculatedFieldLinkEntity extends BaseSqlEntity<CalculatedFieldLink
this.entityType = calculatedFieldLink.getEntityId().getEntityType();
this.entityId = calculatedFieldLink.getEntityId().getId();
this.calculatedFieldId = calculatedFieldLink.getCalculatedFieldId().getId();
this.configuration = calculatedFieldConfigToJson(calculatedFieldLink.getConfiguration(), entityType, entityId);
this.configuration = JacksonUtil.valueToTree(calculatedFieldLink.getConfiguration());
}
@Override
@ -87,7 +86,7 @@ public class CalculatedFieldLinkEntity extends BaseSqlEntity<CalculatedFieldLink
calculatedFieldLink.setTenantId(TenantId.fromUUID(tenantId));
calculatedFieldLink.setEntityId(EntityIdFactory.getByTypeAndUuid(entityType, entityId));
calculatedFieldLink.setCalculatedFieldId(new CalculatedFieldId(calculatedFieldId));
calculatedFieldLink.setConfiguration(toCalculatedFieldConfig(configuration, entityType, entityId));
calculatedFieldLink.setConfiguration(JacksonUtil.treeToValue(configuration, CalculatedFiledLinkConfiguration.class));
return calculatedFieldLink;
}

10
dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java

@ -20,6 +20,7 @@ import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.util.TbPair;
import org.thingsboard.server.dao.ExportableEntityRepository;
import org.thingsboard.server.dao.model.sql.AssetEntity;
@ -139,6 +140,15 @@ public interface AssetRepository extends JpaRepository<AssetEntity, UUID>, Expor
@Param("textSearch") String textSearch,
Pageable pageable);
@Query("SELECT a.id FROM AssetEntity a " +
"WHERE a.tenantId = :tenantId " +
"AND a.assetProfileId = :assetProfileId " +
"AND (:textSearch IS NULL OR ilike(a.type, CONCAT('%', :textSearch, '%')) = true) ")
Page<UUID> findAssetIdsByTenantIdAndAssetProfileId(@Param("tenantId") UUID tenantId,
@Param("assetProfileId") UUID assetProfileId,
@Param("textSearch") String textSearch,
Pageable pageable);
@Query("SELECT a FROM AssetEntity a WHERE a.tenantId = :tenantId " +
"AND a.customerId = :customerId AND a.type = :type " +

11
dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java

@ -41,6 +41,7 @@ import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
import static org.thingsboard.server.dao.DaoUtil.convertTenantEntityInfosToDto;
@ -159,6 +160,16 @@ public class JpaAssetDao extends JpaAbstractDao<AssetEntity, Asset> implements A
DaoUtil.toPageable(pageLink, AssetInfoEntity.assetInfoColumnMap)));
}
@Override
public PageData<AssetId> findAssetIdsByTenantIdAndAssetProfileId(UUID tenantId, UUID assetProfileId, PageLink pageLink) {
return DaoUtil.pageToPageData(assetRepository.findAssetIdsByTenantIdAndAssetProfileId(
tenantId,
assetProfileId,
pageLink.getTextSearch(),
DaoUtil.toPageable(pageLink)))
.mapData(AssetId::new);
}
@Override
public PageData<Asset> findAssetsByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, PageLink pageLink) {
return DaoUtil.toPageData(assetRepository

3
dao/src/main/java/org/thingsboard/server/dao/sql/cf/CalculatedFieldLinkRepository.java

@ -18,10 +18,11 @@ package org.thingsboard.server.dao.sql.cf;
import org.springframework.data.jpa.repository.JpaRepository;
import org.thingsboard.server.dao.model.sql.CalculatedFieldLinkEntity;
import java.util.List;
import java.util.UUID;
public interface CalculatedFieldLinkRepository extends JpaRepository<CalculatedFieldLinkEntity, UUID> {
CalculatedFieldLinkEntity findByTenantIdAndCalculatedFieldId(UUID tenantId, UUID calculatedFieldId);
List<CalculatedFieldLinkEntity> findAllByTenantIdAndCalculatedFieldId(UUID tenantId, UUID calculatedFieldId);
}

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

@ -0,0 +1,146 @@
/**
* 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.cf;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Pageable;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.support.TransactionTemplate;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.cf.CalculatedField;
import org.thingsboard.server.common.data.cf.CalculatedFieldConfiguration;
import org.thingsboard.server.common.data.cf.CalculatedFieldLink;
import org.thingsboard.server.common.data.cf.CalculatedFiledLinkConfiguration;
import org.thingsboard.server.common.data.cf.SimpleCalculatedFieldConfiguration;
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.common.data.page.PageData;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
@RequiredArgsConstructor
@Repository
@Slf4j
public class DefaultNativeCalculatedFieldRepository implements NativeCalculatedFieldRepository {
private final String CF_COUNT_QUERY = "SELECT count(id) FROM calculated_field;";
private final String CF_QUERY = "SELECT * FROM calculated_field ORDER BY created_time ASC LIMIT %s OFFSET %s";
private final String CFL_COUNT_QUERY = "SELECT count(id) FROM calculated_field_link;";
private final String CFL_QUERY = "SELECT * FROM calculated_field_link ORDER BY created_time ASC LIMIT %s OFFSET %s";
private final NamedParameterJdbcTemplate jdbcTemplate;
private final TransactionTemplate transactionTemplate;
@Override
public PageData<CalculatedField> findCalculatedFields(Pageable pageable) {
return transactionTemplate.execute(status -> {
long startTs = System.currentTimeMillis();
int totalElements = jdbcTemplate.queryForObject(CF_COUNT_QUERY, Collections.emptyMap(), Integer.class);
log.debug("Count query took {} ms", System.currentTimeMillis() - startTs);
startTs = System.currentTimeMillis();
List<Map<String, Object>> rows = jdbcTemplate.queryForList(String.format(CF_QUERY, pageable.getPageSize(), pageable.getOffset()), Collections.emptyMap());
log.debug("Main query took {} ms", System.currentTimeMillis() - startTs);
int totalPages = pageable.getPageSize() > 0 ? (int) Math.ceil((float) totalElements / pageable.getPageSize()) : 1;
boolean hasNext = pageable.getPageSize() > 0 && totalElements > pageable.getOffset() + rows.size();
var data = rows.stream().map(row -> {
UUID id = (UUID) row.get("id");
long createdTime = (long) row.get("created_time");
UUID tenantId = (UUID) row.get("tenant_id");
EntityType entityType = EntityType.valueOf((String) row.get("entity_type"));
UUID entityId = (UUID) row.get("entity_id");
String type = (String) row.get("type");
String name = (String) row.get("name");
int configurationVersion = (int) row.get("configuration_version");
JsonNode configuration = JacksonUtil.valueToTree(row.get("configuration"));
long version = (long) row.get("version");
Object externalIdObj = row.get("external_id");
CalculatedField calculatedField = new CalculatedField();
calculatedField.setId(new CalculatedFieldId(id));
calculatedField.setCreatedTime(createdTime);
calculatedField.setTenantId(new TenantId(tenantId));
calculatedField.setEntityId(EntityIdFactory.getByTypeAndUuid(entityType, entityId));
calculatedField.setType(type);
calculatedField.setName(name);
calculatedField.setConfigurationVersion(configurationVersion);
calculatedField.setConfiguration(readCalculatedFieldConfiguration(configuration, entityType, entityId));
calculatedField.setVersion(version);
calculatedField.setExternalId(externalIdObj != null ? new CalculatedFieldId(UUID.fromString((String) externalIdObj)) : null);
return calculatedField;
}).collect(Collectors.toList());
return new PageData<>(data, totalPages, totalElements, hasNext);
});
}
@Override
public PageData<CalculatedFieldLink> findCalculatedFieldLinks(Pageable pageable) {
return transactionTemplate.execute(status -> {
long startTs = System.currentTimeMillis();
int totalElements = jdbcTemplate.queryForObject(CFL_COUNT_QUERY, Collections.emptyMap(), Integer.class);
log.debug("Count query took {} ms", System.currentTimeMillis() - startTs);
startTs = System.currentTimeMillis();
List<Map<String, Object>> rows = jdbcTemplate.queryForList(String.format(CFL_QUERY, pageable.getPageSize(), pageable.getOffset()), Collections.emptyMap());
log.debug("Main query took {} ms", System.currentTimeMillis() - startTs);
int totalPages = pageable.getPageSize() > 0 ? (int) Math.ceil((float) totalElements / pageable.getPageSize()) : 1;
boolean hasNext = pageable.getPageSize() > 0 && totalElements > pageable.getOffset() + rows.size();
var data = rows.stream().map(row -> {
UUID id = (UUID) row.get("id");
long createdTime = (long) row.get("created_time");
UUID tenantId = (UUID) row.get("tenant_id");
EntityType entityType = EntityType.valueOf((String) row.get("entity_type"));
UUID entityId = (UUID) row.get("entity_id");
UUID calculatedFieldId = (UUID) row.get("calculated_field_id");
JsonNode configuration = JacksonUtil.valueToTree(row.get("configuration"));
CalculatedFieldLink calculatedFieldLink = new CalculatedFieldLink();
calculatedFieldLink.setId(new CalculatedFieldLinkId(id));
calculatedFieldLink.setCreatedTime(createdTime);
calculatedFieldLink.setTenantId(new TenantId(tenantId));
calculatedFieldLink.setEntityId(EntityIdFactory.getByTypeAndUuid(entityType, entityId));
calculatedFieldLink.setCalculatedFieldId(new CalculatedFieldId(calculatedFieldId));
calculatedFieldLink.setConfiguration(JacksonUtil.treeToValue(configuration, CalculatedFiledLinkConfiguration.class));
return calculatedFieldLink;
}).collect(Collectors.toList());
return new PageData<>(data, totalPages, totalElements, hasNext);
});
}
private CalculatedFieldConfiguration readCalculatedFieldConfiguration(JsonNode config, EntityType entityType, UUID entityId) {
String type = config.get("type").asText();
switch (type) {
case "SIMPLE":
return new SimpleCalculatedFieldConfiguration(config, entityType, entityId);
default:
throw new IllegalArgumentException("Unsupported calculated field type: " + type + "!");
}
}
}

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

@ -18,16 +18,20 @@ package org.thingsboard.server.dao.sql.cf;
import jakarta.transaction.Transactional;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.cf.CalculatedField;
import org.thingsboard.server.common.data.id.EntityId;
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.DaoUtil;
import org.thingsboard.server.dao.cf.CalculatedFieldDao;
import org.thingsboard.server.dao.model.sql.CalculatedFieldEntity;
import org.thingsboard.server.dao.sql.JpaAbstractDao;
import org.thingsboard.server.dao.sql.device.NativeDeviceRepository;
import org.thingsboard.server.dao.util.SqlDao;
import java.util.List;
@ -40,6 +44,7 @@ import java.util.UUID;
public class JpaCalculatedFieldDao extends JpaAbstractDao<CalculatedFieldEntity, CalculatedField> implements CalculatedFieldDao {
private final CalculatedFieldRepository calculatedFieldRepository;
private final NativeCalculatedFieldRepository nativeCalculatedFieldRepository;
@Override
public boolean existsByTenantIdAndEntityId(TenantId tenantId, EntityId entityId) {
@ -56,6 +61,12 @@ public class JpaCalculatedFieldDao extends JpaAbstractDao<CalculatedFieldEntity,
return DaoUtil.convertDataList(calculatedFieldRepository.findAll());
}
@Override
public PageData<CalculatedField> findAll(PageLink pageLink) {
log.debug("Try to find calculated fields by pageLink [{}]", pageLink);
return nativeCalculatedFieldRepository.findCalculatedFields(DaoUtil.toPageable(pageLink));
}
@Override
@Transactional
public List<CalculatedField> removeAllByEntityId(TenantId tenantId, EntityId entityId) {

14
dao/src/main/java/org/thingsboard/server/dao/sql/cf/JpaCalculatedFieldLinkDao.java

@ -22,7 +22,10 @@ import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.cf.CalculatedFieldLink;
import org.thingsboard.server.common.data.id.CalculatedFieldId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.cf.CalculatedFieldLinkDao;
import org.thingsboard.server.dao.model.sql.CalculatedFieldLinkEntity;
@ -39,10 +42,11 @@ import java.util.UUID;
public class JpaCalculatedFieldLinkDao extends JpaAbstractDao<CalculatedFieldLinkEntity, CalculatedFieldLink> implements CalculatedFieldLinkDao {
private final CalculatedFieldLinkRepository calculatedFieldLinkRepository;
private final NativeCalculatedFieldRepository nativeCalculatedFieldRepository;
@Override
public CalculatedFieldLink findCalculatedFieldLinkByCalculatedFieldId(TenantId tenantId, CalculatedFieldId calculatedFieldId) {
return DaoUtil.getData(calculatedFieldLinkRepository.findByTenantIdAndCalculatedFieldId(tenantId.getId(), calculatedFieldId.getId()));
public List<CalculatedFieldLink> findCalculatedFieldLinksByCalculatedFieldId(TenantId tenantId, CalculatedFieldId calculatedFieldId) {
return DaoUtil.convertDataList(calculatedFieldLinkRepository.findAllByTenantIdAndCalculatedFieldId(tenantId.getId(), calculatedFieldId.getId()));
}
@Override
@ -50,6 +54,12 @@ public class JpaCalculatedFieldLinkDao extends JpaAbstractDao<CalculatedFieldLin
return DaoUtil.convertDataList(calculatedFieldLinkRepository.findAll());
}
@Override
public PageData<CalculatedFieldLink> findAll(PageLink pageLink) {
log.debug("Try to find calculated field links by pageLink [{}]", pageLink);
return nativeCalculatedFieldRepository.findCalculatedFieldLinks(DaoUtil.toPageable(pageLink));
}
@Override
protected Class<CalculatedFieldLinkEntity> getEntityClass() {
return CalculatedFieldLinkEntity.class;

29
dao/src/main/java/org/thingsboard/server/dao/sql/cf/NativeCalculatedFieldRepository.java

@ -0,0 +1,29 @@
/**
* 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.cf;
import org.springframework.data.domain.Pageable;
import org.thingsboard.server.common.data.cf.CalculatedField;
import org.thingsboard.server.common.data.cf.CalculatedFieldLink;
import org.thingsboard.server.common.data.page.PageData;
public interface NativeCalculatedFieldRepository {
PageData<CalculatedField> findCalculatedFields(Pageable pageable);
PageData<CalculatedFieldLink> findCalculatedFieldLinks(Pageable pageable);
}

8
dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java

@ -81,6 +81,14 @@ public interface DeviceRepository extends JpaRepository<DeviceEntity, UUID>, Exp
@Param("textSearch") String textSearch,
Pageable pageable);
@Query("SELECT d.id FROM DeviceEntity d WHERE d.tenantId = :tenantId " +
"AND d.deviceProfileId = :deviceProfileId " +
"AND (:textSearch IS NULL OR ilike(d.type, CONCAT('%', :textSearch, '%')) = true)")
Page<UUID> findIdsByTenantIdAndDeviceProfileId(@Param("tenantId") UUID tenantId,
@Param("deviceProfileId") UUID deviceProfileId,
@Param("textSearch") String textSearch,
Pageable pageable);
@Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " +
"AND d.deviceProfileId = :deviceProfileId " +
"AND d.firmwareId IS NULL")

11
dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java

@ -173,6 +173,17 @@ public class JpaDeviceDao extends JpaAbstractDao<DeviceEntity, Device> implement
DaoUtil.toPageable(pageLink)));
}
@Override
public PageData<DeviceId> findDeviceIdsByTenantIdAndDeviceProfileId(UUID tenantId, UUID deviceProfileId, PageLink pageLink) {
return DaoUtil.pageToPageData(
deviceRepository.findIdsByTenantIdAndDeviceProfileId(
tenantId,
deviceProfileId,
pageLink.getTextSearch(),
DaoUtil.toPageable(pageLink)))
.mapData(DeviceId::new);
}
@Override
public PageData<Device> findDevicesByTenantIdAndTypeAndEmptyOtaPackage(UUID tenantId,
UUID deviceProfileId,

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

@ -892,7 +892,7 @@ public class AssetServiceTest extends AbstractServiceTest {
config.setOutput(output);
calculatedField.setConfiguration(config);
// calculatedField.setConfiguration(config);
CalculatedField savedCalculatedField = calculatedFieldService.save(calculatedField);

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

@ -151,7 +151,7 @@ public class CalculatedFieldServiceTest extends AbstractServiceTest {
calculatedField.setType("Simple");
calculatedField.setName("Test Calculated Field");
calculatedField.setConfigurationVersion(1);
calculatedField.setConfiguration(getCalculatedFieldConfig(referencedEntityId));
// calculatedField.setConfiguration(getCalculatedFieldConfig(referencedEntityId));
calculatedField.setVersion(1L);
return calculatedField;
}

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

@ -387,7 +387,7 @@ public class CustomerServiceTest extends AbstractServiceTest {
config.setOutput(output);
calculatedField.setConfiguration(config);
// calculatedField.setConfiguration(config);
CalculatedField savedCalculatedField = calculatedFieldService.save(calculatedField);

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

@ -1230,7 +1230,7 @@ public class DeviceServiceTest extends AbstractServiceTest {
config.setOutput(output);
calculatedField.setConfiguration(config);
// calculatedField.setConfiguration(config);
CalculatedField savedCalculatedField = calculatedFieldService.save(calculatedField);

Loading…
Cancel
Save