Browse Source

Merge pull request #12297 from thingsboard/feature/telemetry-service-refactoring

Telemetry service refactoring
pull/12340/head
Viacheslav Klimov 2 years ago
committed by GitHub
parent
commit
a1fd1e3ea4
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 163
      application/src/main/java/org/thingsboard/server/controller/TelemetryController.java
  2. 33
      application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java
  3. 36
      application/src/main/java/org/thingsboard/server/service/device/ClaimDevicesServiceImpl.java
  4. 40
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java
  5. 43
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java
  6. 204
      application/src/main/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewService.java
  7. 100
      application/src/main/java/org/thingsboard/server/service/ota/DefaultOtaPackageStateService.java
  8. 47
      application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java
  9. 22
      application/src/main/java/org/thingsboard/server/service/stats/DefaultRuleEngineStatisticsService.java
  10. 75
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java
  11. 25
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/BaseEntityImportService.java
  12. 13
      application/src/main/java/org/thingsboard/server/service/system/DefaultSystemInfoService.java
  13. 446
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java
  14. 31
      application/src/main/java/org/thingsboard/server/service/telemetry/InternalTelemetryService.java
  15. 66
      application/src/test/java/org/thingsboard/server/controller/WebsocketApiTest.java
  16. 241
      application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java
  17. 6
      common/dao-api/src/main/java/org/thingsboard/server/dao/attributes/AttributesService.java
  18. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java
  19. 14
      dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java
  20. 10
      dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java
  21. 4
      dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java
  22. 117
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/AttributesDeleteRequest.java
  23. 128
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/AttributesSaveRequest.java
  24. 88
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineTelemetryService.java
  25. 85
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TimeseriesDeleteRequest.java
  26. 131
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TimeseriesSaveRequest.java
  27. 20
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java
  28. 32
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/math/TbMathNode.java
  29. 31
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java
  30. 19
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgDeleteAttributesNode.java
  31. 15
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java
  32. 59
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNodeTest.java
  33. 47
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/math/TbMathNodeTest.java
  34. 18
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeTest.java
  35. 16
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgDeleteAttributesNodeTest.java
  36. 54
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNodeTest.java

163
application/src/main/java/org/thingsboard/server/controller/TelemetryController.java

@ -47,6 +47,10 @@ import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult; import org.springframework.web.context.request.async.DeferredResult;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.rule.engine.api.AttributesDeleteRequest;
import org.thingsboard.rule.engine.api.AttributesSaveRequest;
import org.thingsboard.rule.engine.api.TimeseriesDeleteRequest;
import org.thingsboard.rule.engine.api.TimeseriesSaveRequest;
import org.thingsboard.server.common.adaptor.JsonConverter; import org.thingsboard.server.common.adaptor.JsonConverter;
import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
@ -399,7 +403,7 @@ public class TelemetryController extends BaseController {
public DeferredResult<ResponseEntity> saveEntityAttributesV2( public DeferredResult<ResponseEntity> saveEntityAttributesV2(
@Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType, @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType,
@Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, @Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
@Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE"}, requiredMode = Schema.RequiredMode.REQUIRED)) @PathVariable("scope")AttributeScope scope, @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE"}, requiredMode = Schema.RequiredMode.REQUIRED)) @PathVariable("scope") AttributeScope scope,
@io.swagger.v3.oas.annotations.parameters.RequestBody(description = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody JsonNode request) throws ThingsboardException { @io.swagger.v3.oas.annotations.parameters.RequestBody(description = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody JsonNode request) throws ThingsboardException {
EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
return saveAttributes(getTenantId(), entityId, scope, request); return saveAttributes(getTenantId(), entityId, scope, request);
@ -423,8 +427,8 @@ public class TelemetryController extends BaseController {
public DeferredResult<ResponseEntity> saveEntityTelemetry( public DeferredResult<ResponseEntity> saveEntityTelemetry(
@Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType, @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType,
@Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, @Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
@Parameter(description = TELEMETRY_SCOPE_DESCRIPTION, required = true, schema = @Schema(allowableValues = "ANY")) @PathVariable("scope")String scope, @Parameter(description = TELEMETRY_SCOPE_DESCRIPTION, required = true, schema = @Schema(allowableValues = "ANY")) @PathVariable("scope") String scope,
@io.swagger.v3.oas.annotations.parameters.RequestBody(description = TELEMETRY_JSON_REQUEST_DESCRIPTION, required = true)@RequestBody String requestBody) throws ThingsboardException { @io.swagger.v3.oas.annotations.parameters.RequestBody(description = TELEMETRY_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody String requestBody) throws ThingsboardException {
EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
return saveTelemetry(getTenantId(), entityId, requestBody, 0L); return saveTelemetry(getTenantId(), entityId, requestBody, 0L);
} }
@ -447,9 +451,9 @@ public class TelemetryController extends BaseController {
public DeferredResult<ResponseEntity> saveEntityTelemetryWithTTL( public DeferredResult<ResponseEntity> saveEntityTelemetryWithTTL(
@Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType, @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType,
@Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, @Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
@Parameter(description = TELEMETRY_SCOPE_DESCRIPTION, required = true, schema = @Schema(allowableValues = "ANY")) @PathVariable("scope")String scope, @Parameter(description = TELEMETRY_SCOPE_DESCRIPTION, required = true, schema = @Schema(allowableValues = "ANY")) @PathVariable("scope") String scope,
@Parameter(description = "A long value representing TTL (Time to Live) parameter.", required = true)@PathVariable("ttl")Long ttl, @Parameter(description = "A long value representing TTL (Time to Live) parameter.", required = true) @PathVariable("ttl") Long ttl,
@io.swagger.v3.oas.annotations.parameters.RequestBody(description = TELEMETRY_JSON_REQUEST_DESCRIPTION, required = true)@RequestBody String requestBody) throws ThingsboardException { @io.swagger.v3.oas.annotations.parameters.RequestBody(description = TELEMETRY_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody String requestBody) throws ThingsboardException {
EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
return saveTelemetry(getTenantId(), entityId, requestBody, ttl); return saveTelemetry(getTenantId(), entityId, requestBody, ttl);
} }
@ -518,19 +522,25 @@ public class TelemetryController extends BaseController {
for (String key : keys) { for (String key : keys) {
deleteTsKvQueries.add(new BaseDeleteTsKvQuery(key, deleteFromTs, deleteToTs, rewriteLatestIfDeleted, deleteLatest)); deleteTsKvQueries.add(new BaseDeleteTsKvQuery(key, deleteFromTs, deleteToTs, rewriteLatestIfDeleted, deleteLatest));
} }
tsSubService.deleteTimeseriesAndNotify(tenantId, entityId, keys, deleteTsKvQueries, new FutureCallback<>() { tsSubService.deleteTimeseries(TimeseriesDeleteRequest.builder()
@Override .tenantId(tenantId)
public void onSuccess(@Nullable Void tmp) { .entityId(entityId)
logTimeseriesDeleted(user, entityId, keys, deleteFromTs, deleteToTs, null); .keys(keys)
result.setResult(new ResponseEntity<>(HttpStatus.OK)); .deleteHistoryQueries(deleteTsKvQueries)
} .callback(new FutureCallback<>() {
@Override
@Override public void onSuccess(@Nullable List<String> tmp) {
public void onFailure(Throwable t) { logTimeseriesDeleted(user, entityId, keys, deleteFromTs, deleteToTs, null);
logTimeseriesDeleted(user, entityId, keys, deleteFromTs, deleteToTs, t); result.setResult(new ResponseEntity<>(HttpStatus.OK));
result.setResult(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR)); }
}
}); @Override
public void onFailure(Throwable t) {
logTimeseriesDeleted(user, entityId, keys, deleteFromTs, deleteToTs, t);
result.setResult(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR));
}
})
.build());
}); });
} }
@ -550,8 +560,8 @@ public class TelemetryController extends BaseController {
@ResponseBody @ResponseBody
public DeferredResult<ResponseEntity> deleteDeviceAttributes( public DeferredResult<ResponseEntity> deleteDeviceAttributes(
@Parameter(description = DEVICE_ID_PARAM_DESCRIPTION, required = true) @PathVariable(DEVICE_ID) String deviceIdStr, @Parameter(description = DEVICE_ID_PARAM_DESCRIPTION, required = true) @PathVariable(DEVICE_ID) String deviceIdStr,
@Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE", "CLIENT_SCOPE"}, requiredMode = Schema.RequiredMode.REQUIRED)) @PathVariable("scope")AttributeScope scope, @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE", "CLIENT_SCOPE"}, requiredMode = Schema.RequiredMode.REQUIRED)) @PathVariable("scope") AttributeScope scope,
@Parameter(description = ATTRIBUTES_KEYS_DESCRIPTION, required = true)@RequestParam(name = "keys")String keysStr) throws ThingsboardException { @Parameter(description = ATTRIBUTES_KEYS_DESCRIPTION, required = true) @RequestParam(name = "keys") String keysStr) throws ThingsboardException {
EntityId entityId = EntityIdFactory.getByTypeAndUuid(EntityType.DEVICE, deviceIdStr); EntityId entityId = EntityIdFactory.getByTypeAndUuid(EntityType.DEVICE, deviceIdStr);
return deleteAttributes(entityId, scope, keysStr); return deleteAttributes(entityId, scope, keysStr);
} }
@ -573,8 +583,8 @@ public class TelemetryController extends BaseController {
public DeferredResult<ResponseEntity> deleteEntityAttributes( public DeferredResult<ResponseEntity> deleteEntityAttributes(
@Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType, @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType,
@Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, @Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
@Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, required = true, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE", "CLIENT_SCOPE"})) @PathVariable("scope")AttributeScope scope, @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, required = true, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE", "CLIENT_SCOPE"})) @PathVariable("scope") AttributeScope scope,
@Parameter(description = ATTRIBUTES_KEYS_DESCRIPTION, required = true)@RequestParam(name = "keys")String keysStr) throws ThingsboardException { @Parameter(description = ATTRIBUTES_KEYS_DESCRIPTION, required = true) @RequestParam(name = "keys") String keysStr) throws ThingsboardException {
EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
return deleteAttributes(entityId, scope, keysStr); return deleteAttributes(entityId, scope, keysStr);
} }
@ -587,24 +597,30 @@ public class TelemetryController extends BaseController {
SecurityUser user = getCurrentUser(); SecurityUser user = getCurrentUser();
return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.WRITE_ATTRIBUTES, entityIdSrc, (result, tenantId, entityId) -> { return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.WRITE_ATTRIBUTES, entityIdSrc, (result, tenantId, entityId) -> {
tsSubService.deleteAndNotify(tenantId, entityId, scope.name(), keys, new FutureCallback<Void>() { tsSubService.deleteAttributes(AttributesDeleteRequest.builder()
@Override .tenantId(tenantId)
public void onSuccess(@Nullable Void tmp) { .entityId(entityId)
logAttributesDeleted(user, entityId, scope, keys, null); .scope(scope)
if (entityIdSrc.getEntityType().equals(EntityType.DEVICE)) { .keys(keys)
DeviceId deviceId = new DeviceId(entityId.getId()); .callback(new FutureCallback<>() {
tbClusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onDelete( @Override
user.getTenantId(), deviceId, scope.name(), keys), null); public void onSuccess(@Nullable Void tmp) {
} logAttributesDeleted(user, entityId, scope, keys, null);
result.setResult(new ResponseEntity<>(HttpStatus.OK)); if (entityIdSrc.getEntityType().equals(EntityType.DEVICE)) {
} DeviceId deviceId = new DeviceId(entityId.getId());
tbClusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onDelete(
@Override user.getTenantId(), deviceId, scope.name(), keys), null);
public void onFailure(Throwable t) { }
logAttributesDeleted(user, entityId, scope, keys, t); result.setResult(new ResponseEntity<>(HttpStatus.OK));
result.setResult(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR)); }
}
}); @Override
public void onFailure(Throwable t) {
logAttributesDeleted(user, entityId, scope, keys, t);
result.setResult(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR));
}
})
.build());
}); });
} }
@ -624,19 +640,25 @@ public class TelemetryController extends BaseController {
} }
SecurityUser user = getCurrentUser(); SecurityUser user = getCurrentUser();
return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.WRITE_ATTRIBUTES, entityIdSrc, (result, tenantId, entityId) -> { return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.WRITE_ATTRIBUTES, entityIdSrc, (result, tenantId, entityId) -> {
tsSubService.saveAndNotify(tenantId, entityId, scope, attributes, new FutureCallback<Void>() { tsSubService.saveAttributes(AttributesSaveRequest.builder()
@Override .tenantId(tenantId)
public void onSuccess(@Nullable Void tmp) { .entityId(entityId)
logAttributesUpdated(user, entityId, scope, attributes, null); .scope(scope)
result.setResult(new ResponseEntity(HttpStatus.OK)); .entries(attributes)
} .callback(new FutureCallback<>() {
@Override
@Override public void onSuccess(@Nullable Void tmp) {
public void onFailure(Throwable t) { logAttributesUpdated(user, entityId, scope, attributes, null);
logAttributesUpdated(user, entityId, scope, attributes, t); result.setResult(new ResponseEntity(HttpStatus.OK));
AccessValidator.handleError(t, result, HttpStatus.INTERNAL_SERVER_ERROR); }
}
}); @Override
public void onFailure(Throwable t) {
logAttributesUpdated(user, entityId, scope, attributes, t);
AccessValidator.handleError(t, result, HttpStatus.INTERNAL_SERVER_ERROR);
}
})
.build());
}); });
} else { } else {
return getImmediateDeferredResult("Request is not a JSON object", HttpStatus.BAD_REQUEST); return getImmediateDeferredResult("Request is not a JSON object", HttpStatus.BAD_REQUEST);
@ -672,19 +694,26 @@ public class TelemetryController extends BaseController {
TenantProfile tenantProfile = tenantProfileCache.get(tenantId); TenantProfile tenantProfile = tenantProfileCache.get(tenantId);
tenantTtl = TimeUnit.DAYS.toSeconds(((DefaultTenantProfileConfiguration) tenantProfile.getProfileData().getConfiguration()).getDefaultStorageTtlDays()); tenantTtl = TimeUnit.DAYS.toSeconds(((DefaultTenantProfileConfiguration) tenantProfile.getProfileData().getConfiguration()).getDefaultStorageTtlDays());
} }
tsSubService.saveAndNotify(tenantId, user.getCustomerId(), entityId, entries, tenantTtl, new FutureCallback<Void>() { tsSubService.saveTimeseries(TimeseriesSaveRequest.builder()
@Override .tenantId(tenantId)
public void onSuccess(@Nullable Void tmp) { .customerId(user.getCustomerId())
logTelemetryUpdated(user, entityId, entries, null); .entityId(entityId)
result.setResult(new ResponseEntity(HttpStatus.OK)); .entries(entries)
} .ttl(tenantTtl)
.callback(new FutureCallback<Void>() {
@Override @Override
public void onFailure(Throwable t) { public void onSuccess(@Nullable Void tmp) {
logTelemetryUpdated(user, entityId, entries, t); logTelemetryUpdated(user, entityId, entries, null);
AccessValidator.handleError(t, result, HttpStatus.INTERNAL_SERVER_ERROR); result.setResult(new ResponseEntity(HttpStatus.OK));
} }
});
@Override
public void onFailure(Throwable t) {
logTelemetryUpdated(user, entityId, entries, t);
AccessValidator.handleError(t, result, HttpStatus.INTERNAL_SERVER_ERROR);
}
})
.build());
}); });
} }

33
application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java

@ -27,6 +27,7 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.rule.engine.api.TimeseriesSaveRequest;
import org.thingsboard.server.common.data.ApiFeature; import org.thingsboard.server.common.data.ApiFeature;
import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageRecordKey;
import org.thingsboard.server.common.data.ApiUsageRecordState; import org.thingsboard.server.common.data.ApiUsageRecordState;
@ -91,9 +92,9 @@ import java.util.stream.Collectors;
public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService<EntityId> implements TbApiUsageStateService { public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService<EntityId> implements TbApiUsageStateService {
public static final String HOURLY = "Hourly"; public static final String HOURLY = "Hourly";
public static final FutureCallback<Integer> VOID_CALLBACK = new FutureCallback<Integer>() { public static final FutureCallback<Void> VOID_CALLBACK = new FutureCallback<Void>() {
@Override @Override
public void onSuccess(@Nullable Integer result) { public void onSuccess(@Nullable Void result) {
} }
@Override @Override
@ -214,7 +215,12 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService
updateLock.unlock(); updateLock.unlock();
} }
log.trace("[{}][{}] Saving new stats: {}", tenantId, ownerId, updatedEntries); log.trace("[{}][{}] Saving new stats: {}", tenantId, ownerId, updatedEntries);
tsWsService.saveAndNotifyInternal(tenantId, usageState.getApiUsageState().getId(), updatedEntries, VOID_CALLBACK); tsWsService.saveTimeseriesInternal(TimeseriesSaveRequest.builder()
.tenantId(tenantId)
.entityId(usageState.getApiUsageState().getId())
.entries(updatedEntries)
.callback(VOID_CALLBACK)
.build());
if (!result.isEmpty()) { if (!result.isEmpty()) {
persistAndNotify(usageState, result); persistAndNotify(usageState, result);
} }
@ -321,7 +327,12 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService
} }
} }
if (!profileThresholds.isEmpty()) { if (!profileThresholds.isEmpty()) {
tsWsService.saveAndNotifyInternal(tenantId, id, profileThresholds, VOID_CALLBACK); tsWsService.saveTimeseriesInternal(TimeseriesSaveRequest.builder()
.tenantId(tenantId)
.entityId(id)
.entries(profileThresholds)
.callback(VOID_CALLBACK)
.build());
} }
} }
@ -348,7 +359,12 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService
long ts = System.currentTimeMillis(); long ts = System.currentTimeMillis();
List<TsKvEntry> stateTelemetry = new ArrayList<>(); List<TsKvEntry> stateTelemetry = new ArrayList<>();
result.forEach((apiFeature, aState) -> stateTelemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(apiFeature.getApiStateKey(), aState.name())))); result.forEach((apiFeature, aState) -> stateTelemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(apiFeature.getApiStateKey(), aState.name()))));
tsWsService.saveAndNotifyInternal(state.getTenantId(), state.getApiUsageState().getId(), stateTelemetry, VOID_CALLBACK); tsWsService.saveTimeseriesInternal(TimeseriesSaveRequest.builder()
.tenantId(state.getTenantId())
.entityId(state.getApiUsageState().getId())
.entries(stateTelemetry)
.callback(VOID_CALLBACK)
.build());
if (state.getEntityType() == EntityType.TENANT && !state.getEntityId().equals(TenantId.SYS_TENANT_ID)) { if (state.getEntityType() == EntityType.TENANT && !state.getEntityId().equals(TenantId.SYS_TENANT_ID)) {
String email = tenantService.findTenantById(state.getTenantId()).getEmail(); String email = tenantService.findTenantById(state.getTenantId()).getEmail();
@ -436,7 +452,12 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService
.map(key -> new BasicTsKvEntry(state.getCurrentCycleTs(), new LongDataEntry(key.getApiCountKey(), 0L))) .map(key -> new BasicTsKvEntry(state.getCurrentCycleTs(), new LongDataEntry(key.getApiCountKey(), 0L)))
.collect(Collectors.toList()); .collect(Collectors.toList());
tsWsService.saveAndNotifyInternal(state.getTenantId(), state.getApiUsageState().getId(), counts, VOID_CALLBACK); tsWsService.saveTimeseriesInternal(TimeseriesSaveRequest.builder()
.tenantId(state.getTenantId())
.entityId(state.getApiUsageState().getId())
.entries(counts)
.callback(VOID_CALLBACK)
.build());
} }
BaseApiUsageState getOrFetchState(TenantId tenantId, EntityId ownerId) { BaseApiUsageState getOrFetchState(TenantId tenantId, EntityId ownerId) {

36
application/src/main/java/org/thingsboard/server/service/device/ClaimDevicesServiceImpl.java

@ -28,6 +28,8 @@ import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager; import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.AttributesDeleteRequest;
import org.thingsboard.rule.engine.api.AttributesSaveRequest;
import org.thingsboard.rule.engine.api.RuleEngineTelemetryService; import org.thingsboard.rule.engine.api.RuleEngineTelemetryService;
import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
@ -37,7 +39,6 @@ import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.BooleanDataEntry; import org.thingsboard.server.common.data.kv.BooleanDataEntry;
import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.customer.CustomerService;
@ -178,11 +179,12 @@ public class ClaimDevicesServiceImpl implements ClaimDevicesService {
return Futures.immediateFuture(new ReclaimResult(unassignedCustomer)); return Futures.immediateFuture(new ReclaimResult(unassignedCustomer));
} }
SettableFuture<ReclaimResult> result = SettableFuture.create(); SettableFuture<ReclaimResult> result = SettableFuture.create();
telemetryService.saveAndNotify( telemetryService.saveAttributes(AttributesSaveRequest.builder()
tenantId, savedDevice.getId(), AttributeScope.SERVER_SCOPE, List.of( .tenantId(tenantId)
new BaseAttributeKvEntry(new BooleanDataEntry(CLAIM_ATTRIBUTE_NAME, true), System.currentTimeMillis()) .entityId(savedDevice.getId())
), .scope(AttributeScope.SERVER_SCOPE)
new FutureCallback<>() { .entry(new BooleanDataEntry(CLAIM_ATTRIBUTE_NAME, true))
.callback(new FutureCallback<>() {
@Override @Override
public void onSuccess(@Nullable Void tmp) { public void onSuccess(@Nullable Void tmp) {
result.set(new ReclaimResult(unassignedCustomer)); result.set(new ReclaimResult(unassignedCustomer));
@ -192,7 +194,8 @@ public class ClaimDevicesServiceImpl implements ClaimDevicesService {
public void onFailure(Throwable t) { public void onFailure(Throwable t) {
result.setException(t); result.setException(t);
} }
}); })
.build());
return result; return result;
} }
cacheEviction(device.getId()); cacheEviction(device.getId());
@ -221,18 +224,13 @@ public class ClaimDevicesServiceImpl implements ClaimDevicesService {
cache.evict(data.getKey()); cache.evict(data.getKey());
} }
SettableFuture<Void> result = SettableFuture.create(); SettableFuture<Void> result = SettableFuture.create();
telemetryService.deleteAndNotify(device.getTenantId(), telemetryService.deleteAttributes(AttributesDeleteRequest.builder()
device.getId(), AttributeScope.SERVER_SCOPE, Arrays.asList(CLAIM_ATTRIBUTE_NAME, CLAIM_DATA_ATTRIBUTE_NAME), new FutureCallback<>() { .tenantId(device.getTenantId())
@Override .entityId(device.getId())
public void onSuccess(@Nullable Void tmp) { .scope(AttributeScope.SERVER_SCOPE)
result.set(tmp); .keys(Arrays.asList(CLAIM_ATTRIBUTE_NAME, CLAIM_DATA_ATTRIBUTE_NAME))
} .future(result)
.build());
@Override
public void onFailure(Throwable t) {
result.setException(t);
}
});
return result; return result;
} }

40
application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java

@ -32,6 +32,8 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.rule.engine.api.AttributesSaveRequest;
import org.thingsboard.rule.engine.api.TimeseriesSaveRequest;
import org.thingsboard.server.cache.TbTransactionalCache; import org.thingsboard.server.cache.TbTransactionalCache;
import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.AttributeScope;
@ -41,7 +43,6 @@ import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.BooleanDataEntry; import org.thingsboard.server.common.data.kv.BooleanDataEntry;
import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.msg.TbMsgType;
@ -68,7 +69,6 @@ import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
@ -505,24 +505,40 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i
private void save(TenantId tenantId, EdgeId edgeId, String key, long value) { private void save(TenantId tenantId, EdgeId edgeId, String key, long value) {
log.debug("[{}][{}] Updating long edge telemetry [{}] [{}]", tenantId, edgeId, key, value); log.debug("[{}][{}] Updating long edge telemetry [{}] [{}]", tenantId, edgeId, key, value);
if (persistToTelemetry) { if (persistToTelemetry) {
tsSubService.saveAndNotify( tsSubService.saveTimeseries(TimeseriesSaveRequest.builder()
tenantId, edgeId, .tenantId(tenantId)
Collections.singletonList(new BasicTsKvEntry(System.currentTimeMillis(), new LongDataEntry(key, value))), .entityId(edgeId)
new AttributeSaveCallback(tenantId, edgeId, key, value)); .entry(new LongDataEntry(key, value))
.callback(new AttributeSaveCallback(tenantId, edgeId, key, value))
.build());
} else { } else {
tsSubService.saveAttrAndNotify(tenantId, edgeId, AttributeScope.SERVER_SCOPE, key, value, new AttributeSaveCallback(tenantId, edgeId, key, value)); tsSubService.saveAttributes(AttributesSaveRequest.builder()
.tenantId(tenantId)
.entityId(edgeId)
.scope(AttributeScope.SERVER_SCOPE)
.entry(new LongDataEntry(key, value))
.callback(new AttributeSaveCallback(tenantId, edgeId, key, value))
.build());
} }
} }
private void save(TenantId tenantId, EdgeId edgeId, String key, boolean value) { private void save(TenantId tenantId, EdgeId edgeId, String key, boolean value) {
log.debug("[{}][{}] Updating boolean edge telemetry [{}] [{}]", tenantId, edgeId, key, value); log.debug("[{}][{}] Updating boolean edge telemetry [{}] [{}]", tenantId, edgeId, key, value);
if (persistToTelemetry) { if (persistToTelemetry) {
tsSubService.saveAndNotify( tsSubService.saveTimeseries(TimeseriesSaveRequest.builder()
tenantId, edgeId, .tenantId(tenantId)
Collections.singletonList(new BasicTsKvEntry(System.currentTimeMillis(), new BooleanDataEntry(key, value))), .entityId(edgeId)
new AttributeSaveCallback(tenantId, edgeId, key, value)); .entry(new BooleanDataEntry(key, value))
.callback(new AttributeSaveCallback(tenantId, edgeId, key, value))
.build());
} else { } else {
tsSubService.saveAttrAndNotify(tenantId, edgeId, AttributeScope.SERVER_SCOPE, key, value, new AttributeSaveCallback(tenantId, edgeId, key, value)); tsSubService.saveAttributes(AttributesSaveRequest.builder()
.tenantId(tenantId)
.entityId(edgeId)
.scope(AttributeScope.SERVER_SCOPE)
.entry(new BooleanDataEntry(key, value))
.callback(new AttributeSaveCallback(tenantId, edgeId, key, value))
.build());
} }
} }

43
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java

@ -31,6 +31,7 @@ import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.AttributesSaveRequest;
import org.thingsboard.server.common.adaptor.JsonConverter; import org.thingsboard.server.common.adaptor.JsonConverter;
import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
@ -277,16 +278,29 @@ public abstract class BaseTelemetryProcessor extends BaseEdgeProcessor {
JsonObject json = JsonUtils.getJsonObject(msg.getKvList()); JsonObject json = JsonUtils.getJsonObject(msg.getKvList());
List<AttributeKvEntry> attributes = new ArrayList<>(JsonConverter.convertToAttributes(json)); List<AttributeKvEntry> attributes = new ArrayList<>(JsonConverter.convertToAttributes(json));
String scope = metaData.getValue("scope"); String scope = metaData.getValue("scope");
tsSubService.saveAndNotify(tenantId, entityId, AttributeScope.valueOf(scope), attributes, new FutureCallback<Void>() { tsSubService.saveAttributes(AttributesSaveRequest.builder()
@Override .tenantId(tenantId)
public void onSuccess(@Nullable Void tmp) { .entityId(entityId)
var defaultQueueAndRuleChain = getDefaultQueueNameAndRuleChainId(tenantId, entityId); .scope(AttributeScope.valueOf(scope))
TbMsg tbMsg = TbMsg.newMsg(defaultQueueAndRuleChain.getKey(), TbMsgType.ATTRIBUTES_UPDATED, entityId, .entries(attributes)
customerId, metaData, gson.toJson(json), defaultQueueAndRuleChain.getValue(), null); .callback(new FutureCallback<>() {
edgeCtx.getClusterService().pushMsgToRuleEngine(tenantId, tbMsg.getOriginator(), tbMsg, new TbQueueCallback() {
@Override @Override
public void onSuccess(TbQueueMsgMetadata metadata) { public void onSuccess(@Nullable Void tmp) {
futureToSet.set(null); var defaultQueueAndRuleChain = getDefaultQueueNameAndRuleChainId(tenantId, entityId);
TbMsg tbMsg = TbMsg.newMsg(defaultQueueAndRuleChain.getKey(), TbMsgType.ATTRIBUTES_UPDATED, entityId,
customerId, metaData, gson.toJson(json), defaultQueueAndRuleChain.getValue(), null);
edgeCtx.getClusterService().pushMsgToRuleEngine(tenantId, tbMsg.getOriginator(), tbMsg, new TbQueueCallback() {
@Override
public void onSuccess(TbQueueMsgMetadata metadata) {
futureToSet.set(null);
}
@Override
public void onFailure(Throwable t) {
log.error("[{}] Can't process attributes update [{}]", tenantId, msg, t);
futureToSet.setException(t);
}
});
} }
@Override @Override
@ -294,15 +308,8 @@ public abstract class BaseTelemetryProcessor extends BaseEdgeProcessor {
log.error("[{}] Can't process attributes update [{}]", tenantId, msg, t); log.error("[{}] Can't process attributes update [{}]", tenantId, msg, t);
futureToSet.setException(t); futureToSet.setException(t);
} }
}); })
} .build());
@Override
public void onFailure(Throwable t) {
log.error("[{}] Can't process attributes update [{}]", tenantId, msg, t);
futureToSet.setException(t);
}
});
return futureToSet; return futureToSet;
} }

204
application/src/main/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewService.java

@ -25,6 +25,10 @@ import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.ConcurrentReferenceHashMap; import org.springframework.util.ConcurrentReferenceHashMap;
import org.thingsboard.rule.engine.api.AttributesDeleteRequest;
import org.thingsboard.rule.engine.api.AttributesSaveRequest;
import org.thingsboard.rule.engine.api.TimeseriesDeleteRequest;
import org.thingsboard.rule.engine.api.TimeseriesSaveRequest;
import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
@ -55,6 +59,7 @@ import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -273,36 +278,41 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen
return Futures.transform(getAttrFuture, attributeKvEntries -> { return Futures.transform(getAttrFuture, attributeKvEntries -> {
List<AttributeKvEntry> attributes; List<AttributeKvEntry> attributes;
if (attributeKvEntries != null && !attributeKvEntries.isEmpty()) { if (attributeKvEntries != null && !attributeKvEntries.isEmpty()) {
attributes = attributes = attributeKvEntries.stream()
attributeKvEntries.stream() .filter(attributeKvEntry -> {
.filter(attributeKvEntry -> { long startTime = entityView.getStartTimeMs();
long startTime = entityView.getStartTimeMs(); long endTime = entityView.getEndTimeMs();
long endTime = entityView.getEndTimeMs(); long lastUpdateTs = attributeKvEntry.getLastUpdateTs();
long lastUpdateTs = attributeKvEntry.getLastUpdateTs(); return startTime == 0 && endTime == 0 ||
return startTime == 0 && endTime == 0 || (endTime == 0 && startTime < lastUpdateTs) ||
(endTime == 0 && startTime < lastUpdateTs) || (startTime == 0 && endTime > lastUpdateTs) ||
(startTime == 0 && endTime > lastUpdateTs) || (startTime < lastUpdateTs && endTime > lastUpdateTs);
(startTime < lastUpdateTs && endTime > lastUpdateTs); }).collect(Collectors.toList());
}).collect(Collectors.toList()); tsSubService.saveAttributes(AttributesSaveRequest.builder()
tsSubService.saveAndNotify(entityView.getTenantId(), entityId, scope, attributes, new FutureCallback<Void>() { .tenantId(entityView.getTenantId())
@Override .entityId(entityId)
public void onSuccess(@Nullable Void tmp) { .scope(scope)
try { .entries(attributes)
logAttributesUpdated(entityView.getTenantId(), user, entityId, scope, attributes, null); .callback(new FutureCallback<>() {
} catch (ThingsboardException e) { @Override
log.error("Failed to log attribute updates", e); public void onSuccess(@Nullable Void tmp) {
} try {
} logAttributesUpdated(entityView.getTenantId(), user, entityId, scope, attributes, null);
} catch (ThingsboardException e) {
@Override log.error("Failed to log attribute updates", e);
public void onFailure(Throwable t) { }
try { }
logAttributesUpdated(entityView.getTenantId(), user, entityId, scope, attributes, t);
} catch (ThingsboardException e) { @Override
log.error("Failed to log attribute updates", e); public void onFailure(Throwable t) {
} try {
} logAttributesUpdated(entityView.getTenantId(), user, entityId, scope, attributes, t);
}); } catch (ThingsboardException e) {
log.error("Failed to log attribute updates", e);
}
}
})
.build());
} }
return null; return null;
}, MoreExecutors.directExecutor()); }, MoreExecutors.directExecutor());
@ -334,15 +344,22 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen
}, MoreExecutors.directExecutor()); }, MoreExecutors.directExecutor());
return Futures.transform(latestFuture, latestValues -> { return Futures.transform(latestFuture, latestValues -> {
if (latestValues != null && !latestValues.isEmpty()) { if (latestValues != null && !latestValues.isEmpty()) {
tsSubService.saveLatestAndNotify(entityView.getTenantId(), entityId, latestValues, new FutureCallback<Void>() { tsSubService.saveTimeseries(TimeseriesSaveRequest.builder()
@Override .tenantId(entityView.getTenantId())
public void onSuccess(@Nullable Void tmp) { .entityId(entityId)
} .entries(latestValues)
.onlyLatest(true)
.callback(new FutureCallback<Void>() {
@Override
public void onSuccess(@Nullable Void tmp) {
}
@Override @Override
public void onFailure(Throwable t) { public void onFailure(Throwable t) {
} log.error("[{}][{}] Failed to save entity view latest timeseries: {}", tenantId, entityView.getId(), latestValues, t);
}); }
})
.build());
} }
return null; return null;
}, MoreExecutors.directExecutor()); }, MoreExecutors.directExecutor());
@ -352,27 +369,33 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen
EntityViewId entityId = entityView.getId(); EntityViewId entityId = entityView.getId();
SettableFuture<Void> resultFuture = SettableFuture.create(); SettableFuture<Void> resultFuture = SettableFuture.create();
if (keys != null && !keys.isEmpty()) { if (keys != null && !keys.isEmpty()) {
tsSubService.deleteAndNotify(entityView.getTenantId(), entityId, scope, keys, new FutureCallback<Void>() { tsSubService.deleteAttributes(AttributesDeleteRequest.builder()
@Override .tenantId(entityView.getTenantId())
public void onSuccess(@Nullable Void tmp) { .entityId(entityId)
try { .scope(scope)
logAttributesDeleted(entityView.getTenantId(), user, entityId, scope, keys, null); .keys(keys)
} catch (ThingsboardException e) { .callback(new FutureCallback<>() {
log.error("Failed to log attribute delete", e); @Override
} public void onSuccess(@Nullable Void tmp) {
resultFuture.set(tmp); try {
} logAttributesDeleted(entityView.getTenantId(), user, entityId, scope, keys, null);
} catch (ThingsboardException e) {
log.error("Failed to log attribute delete", e);
}
resultFuture.set(tmp);
}
@Override @Override
public void onFailure(Throwable t) { public void onFailure(Throwable t) {
try { try {
logAttributesDeleted(entityView.getTenantId(), user, entityId, scope, keys, t); logAttributesDeleted(entityView.getTenantId(), user, entityId, scope, keys, t);
} catch (ThingsboardException e) { } catch (ThingsboardException e) {
log.error("Failed to log attribute delete", e); log.error("Failed to log attribute delete", e);
} }
resultFuture.setException(t); resultFuture.setException(t);
} }
}); })
.build());
} else { } else {
resultFuture.set(null); resultFuture.set(null);
} }
@ -382,51 +405,32 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen
private ListenableFuture<Void> deleteLatestFromEntityView(EntityView entityView, List<String> keys, User user) { private ListenableFuture<Void> deleteLatestFromEntityView(EntityView entityView, List<String> keys, User user) {
EntityViewId entityId = entityView.getId(); EntityViewId entityId = entityView.getId();
SettableFuture<Void> resultFuture = SettableFuture.create(); SettableFuture<Void> resultFuture = SettableFuture.create();
if (keys != null && !keys.isEmpty()) { tsSubService.deleteTimeseries(TimeseriesDeleteRequest.builder()
tsSubService.deleteLatest(entityView.getTenantId(), entityId, keys, new FutureCallback<Void>() { .tenantId(entityView.getTenantId())
@Override .entityId(entityId)
public void onSuccess(@Nullable Void tmp) { .keys(keys)
try { .callback(new FutureCallback<>() {
logTimeseriesDeleted(entityView.getTenantId(), user, entityId, keys, null); @Override
} catch (ThingsboardException e) { public void onSuccess(@Nullable List<String> result) {
log.error("Failed to log timeseries delete", e); try {
} logTimeseriesDeleted(entityView.getTenantId(), user, entityId, result, null);
resultFuture.set(tmp); } catch (ThingsboardException e) {
} log.error("Failed to log timeseries delete", e);
}
@Override resultFuture.set(null);
public void onFailure(Throwable t) {
try {
logTimeseriesDeleted(entityView.getTenantId(), user, entityId, keys, t);
} catch (ThingsboardException e) {
log.error("Failed to log timeseries delete", e);
}
resultFuture.setException(t);
}
});
} else {
tsSubService.deleteAllLatest(entityView.getTenantId(), entityId, new FutureCallback<Collection<String>>() {
@Override
public void onSuccess(@Nullable Collection<String> keys) {
try {
logTimeseriesDeleted(entityView.getTenantId(), user, entityId, new ArrayList<>(keys), null);
} catch (ThingsboardException e) {
log.error("Failed to log timeseries delete", e);
} }
resultFuture.set(null);
}
@Override @Override
public void onFailure(Throwable t) { public void onFailure(Throwable t) {
try { try {
logTimeseriesDeleted(entityView.getTenantId(), user, entityId, Collections.emptyList(), t); logTimeseriesDeleted(entityView.getTenantId(), user, entityId, Optional.ofNullable(keys).orElse(Collections.emptyList()), t);
} catch (ThingsboardException e) { } catch (ThingsboardException e) {
log.error("Failed to log timeseries delete", e); log.error("Failed to log timeseries delete", e);
}
resultFuture.setException(t);
} }
resultFuture.setException(t); })
} .build());
});
}
return resultFuture; return resultFuture;
} }

100
application/src/main/java/org/thingsboard/server/service/ota/DefaultOtaPackageStateService.java

@ -20,7 +20,10 @@ import jakarta.annotation.Nullable;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.rule.engine.api.AttributesDeleteRequest;
import org.thingsboard.rule.engine.api.AttributesSaveRequest;
import org.thingsboard.rule.engine.api.RuleEngineTelemetryService; import org.thingsboard.rule.engine.api.RuleEngineTelemetryService;
import org.thingsboard.rule.engine.api.TimeseriesSaveRequest;
import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
@ -54,7 +57,6 @@ import org.thingsboard.server.queue.provider.TbCoreQueueFactory;
import org.thingsboard.server.queue.provider.TbRuleEngineQueueFactory; import org.thingsboard.server.queue.provider.TbRuleEngineQueueFactory;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
@ -128,7 +130,7 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService {
// Device was updated and new firmware is different from previous firmware. // Device was updated and new firmware is different from previous firmware.
send(device.getTenantId(), device.getId(), newFirmwareId, System.currentTimeMillis(), FIRMWARE); send(device.getTenantId(), device.getId(), newFirmwareId, System.currentTimeMillis(), FIRMWARE);
} }
} else if (oldFirmwareId != null){ } else if (oldFirmwareId != null) {
// Device was updated and new firmware is not set. // Device was updated and new firmware is not set.
remove(device, FIRMWARE); remove(device, FIRMWARE);
} }
@ -155,7 +157,7 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService {
// Device was updated and new firmware is different from previous firmware. // Device was updated and new firmware is different from previous firmware.
send(device.getTenantId(), device.getId(), newSoftwareId, System.currentTimeMillis(), SOFTWARE); send(device.getTenantId(), device.getId(), newSoftwareId, System.currentTimeMillis(), SOFTWARE);
} }
} else if (oldSoftwareId != null){ } else if (oldSoftwareId != null) {
// Device was updated and new firmware is not set. // Device was updated and new firmware is not set.
remove(device, SOFTWARE); remove(device, SOFTWARE);
} }
@ -261,17 +263,22 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService {
telemetry.add(new BasicTsKvEntry(ts, new LongDataEntry(getTargetTelemetryKey(firmware.getType(), TS), ts))); telemetry.add(new BasicTsKvEntry(ts, new LongDataEntry(getTargetTelemetryKey(firmware.getType(), TS), ts)));
telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(getTelemetryKey(firmware.getType(), STATE), OtaPackageUpdateStatus.QUEUED.name()))); telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(getTelemetryKey(firmware.getType(), STATE), OtaPackageUpdateStatus.QUEUED.name())));
telemetryService.saveAndNotify(tenantId, deviceId, telemetry, new FutureCallback<>() { telemetryService.saveTimeseries(TimeseriesSaveRequest.builder()
@Override .tenantId(tenantId)
public void onSuccess(@Nullable Void tmp) { .entityId(deviceId)
log.trace("[{}] Success save firmware status!", deviceId); .entries(telemetry)
} .callback(new FutureCallback<Void>() {
@Override
public void onSuccess(@Nullable Void tmp) {
log.trace("[{}] Success save firmware status!", deviceId);
}
@Override @Override
public void onFailure(Throwable t) { public void onFailure(Throwable t) {
log.error("[{}] Failed to save firmware status!", deviceId, t); log.error("[{}] Failed to save firmware status!", deviceId, t);
} }
}); })
.build());
} }
@ -282,19 +289,24 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService {
BasicTsKvEntry status = new BasicTsKvEntry(System.currentTimeMillis(), new StringDataEntry(getTelemetryKey(otaPackageType, STATE), OtaPackageUpdateStatus.INITIATED.name())); BasicTsKvEntry status = new BasicTsKvEntry(System.currentTimeMillis(), new StringDataEntry(getTelemetryKey(otaPackageType, STATE), OtaPackageUpdateStatus.INITIATED.name()));
telemetryService.saveAndNotify(tenantId, deviceId, Collections.singletonList(status), new FutureCallback<>() { telemetryService.saveTimeseries(TimeseriesSaveRequest.builder()
@Override .tenantId(tenantId)
public void onSuccess(@Nullable Void tmp) { .entityId(deviceId)
log.trace("[{}] Success save telemetry with target {} for device!", deviceId, otaPackage); .entry(status)
updateAttributes(device, otaPackage, ts, tenantId, deviceId, otaPackageType); .callback(new FutureCallback<>() {
} @Override
public void onSuccess(@Nullable Void tmp) {
log.trace("[{}] Success save telemetry with target {} for device!", deviceId, otaPackage);
updateAttributes(device, otaPackage, ts, tenantId, deviceId, otaPackageType);
}
@Override @Override
public void onFailure(Throwable t) { public void onFailure(Throwable t) {
log.error("[{}] Failed to save telemetry with target {} for device!", deviceId, otaPackage, t); log.error("[{}] Failed to save telemetry with target {} for device!", deviceId, otaPackage, t);
updateAttributes(device, otaPackage, ts, tenantId, deviceId, otaPackageType); updateAttributes(device, otaPackage, ts, tenantId, deviceId, otaPackageType);
} }
}); })
.build());
} }
private void updateAttributes(Device device, OtaPackageInfo otaPackage, long ts, TenantId tenantId, DeviceId deviceId, OtaPackageType otaPackageType) { private void updateAttributes(Device device, OtaPackageInfo otaPackage, long ts, TenantId tenantId, DeviceId deviceId, OtaPackageType otaPackageType) {
@ -336,17 +348,23 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService {
remove(device, otaPackageType, attrToRemove); remove(device, otaPackageType, attrToRemove);
telemetryService.saveAndNotify(tenantId, deviceId, AttributeScope.SHARED_SCOPE, attributes, new FutureCallback<>() { telemetryService.saveAttributes(AttributesSaveRequest.builder()
@Override .tenantId(tenantId)
public void onSuccess(@Nullable Void tmp) { .entityId(deviceId)
log.trace("[{}] Success save attributes with target firmware!", deviceId); .scope(AttributeScope.SHARED_SCOPE)
} .entries(attributes)
.callback(new FutureCallback<>() {
@Override
public void onSuccess(@Nullable Void tmp) {
log.trace("[{}] Success save attributes with target firmware!", deviceId);
}
@Override @Override
public void onFailure(Throwable t) { public void onFailure(Throwable t) {
log.error("[{}] Failed to save attributes with target firmware!", deviceId, t); log.error("[{}] Failed to save attributes with target firmware!", deviceId, t);
} }
}); })
.build());
} }
private void remove(Device device, OtaPackageType otaPackageType) { private void remove(Device device, OtaPackageType otaPackageType) {
@ -354,8 +372,12 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService {
} }
private void remove(Device device, OtaPackageType otaPackageType, List<String> attributesKeys) { private void remove(Device device, OtaPackageType otaPackageType, List<String> attributesKeys) {
telemetryService.deleteAndNotify(device.getTenantId(), device.getId(), AttributeScope.SHARED_SCOPE, attributesKeys, telemetryService.deleteAttributes(AttributesDeleteRequest.builder()
new FutureCallback<>() { .tenantId(device.getTenantId())
.entityId(device.getId())
.scope(AttributeScope.SHARED_SCOPE)
.keys(attributesKeys)
.callback(new FutureCallback<>() {
@Override @Override
public void onSuccess(@Nullable Void tmp) { public void onSuccess(@Nullable Void tmp) {
log.trace("[{}] Success remove target {} attributes!", device.getId(), otaPackageType); log.trace("[{}] Success remove target {} attributes!", device.getId(), otaPackageType);
@ -366,6 +388,8 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService {
public void onFailure(Throwable t) { public void onFailure(Throwable t) {
log.error("[{}] Failed to remove target {} attributes!", device.getId(), otaPackageType, t); log.error("[{}] Failed to remove target {} attributes!", device.getId(), otaPackageType, t);
} }
}); })
.build());
} }
} }

47
application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java

@ -38,6 +38,8 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.rule.engine.api.AttributesSaveRequest;
import org.thingsboard.rule.engine.api.TimeseriesSaveRequest;
import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageRecordKey;
import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.AttributeScope;
@ -51,6 +53,7 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UUIDBased; import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.BooleanDataEntry; import org.thingsboard.server.common.data.kv.BooleanDataEntry;
import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.KvEntry;
@ -865,24 +868,30 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService<Dev
} }
private void save(DeviceId deviceId, String key, long value) { private void save(DeviceId deviceId, String key, long value) {
if (persistToTelemetry) { save(deviceId, new LongDataEntry(key, value), getCurrentTimeMillis());
tsSubService.saveAndNotifyInternal(
TenantId.SYS_TENANT_ID, deviceId,
Collections.singletonList(new BasicTsKvEntry(getCurrentTimeMillis(), new LongDataEntry(key, value))),
telemetryTtl, new TelemetrySaveCallback<>(deviceId, key, value));
} else {
tsSubService.saveAttrAndNotify(TenantId.SYS_TENANT_ID, deviceId, AttributeScope.SERVER_SCOPE, key, value, new TelemetrySaveCallback<>(deviceId, key, value));
}
} }
private void save(DeviceId deviceId, String key, boolean value) { private void save(DeviceId deviceId, String key, boolean value) {
save(deviceId, new BooleanDataEntry(key, value), getCurrentTimeMillis());
}
private void save(DeviceId deviceId, KvEntry kvEntry, long ts) {
if (persistToTelemetry) { if (persistToTelemetry) {
tsSubService.saveAndNotifyInternal( tsSubService.saveTimeseriesInternal(TimeseriesSaveRequest.builder()
TenantId.SYS_TENANT_ID, deviceId, .tenantId(TenantId.SYS_TENANT_ID)
Collections.singletonList(new BasicTsKvEntry(getCurrentTimeMillis(), new BooleanDataEntry(key, value))), .entityId(deviceId)
telemetryTtl, new TelemetrySaveCallback<>(deviceId, key, value)); .entry(new BasicTsKvEntry(ts, kvEntry))
.ttl(telemetryTtl)
.callback(new TelemetrySaveCallback<>(deviceId, kvEntry))
.build());
} else { } else {
tsSubService.saveAttrAndNotify(TenantId.SYS_TENANT_ID, deviceId, AttributeScope.SERVER_SCOPE, key, value, new TelemetrySaveCallback<>(deviceId, key, value)); tsSubService.saveAttributes(AttributesSaveRequest.builder()
.tenantId(TenantId.SYS_TENANT_ID)
.entityId(deviceId)
.scope(AttributeScope.SERVER_SCOPE)
.entry(new BaseAttributeKvEntry(ts, kvEntry))
.callback(new TelemetrySaveCallback<>(deviceId, kvEntry))
.build());
} }
} }
@ -892,23 +901,21 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService<Dev
private static class TelemetrySaveCallback<T> implements FutureCallback<T> { private static class TelemetrySaveCallback<T> implements FutureCallback<T> {
private final DeviceId deviceId; private final DeviceId deviceId;
private final String key; private final KvEntry kvEntry;
private final Object value;
TelemetrySaveCallback(DeviceId deviceId, String key, Object value) { TelemetrySaveCallback(DeviceId deviceId, KvEntry kvEntry) {
this.deviceId = deviceId; this.deviceId = deviceId;
this.key = key; this.kvEntry = kvEntry;
this.value = value;
} }
@Override @Override
public void onSuccess(@Nullable T result) { public void onSuccess(@Nullable T result) {
log.trace("[{}] Successfully updated attribute [{}] with value [{}]", deviceId, key, value); log.trace("[{}] Successfully updated entry {}", deviceId, kvEntry);
} }
@Override @Override
public void onFailure(Throwable t) { public void onFailure(Throwable t) {
log.warn("[{}] Failed to update attribute [{}] with value [{}]", deviceId, key, value, t); log.warn("[{}] Failed to update entry {}", deviceId, kvEntry, t);
} }
} }
} }

22
application/src/main/java/org/thingsboard/server/service/stats/DefaultRuleEngineStatisticsService.java

@ -22,6 +22,7 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.rule.engine.api.TimeseriesSaveRequest;
import org.thingsboard.server.common.data.id.QueueStatsId; import org.thingsboard.server.common.data.id.QueueStatsId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
@ -37,7 +38,6 @@ import org.thingsboard.server.queue.util.TbRuleEngineComponent;
import org.thingsboard.server.service.queue.TbRuleEngineConsumerStats; import org.thingsboard.server.service.queue.TbRuleEngineConsumerStats;
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
@ -53,9 +53,9 @@ import java.util.stream.Collectors;
public class DefaultRuleEngineStatisticsService implements RuleEngineStatisticsService { public class DefaultRuleEngineStatisticsService implements RuleEngineStatisticsService {
public static final String RULE_ENGINE_EXCEPTION = "ruleEngineException"; public static final String RULE_ENGINE_EXCEPTION = "ruleEngineException";
public static final FutureCallback<Integer> CALLBACK = new FutureCallback<Integer>() { public static final FutureCallback<Void> CALLBACK = new FutureCallback<Void>() {
@Override @Override
public void onSuccess(@Nullable Integer result) { public void onSuccess(@Nullable Void result) {
} }
@ -89,7 +89,13 @@ public class DefaultRuleEngineStatisticsService implements RuleEngineStatisticsS
if (!tsList.isEmpty()) { if (!tsList.isEmpty()) {
long ttl = apiLimitService.getLimit(tenantId, DefaultTenantProfileConfiguration::getQueueStatsTtlDays); long ttl = apiLimitService.getLimit(tenantId, DefaultTenantProfileConfiguration::getQueueStatsTtlDays);
ttl = TimeUnit.DAYS.toSeconds(ttl); ttl = TimeUnit.DAYS.toSeconds(ttl);
tsService.saveAndNotifyInternal(tenantId, queueStatsId, tsList, ttl, CALLBACK); tsService.saveTimeseriesInternal(TimeseriesSaveRequest.builder()
.tenantId(tenantId)
.entityId(queueStatsId)
.entries(tsList)
.ttl(ttl)
.callback(CALLBACK)
.build());
} }
} }
} catch (Exception e) { } catch (Exception e) {
@ -103,7 +109,13 @@ public class DefaultRuleEngineStatisticsService implements RuleEngineStatisticsS
TsKvEntry tsKv = new BasicTsKvEntry(e.getTs(), new JsonDataEntry(RULE_ENGINE_EXCEPTION, e.toJsonString(maxErrorMessageLength))); TsKvEntry tsKv = new BasicTsKvEntry(e.getTs(), new JsonDataEntry(RULE_ENGINE_EXCEPTION, e.toJsonString(maxErrorMessageLength)));
long ttl = apiLimitService.getLimit(tenantId, DefaultTenantProfileConfiguration::getRuleEngineExceptionsTtlDays); long ttl = apiLimitService.getLimit(tenantId, DefaultTenantProfileConfiguration::getRuleEngineExceptionsTtlDays);
ttl = TimeUnit.DAYS.toSeconds(ttl); ttl = TimeUnit.DAYS.toSeconds(ttl);
tsService.saveAndNotifyInternal(tenantId, getQueueStatsId(tenantId, queueName), Collections.singletonList(tsKv), ttl, CALLBACK); tsService.saveTimeseriesInternal(TimeseriesSaveRequest.builder()
.tenantId(tenantId)
.entityId(getQueueStatsId(tenantId, queueName))
.entry(tsKv)
.ttl(ttl)
.callback(CALLBACK)
.build());
} catch (Exception e2) { } catch (Exception e2) {
if (!"Asset is referencing to non-existent tenant!".equalsIgnoreCase(e2.getMessage())) { if (!"Asset is referencing to non-existent tenant!".equalsIgnoreCase(e2.getMessage())) {
log.debug("[{}] Failed to store the statistics", tenantId, e2); log.debug("[{}] Failed to store the statistics", tenantId, e2);

75
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java

@ -32,6 +32,8 @@ import org.springframework.security.core.context.SecurityContextHolder;
import org.thingsboard.common.util.DonAsynchron; import org.thingsboard.common.util.DonAsynchron;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.rule.engine.api.AttributesSaveRequest;
import org.thingsboard.rule.engine.api.TimeseriesSaveRequest;
import org.thingsboard.server.common.adaptor.JsonConverter; import org.thingsboard.server.common.adaptor.JsonConverter;
import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
@ -206,20 +208,27 @@ public abstract class AbstractBulkImportService<E extends HasId<? extends Entity
accessValidator.validateEntityAndCallback(user, Operation.WRITE_TELEMETRY, entity.getId(), (result, tenantId, entityId) -> { accessValidator.validateEntityAndCallback(user, Operation.WRITE_TELEMETRY, entity.getId(), (result, tenantId, entityId) -> {
TenantProfile tenantProfile = tenantProfileCache.get(tenantId); TenantProfile tenantProfile = tenantProfileCache.get(tenantId);
long tenantTtl = TimeUnit.DAYS.toSeconds(((DefaultTenantProfileConfiguration) tenantProfile.getProfileData().getConfiguration()).getDefaultStorageTtlDays()); long tenantTtl = TimeUnit.DAYS.toSeconds(((DefaultTenantProfileConfiguration) tenantProfile.getProfileData().getConfiguration()).getDefaultStorageTtlDays());
tsSubscriptionService.saveAndNotify(tenantId, user.getCustomerId(), entityId, timeseries, tenantTtl, new FutureCallback<Void>() { tsSubscriptionService.saveTimeseries(TimeseriesSaveRequest.builder()
@Override .tenantId(tenantId)
public void onSuccess(@Nullable Void tmp) { .customerId(user.getCustomerId())
entityActionService.logEntityAction(user, (UUIDBased & EntityId) entityId, null, null, .entityId(entityId)
ActionType.TIMESERIES_UPDATED, null, timeseries); .entries(timeseries)
} .ttl(tenantTtl)
.callback(new FutureCallback<>() {
@Override @Override
public void onFailure(Throwable t) { public void onSuccess(@Nullable Void tmp) {
entityActionService.logEntityAction(user, (UUIDBased & EntityId) entityId, null, null, entityActionService.logEntityAction(user, (UUIDBased & EntityId) entityId, null, null,
ActionType.TIMESERIES_UPDATED, BaseController.toException(t), timeseries); ActionType.TIMESERIES_UPDATED, null, timeseries);
throw new RuntimeException(t); }
}
}); @Override
public void onFailure(Throwable t) {
entityActionService.logEntityAction(user, (UUIDBased & EntityId) entityId, null, null,
ActionType.TIMESERIES_UPDATED, BaseController.toException(t), timeseries);
throw new RuntimeException(t);
}
})
.build());
}); });
} }
@ -229,23 +238,27 @@ public abstract class AbstractBulkImportService<E extends HasId<? extends Entity
List<AttributeKvEntry> attributes = new ArrayList<>(JsonConverter.convertToAttributes(kvsEntry.getValue())); List<AttributeKvEntry> attributes = new ArrayList<>(JsonConverter.convertToAttributes(kvsEntry.getValue()));
accessValidator.validateEntityAndCallback(user, Operation.WRITE_ATTRIBUTES, entity.getId(), (result, tenantId, entityId) -> { accessValidator.validateEntityAndCallback(user, Operation.WRITE_ATTRIBUTES, entity.getId(), (result, tenantId, entityId) -> {
tsSubscriptionService.saveAndNotify(tenantId, entityId, AttributeScope.valueOf(scope), attributes, new FutureCallback<>() { tsSubscriptionService.saveAttributes(AttributesSaveRequest.builder()
.tenantId(tenantId)
@Override .entityId(entityId)
public void onSuccess(Void unused) { .scope(AttributeScope.valueOf(scope))
entityActionService.logEntityAction(user, (UUIDBased & EntityId) entityId, null, .entries(attributes)
null, ActionType.ATTRIBUTES_UPDATED, null, AttributeScope.valueOf(scope), attributes); .callback(new FutureCallback<>() {
} @Override
public void onSuccess(Void unused) {
@Override entityActionService.logEntityAction(user, (UUIDBased & EntityId) entityId, null,
public void onFailure(Throwable throwable) { null, ActionType.ATTRIBUTES_UPDATED, null, AttributeScope.valueOf(scope), attributes);
entityActionService.logEntityAction(user, (UUIDBased & EntityId) entityId, null, }
null, ActionType.ATTRIBUTES_UPDATED, BaseController.toException(throwable),
AttributeScope.valueOf(scope), attributes); @Override
throw new RuntimeException(throwable); public void onFailure(Throwable throwable) {
} entityActionService.logEntityAction(user, (UUIDBased & EntityId) entityId, null,
null, ActionType.ATTRIBUTES_UPDATED, BaseController.toException(throwable),
}); AttributeScope.valueOf(scope), attributes);
throw new RuntimeException(throwable);
}
})
.build());
}); });
} }

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

@ -24,6 +24,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.AttributesSaveRequest;
import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.ExportableEntity; import org.thingsboard.server.common.data.ExportableEntity;
@ -257,16 +258,22 @@ public abstract class BaseEntityImportService<I extends EntityId, E extends Expo
}) })
.collect(Collectors.toList()); .collect(Collectors.toList());
// fixme: attributes are saved outside the transaction // fixme: attributes are saved outside the transaction
tsSubService.saveAndNotify(user.getTenantId(), entity.getId(), scope, attributeKvEntries, new FutureCallback<Void>() { tsSubService.saveAttributes(AttributesSaveRequest.builder()
@Override .tenantId(user.getTenantId())
public void onSuccess(@Nullable Void unused) { .entityId(entity.getId())
} .scope(scope)
.entries(attributeKvEntries)
.callback(new FutureCallback<>() {
@Override
public void onSuccess(@Nullable Void unused) {
}
@Override @Override
public void onFailure(Throwable thr) { public void onFailure(Throwable thr) {
log.error("Failed to import attributes for {} {}", entity.getId().getEntityType(), entity.getId(), thr); log.error("Failed to import attributes for {} {}", entity.getId().getEntityType(), entity.getId(), thr);
} }
}); })
.build());
}); });
}); });
} }

13
application/src/main/java/org/thingsboard/server/service/system/DefaultSystemInfoService.java

@ -27,6 +27,7 @@ import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.rule.engine.api.SmsService; import org.thingsboard.rule.engine.api.SmsService;
import org.thingsboard.rule.engine.api.TimeseriesSaveRequest;
import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.ApiUsageState;
import org.thingsboard.server.common.data.FeaturesInfo; import org.thingsboard.server.common.data.FeaturesInfo;
@ -71,9 +72,9 @@ import static org.thingsboard.common.util.SystemUtil.getTotalMemory;
@Slf4j @Slf4j
public class DefaultSystemInfoService extends TbApplicationEventListener<PartitionChangeEvent> implements SystemInfoService { public class DefaultSystemInfoService extends TbApplicationEventListener<PartitionChangeEvent> implements SystemInfoService {
public static final FutureCallback<Integer> CALLBACK = new FutureCallback<>() { public static final FutureCallback<Void> CALLBACK = new FutureCallback<>() {
@Override @Override
public void onSuccess(@Nullable Integer result) { public void onSuccess(@Nullable Void result) {
} }
@Override @Override
@ -200,7 +201,13 @@ public class DefaultSystemInfoService extends TbApplicationEventListener<Partiti
private void doSave(List<TsKvEntry> telemetry) { private void doSave(List<TsKvEntry> telemetry) {
ApiUsageState apiUsageState = apiUsageStateClient.getApiUsageState(TenantId.SYS_TENANT_ID); ApiUsageState apiUsageState = apiUsageStateClient.getApiUsageState(TenantId.SYS_TENANT_ID);
telemetryService.saveAndNotifyInternal(TenantId.SYS_TENANT_ID, apiUsageState.getId(), telemetry, systemInfoTtlSeconds, CALLBACK); telemetryService.saveTimeseriesInternal(TimeseriesSaveRequest.builder()
.tenantId(TenantId.SYS_TENANT_ID)
.entityId(apiUsageState.getId())
.entries(telemetry)
.ttl(systemInfoTtlSeconds)
.callback(CALLBACK)
.build());
} }
private List<SystemInfoData> getSystemData(ServiceInfo serviceInfo) { private List<SystemInfoData> getSystemData(ServiceInfo serviceInfo) {

446
application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java

@ -19,29 +19,28 @@ import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.SettableFuture;
import jakarta.annotation.Nullable; import jakarta.annotation.Nullable;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy; import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.DonAsynchron;
import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.rule.engine.api.AttributesDeleteRequest;
import org.thingsboard.rule.engine.api.AttributesSaveRequest;
import org.thingsboard.rule.engine.api.RuleEngineTelemetryService;
import org.thingsboard.rule.engine.api.TimeseriesDeleteRequest;
import org.thingsboard.rule.engine.api.TimeseriesSaveRequest;
import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageRecordKey;
import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.BooleanDataEntry;
import org.thingsboard.server.common.data.kv.DeleteTsKvQuery;
import org.thingsboard.server.common.data.kv.DoubleDataEntry;
import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.kv.TsKvLatestRemovingResult; import org.thingsboard.server.common.data.kv.TsKvLatestRemovingResult;
import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TbCallback;
@ -54,8 +53,6 @@ import org.thingsboard.server.service.entitiy.entityview.TbEntityViewService;
import org.thingsboard.server.service.subscription.TbSubscriptionUtils; import org.thingsboard.server.service.subscription.TbSubscriptionUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -64,13 +61,14 @@ import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.function.Consumer;
/** /**
* Created by ashvayka on 27.03.18. * Created by ashvayka on 27.03.18.
*/ */
@Service @Service
@Slf4j @Slf4j
public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionService implements TelemetrySubscriptionService { public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionService implements TelemetrySubscriptionService, RuleEngineTelemetryService {
private final AttributesService attrService; private final AttributesService attrService;
private final TimeseriesService tsService; private final TimeseriesService tsService;
@ -115,76 +113,92 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
} }
@Override @Override
public ListenableFuture<Void> saveAndNotify(TenantId tenantId, EntityId entityId, TsKvEntry ts) { public void saveTimeseries(TimeseriesSaveRequest request) {
SettableFuture<Void> future = SettableFuture.create(); TenantId tenantId = request.getTenantId();
saveAndNotify(tenantId, entityId, Collections.singletonList(ts), new VoidFutureCallback(future)); EntityId entityId = request.getEntityId();
return future; checkInternalEntity(entityId);
boolean sysTenant = TenantId.SYS_TENANT_ID.equals(tenantId) || tenantId == null;
if (sysTenant || request.isOnlyLatest() || apiUsageStateService.getApiUsageState(tenantId).isDbStorageEnabled()) {
KvUtils.validate(request.getEntries(), valueNoXssValidation);
ListenableFuture<Integer> future = saveTimeseriesInternal(request);
if (!request.isOnlyLatest()) {
FutureCallback<Integer> callback = getApiUsageCallback(tenantId, request.getCustomerId(), sysTenant, request.getCallback());
Futures.addCallback(future, callback, tsCallBackExecutor);
}
} else {
request.getCallback().onFailure(new RuntimeException("DB storage writes are disabled due to API limits!"));
}
} }
@Override @Override
public void saveAndNotify(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts, FutureCallback<Void> callback) { public ListenableFuture<Integer> saveTimeseriesInternal(TimeseriesSaveRequest request) {
saveAndNotify(tenantId, null, entityId, ts, 0L, callback); TenantId tenantId = request.getTenantId();
} EntityId entityId = request.getEntityId();
ListenableFuture<Integer> saveFuture;
if (request.isOnlyLatest()) {
saveFuture = Futures.transform(tsService.saveLatest(tenantId, entityId, request.getEntries()), result -> 0, MoreExecutors.directExecutor());
} else if (request.isSaveLatest()) {
saveFuture = tsService.save(tenantId, entityId, request.getEntries(), request.getTtl());
} else {
saveFuture = tsService.saveWithoutLatest(tenantId, entityId, request.getEntries(), request.getTtl());
}
@Override addMainCallback(saveFuture, request.getCallback());
public void saveAndNotify(TenantId tenantId, CustomerId customerId, EntityId entityId, List<TsKvEntry> ts, long ttl, FutureCallback<Void> callback) { addWsCallback(saveFuture, success -> onTimeSeriesUpdate(tenantId, entityId, request.getEntries()));
doSaveAndNotify(tenantId, customerId, entityId, ts, ttl, callback, true); if (request.isSaveLatest() && !request.isOnlyLatest()) {
addEntityViewCallback(tenantId, entityId, request.getEntries());
}
return saveFuture;
} }
@Override @Override
public void saveWithoutLatestAndNotify(TenantId tenantId, CustomerId customerId, EntityId entityId, List<TsKvEntry> ts, long ttl, FutureCallback<Void> callback) { public void saveAttributes(AttributesSaveRequest request) {
doSaveAndNotify(tenantId, customerId, entityId, ts, ttl, callback, false); checkInternalEntity(request.getEntityId());
saveAttributesInternal(request);
} }
private void doSaveAndNotify(TenantId tenantId, CustomerId customerId, EntityId entityId, List<TsKvEntry> ts, long ttl, FutureCallback<Void> callback, boolean saveLatest) { @Override
checkInternalEntity(entityId); public void saveAttributesInternal(AttributesSaveRequest request) {
boolean sysTenant = TenantId.SYS_TENANT_ID.equals(tenantId) || tenantId == null; log.trace("Executing saveInternal [{}]", request);
if (sysTenant || apiUsageStateService.getApiUsageState(tenantId).isDbStorageEnabled()) { ListenableFuture<List<Long>> saveFuture = attrService.save(request.getTenantId(), request.getEntityId(), request.getScope(), request.getEntries());
KvUtils.validate(ts, valueNoXssValidation); addMainCallback(saveFuture, request.getCallback());
if (saveLatest) { addWsCallback(saveFuture, success -> onAttributesUpdate(request.getTenantId(), request.getEntityId(), request.getScope().name(), request.getEntries(), request.isNotifyDevice()));
saveAndNotifyInternal(tenantId, entityId, ts, ttl, getCallback(tenantId, customerId, sysTenant, callback));
} else {
saveWithoutLatestAndNotifyInternal(tenantId, entityId, ts, ttl, getCallback(tenantId, customerId, sysTenant, callback));
}
} else {
callback.onFailure(new RuntimeException("DB storage writes are disabled due to API limits!"));
}
} }
private FutureCallback<Integer> getCallback(TenantId tenantId, CustomerId customerId, boolean sysTenant, FutureCallback<Void> callback) { @Override
return new FutureCallback<>() { public void deleteAttributes(AttributesDeleteRequest request) {
@Override checkInternalEntity(request.getEntityId());
public void onSuccess(Integer result) { deleteAttributesInternal(request);
if (!sysTenant && result != null && result > 0) {
apiUsageClient.report(tenantId, customerId, ApiUsageRecordKey.STORAGE_DP_COUNT, result);
}
callback.onSuccess(null);
}
@Override
public void onFailure(Throwable t) {
callback.onFailure(t);
}
};
} }
@Override @Override
public void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts, FutureCallback<Integer> callback) { public void deleteAttributesInternal(AttributesDeleteRequest request) {
saveAndNotifyInternal(tenantId, entityId, ts, 0L, callback); ListenableFuture<List<String>> deleteFuture = attrService.removeAll(request.getTenantId(), request.getEntityId(), request.getScope(), request.getKeys());
addMainCallback(deleteFuture, request.getCallback());
addWsCallback(deleteFuture, success -> onAttributesDelete(request.getTenantId(), request.getEntityId(), request.getScope().name(), request.getKeys(), request.isNotifyDevice()));
} }
@Override @Override
public void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts, long ttl, FutureCallback<Integer> callback) { public void deleteTimeseries(TimeseriesDeleteRequest request) {
ListenableFuture<Integer> saveFuture = tsService.save(tenantId, entityId, ts, ttl); checkInternalEntity(request.getEntityId());
addMainCallback(saveFuture, callback); deleteTimeseriesInternal(request);
addWsCallback(saveFuture, success -> onTimeSeriesUpdate(tenantId, entityId, ts));
addEntityViewCallback(tenantId, entityId, ts);
} }
private void saveWithoutLatestAndNotifyInternal(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts, long ttl, FutureCallback<Integer> callback) { @Override
ListenableFuture<Integer> saveFuture = tsService.saveWithoutLatest(tenantId, entityId, ts, ttl); public void deleteTimeseriesInternal(TimeseriesDeleteRequest request) {
addMainCallback(saveFuture, callback); if (CollectionUtils.isNotEmpty(request.getKeys())) {
addWsCallback(saveFuture, success -> onTimeSeriesUpdate(tenantId, entityId, ts)); ListenableFuture<List<TsKvLatestRemovingResult>> deleteFuture;
if (request.getDeleteHistoryQueries() == null) {
deleteFuture = tsService.removeLatest(request.getTenantId(), request.getEntityId(), request.getKeys());
} else {
deleteFuture = tsService.remove(request.getTenantId(), request.getEntityId(), request.getDeleteHistoryQueries());
addWsCallback(deleteFuture, result -> onTimeSeriesDelete(request.getTenantId(), request.getEntityId(), request.getKeys(), result));
}
addMainCallback(deleteFuture, __ -> request.getCallback().onSuccess(request.getKeys()), request.getCallback()::onFailure);
} else {
ListenableFuture<List<String>> deleteFuture = tsService.removeAllLatest(request.getTenantId(), request.getEntityId());
addMainCallback(deleteFuture, request.getCallback()::onSuccess, request.getCallback()::onFailure);
}
} }
private void addEntityViewCallback(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts) { private void addEntityViewCallback(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts) {
@ -214,15 +228,21 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
} }
} }
if (!entityViewLatest.isEmpty()) { if (!entityViewLatest.isEmpty()) {
saveLatestAndNotify(tenantId, entityView.getId(), entityViewLatest, new FutureCallback<>() { saveTimeseries(TimeseriesSaveRequest.builder()
@Override .tenantId(tenantId)
public void onSuccess(@Nullable Void tmp) { .entityId(entityView.getId())
} .entries(entityViewLatest)
.onlyLatest(true)
@Override .callback(new FutureCallback<>() {
public void onFailure(Throwable t) { @Override
} public void onSuccess(@Nullable Void tmp) {}
});
@Override
public void onFailure(Throwable t) {
log.error("[{}][{}] Failed to save entity view latest timeseries: {}", tenantId, entityView.getId(), entityViewLatest, t);
}
})
.build());
} }
} }
} }
@ -236,233 +256,6 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
} }
} }
@Override
public void saveAndNotify(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, FutureCallback<Void> callback) {
saveAndNotify(tenantId, entityId, scope, attributes, true, callback);
}
@Override
public void saveAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, List<AttributeKvEntry> attributes, FutureCallback<Void> callback) {
saveAndNotify(tenantId, entityId, scope, attributes, true, callback);
}
@Override
public void saveAndNotify(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, boolean notifyDevice, FutureCallback<Void> callback) {
checkInternalEntity(entityId);
saveAndNotifyInternal(tenantId, entityId, scope, attributes, notifyDevice, callback);
}
@Override
public void saveAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, List<AttributeKvEntry> attributes, boolean notifyDevice, FutureCallback<Void> callback) {
checkInternalEntity(entityId);
saveAndNotifyInternal(tenantId, entityId, scope, attributes, notifyDevice, callback);
}
@Override
public void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, boolean notifyDevice, FutureCallback<Void> callback) {
ListenableFuture<List<Long>> saveFuture = attrService.save(tenantId, entityId, scope, attributes);
addVoidCallback(saveFuture, callback);
addWsCallback(saveFuture, success -> onAttributesUpdate(tenantId, entityId, scope, attributes, notifyDevice));
}
@Override
public void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, AttributeScope scope, List<AttributeKvEntry> attributes, boolean notifyDevice, FutureCallback<Void> callback) {
ListenableFuture<List<Long>> saveFuture = attrService.save(tenantId, entityId, scope, attributes);
addVoidCallback(saveFuture, callback);
addWsCallback(saveFuture, success -> onAttributesUpdate(tenantId, entityId, scope.name(), attributes, notifyDevice));
}
@Override
public void saveLatestAndNotify(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts, FutureCallback<Void> callback) {
checkInternalEntity(entityId);
saveLatestAndNotifyInternal(tenantId, entityId, ts, callback);
}
@Override
public void saveLatestAndNotifyInternal(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts, FutureCallback<Void> callback) {
ListenableFuture<List<Long>> saveFuture = tsService.saveLatest(tenantId, entityId, ts);
addVoidCallback(saveFuture, callback);
addWsCallback(saveFuture, success -> onTimeSeriesUpdate(tenantId, entityId, ts));
}
@Override
public void deleteAndNotify(TenantId tenantId, EntityId entityId, String scope, List<String> keys, FutureCallback<Void> callback) {
checkInternalEntity(entityId);
deleteAndNotifyInternal(tenantId, entityId, scope, keys, false, callback);
}
@Override
public void deleteAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, List<String> keys, FutureCallback<Void> callback) {
checkInternalEntity(entityId);
deleteAndNotifyInternal(tenantId, entityId, scope, keys, false, callback);
}
@Override
public void deleteAndNotify(TenantId tenantId, EntityId entityId, String scope, List<String> keys, boolean notifyDevice, FutureCallback<Void> callback) {
checkInternalEntity(entityId);
deleteAndNotifyInternal(tenantId, entityId, scope, keys, notifyDevice, callback);
}
@Override
public void deleteAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, List<String> keys, boolean notifyDevice, FutureCallback<Void> callback) {
checkInternalEntity(entityId);
deleteAndNotifyInternal(tenantId, entityId, scope, keys, notifyDevice, callback);
}
@Override
public void deleteAndNotifyInternal(TenantId tenantId, EntityId entityId, String scope, List<String> keys, boolean notifyDevice, FutureCallback<Void> callback) {
ListenableFuture<List<String>> deleteFuture = attrService.removeAll(tenantId, entityId, scope, keys);
addVoidCallback(deleteFuture, callback);
addWsCallback(deleteFuture, success -> onAttributesDelete(tenantId, entityId, scope, keys, notifyDevice));
}
@Override
public void deleteAndNotifyInternal(TenantId tenantId, EntityId entityId, AttributeScope scope, List<String> keys, boolean notifyDevice, FutureCallback<Void> callback) {
ListenableFuture<List<String>> deleteFuture = attrService.removeAll(tenantId, entityId, scope, keys);
addVoidCallback(deleteFuture, callback);
addWsCallback(deleteFuture, success -> onAttributesDelete(tenantId, entityId, scope.name(), keys, notifyDevice));
}
@Override
public void deleteLatest(TenantId tenantId, EntityId entityId, List<String> keys, FutureCallback<Void> callback) {
checkInternalEntity(entityId);
deleteLatestInternal(tenantId, entityId, keys, callback);
}
@Override
public void deleteLatestInternal(TenantId tenantId, EntityId entityId, List<String> keys, FutureCallback<Void> callback) {
ListenableFuture<List<TsKvLatestRemovingResult>> deleteFuture = tsService.removeLatest(tenantId, entityId, keys);
addVoidCallback(deleteFuture, callback);
}
@Override
public void deleteAllLatest(TenantId tenantId, EntityId entityId, FutureCallback<Collection<String>> callback) {
ListenableFuture<Collection<String>> deleteFuture = tsService.removeAllLatest(tenantId, entityId);
Futures.addCallback(deleteFuture, new FutureCallback<Collection<String>>() {
@Override
public void onSuccess(@Nullable Collection<String> result) {
callback.onSuccess(result);
}
@Override
public void onFailure(Throwable t) {
callback.onFailure(t);
}
}, tsCallBackExecutor);
}
@Override
public void deleteTimeseriesAndNotify(TenantId tenantId, EntityId entityId, List<String> keys, List<DeleteTsKvQuery> deleteTsKvQueries, FutureCallback<Void> callback) {
ListenableFuture<List<TsKvLatestRemovingResult>> deleteFuture = tsService.remove(tenantId, entityId, deleteTsKvQueries);
addVoidCallback(deleteFuture, callback);
addWsCallback(deleteFuture, list -> onTimeSeriesDelete(tenantId, entityId, keys, list));
}
@Override
public void saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, long value, FutureCallback<Void> callback) {
saveAndNotify(tenantId, entityId, scope, Collections.singletonList(new BaseAttributeKvEntry(new LongDataEntry(key, value)
, System.currentTimeMillis())), callback);
}
@Override
public void saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, long value, FutureCallback<Void> callback) {
saveAndNotify(tenantId, entityId, scope, Collections.singletonList(new BaseAttributeKvEntry(new LongDataEntry(key, value)
, System.currentTimeMillis())), callback);
}
@Override
public void saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, String value, FutureCallback<Void> callback) {
saveAndNotify(tenantId, entityId, scope, Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry(key, value)
, System.currentTimeMillis())), callback);
}
@Override
public void saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, String value, FutureCallback<Void> callback) {
saveAndNotify(tenantId, entityId, scope, Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry(key, value)
, System.currentTimeMillis())), callback);
}
@Override
public void saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, double value, FutureCallback<Void> callback) {
saveAndNotify(tenantId, entityId, scope, Collections.singletonList(new BaseAttributeKvEntry(new DoubleDataEntry(key, value)
, System.currentTimeMillis())), callback);
}
@Override
public void saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, double value, FutureCallback<Void> callback) {
saveAndNotify(tenantId, entityId, scope, Collections.singletonList(new BaseAttributeKvEntry(new DoubleDataEntry(key, value)
, System.currentTimeMillis())), callback);
}
@Override
public void saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, boolean value, FutureCallback<Void> callback) {
saveAndNotify(tenantId, entityId, scope, Collections.singletonList(new BaseAttributeKvEntry(new BooleanDataEntry(key, value)
, System.currentTimeMillis())), callback);
}
@Override
public void saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, boolean value, FutureCallback<Void> callback) {
saveAndNotify(tenantId, entityId, scope, Collections.singletonList(new BaseAttributeKvEntry(new BooleanDataEntry(key, value)
, System.currentTimeMillis())), callback);
}
@Override
public ListenableFuture<Void> saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, long value) {
SettableFuture<Void> future = SettableFuture.create();
saveAttrAndNotify(tenantId, entityId, scope, key, value, new VoidFutureCallback(future));
return future;
}
@Override
public ListenableFuture<Void> saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, long value) {
SettableFuture<Void> future = SettableFuture.create();
saveAttrAndNotify(tenantId, entityId, scope, key, value, new VoidFutureCallback(future));
return future;
}
@Override
public ListenableFuture<Void> saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, String value) {
SettableFuture<Void> future = SettableFuture.create();
saveAttrAndNotify(tenantId, entityId, scope, key, value, new VoidFutureCallback(future));
return future;
}
@Override
public ListenableFuture<Void> saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, String value) {
SettableFuture<Void> future = SettableFuture.create();
saveAttrAndNotify(tenantId, entityId, scope, key, value, new VoidFutureCallback(future));
return future;
}
@Override
public ListenableFuture<Void> saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, double value) {
SettableFuture<Void> future = SettableFuture.create();
saveAttrAndNotify(tenantId, entityId, scope, key, value, new VoidFutureCallback(future));
return future;
}
@Override
public ListenableFuture<Void> saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, double value) {
SettableFuture<Void> future = SettableFuture.create();
saveAttrAndNotify(tenantId, entityId, scope, key, value, new VoidFutureCallback(future));
return future;
}
@Override
public ListenableFuture<Void> saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, boolean value) {
SettableFuture<Void> future = SettableFuture.create();
saveAttrAndNotify(tenantId, entityId, scope, key, value, new VoidFutureCallback(future));
return future;
}
@Override
public ListenableFuture<Void> saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, boolean value) {
SettableFuture<Void> future = SettableFuture.create();
saveAttrAndNotify(tenantId, entityId, scope, key, value, new VoidFutureCallback(future));
return future;
}
private void onAttributesUpdate(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, boolean notifyDevice) { private void onAttributesUpdate(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, boolean notifyDevice) {
forwardToSubscriptionManagerService(tenantId, entityId, subscriptionManagerService -> { forwardToSubscriptionManagerService(tenantId, entityId, subscriptionManagerService -> {
subscriptionManagerService.onAttributesUpdate(tenantId, entityId, scope, attributes, notifyDevice, TbCallback.EMPTY); subscriptionManagerService.onAttributesUpdate(tenantId, entityId, scope, attributes, notifyDevice, TbCallback.EMPTY);
@ -509,33 +302,13 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
}); });
} }
private <S> void addVoidCallback(ListenableFuture<S> saveFuture, final FutureCallback<Void> callback) { private <S> void addMainCallback(ListenableFuture<S> saveFuture, final FutureCallback<Void> callback) {
if (callback == null) return; if (callback == null) return;
Futures.addCallback(saveFuture, new FutureCallback<S>() { addMainCallback(saveFuture, result -> callback.onSuccess(null), callback::onFailure);
@Override
public void onSuccess(@Nullable S result) {
callback.onSuccess(null);
}
@Override
public void onFailure(Throwable t) {
callback.onFailure(t);
}
}, tsCallBackExecutor);
} }
private <S> void addMainCallback(ListenableFuture<S> saveFuture, final FutureCallback<S> callback) { private <S> void addMainCallback(ListenableFuture<S> saveFuture, Consumer<S> onSuccess, Consumer<Throwable> onFailure) {
Futures.addCallback(saveFuture, new FutureCallback<S>() { DonAsynchron.withCallback(saveFuture, onSuccess, onFailure, tsCallBackExecutor);
@Override
public void onSuccess(@Nullable S result) {
callback.onSuccess(result);
}
@Override
public void onFailure(Throwable t) {
callback.onFailure(t);
}
}, tsCallBackExecutor);
} }
private void checkInternalEntity(EntityId entityId) { private void checkInternalEntity(EntityId entityId) {
@ -544,22 +317,21 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
} }
} }
private static class VoidFutureCallback implements FutureCallback<Void> { private FutureCallback<Integer> getApiUsageCallback(TenantId tenantId, CustomerId customerId, boolean sysTenant, FutureCallback<Void> callback) {
private final SettableFuture<Void> future; return new FutureCallback<>() {
@Override
public VoidFutureCallback(SettableFuture<Void> future) { public void onSuccess(Integer result) {
this.future = future; if (!sysTenant && result != null && result > 0) {
} apiUsageClient.report(tenantId, customerId, ApiUsageRecordKey.STORAGE_DP_COUNT, result);
}
@Override callback.onSuccess(null);
public void onSuccess(Void result) { }
future.set(null);
}
@Override @Override
public void onFailure(Throwable t) { public void onFailure(Throwable t) {
future.setException(t); callback.onFailure(t);
} }
};
} }
} }

31
application/src/main/java/org/thingsboard/server/service/telemetry/InternalTelemetryService.java

@ -15,37 +15,24 @@
*/ */
package org.thingsboard.server.service.telemetry; package org.thingsboard.server.service.telemetry;
import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.rule.engine.api.AttributesDeleteRequest;
import org.thingsboard.rule.engine.api.AttributesSaveRequest;
import org.thingsboard.rule.engine.api.RuleEngineTelemetryService; import org.thingsboard.rule.engine.api.RuleEngineTelemetryService;
import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.rule.engine.api.TimeseriesDeleteRequest;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.rule.engine.api.TimeseriesSaveRequest;
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 java.util.List;
/** /**
* Created by ashvayka on 27.03.18. * Created by ashvayka on 27.03.18.
*/ */
public interface InternalTelemetryService extends RuleEngineTelemetryService { public interface InternalTelemetryService extends RuleEngineTelemetryService {
void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts, FutureCallback<Integer> callback); ListenableFuture<Integer> saveTimeseriesInternal(TimeseriesSaveRequest request);
void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts, long ttl, FutureCallback<Integer> callback);
@Deprecated(since = "3.7.0")
void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, boolean notifyDevice, FutureCallback<Void> callback);
void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, AttributeScope scope, List<AttributeKvEntry> attributes, boolean notifyDevice, FutureCallback<Void> callback);
void saveLatestAndNotifyInternal(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts, FutureCallback<Void> callback);
@Deprecated(since = "3.7.0") void saveAttributesInternal(AttributesSaveRequest request);
void deleteAndNotifyInternal(TenantId tenantId, EntityId entityId, String scope, List<String> keys, boolean notifyDevice, FutureCallback<Void> callback);
void deleteAndNotifyInternal(TenantId tenantId, EntityId entityId, AttributeScope scope, List<String> keys, boolean notifyDevice, FutureCallback<Void> callback); void deleteTimeseriesInternal(TimeseriesDeleteRequest request);
void deleteLatestInternal(TenantId tenantId, EntityId entityId, List<String> keys, FutureCallback<Void> callback); void deleteAttributesInternal(AttributesDeleteRequest request);
} }

66
application/src/test/java/org/thingsboard/server/controller/WebsocketApiTest.java

@ -29,6 +29,8 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.TestPropertySource;
import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils; import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.AttributesSaveRequest;
import org.thingsboard.rule.engine.api.TimeseriesSaveRequest;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmSeverity;
@ -804,19 +806,24 @@ public class WebsocketApiTest extends AbstractControllerTest {
private void sendTelemetry(Device device, List<TsKvEntry> tsData) throws InterruptedException { private void sendTelemetry(Device device, List<TsKvEntry> tsData) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1); CountDownLatch latch = new CountDownLatch(1);
tsService.saveAndNotify(device.getTenantId(), null, device.getId(), tsData, 0, new FutureCallback<Void>() { tsService.saveTimeseries(TimeseriesSaveRequest.builder()
@Override .tenantId(device.getTenantId())
public void onSuccess(@Nullable Void result) { .entityId(device.getId())
log.debug("sendTelemetry callback onSuccess"); .entries(tsData)
latch.countDown(); .callback(new FutureCallback<Void>() {
} @Override
public void onSuccess(@Nullable Void result) {
@Override log.debug("sendTelemetry callback onSuccess");
public void onFailure(Throwable t) { latch.countDown();
log.error("Failed to send telemetry", t); }
latch.countDown();
} @Override
}); public void onFailure(Throwable t) {
log.error("Failed to send telemetry", t);
latch.countDown();
}
})
.build());
assertThat(latch.await(TIMEOUT, TimeUnit.SECONDS)).as("await sendTelemetry callback"); assertThat(latch.await(TIMEOUT, TimeUnit.SECONDS)).as("await sendTelemetry callback");
} }
@ -826,19 +833,26 @@ public class WebsocketApiTest extends AbstractControllerTest {
private void sendAttributes(TenantId tenantId, EntityId entityId, TbAttributeSubscriptionScope scope, List<AttributeKvEntry> attrData) throws InterruptedException { private void sendAttributes(TenantId tenantId, EntityId entityId, TbAttributeSubscriptionScope scope, List<AttributeKvEntry> attrData) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1); CountDownLatch latch = new CountDownLatch(1);
tsService.saveAndNotify(tenantId, entityId, scope.getAttributeScope(), attrData, new FutureCallback<Void>() { tsService.saveAttributes(AttributesSaveRequest.builder()
@Override .tenantId(tenantId)
public void onSuccess(@Nullable Void result) { .entityId(entityId)
log.debug("sendAttributes callback onSuccess"); .scope(scope.getAttributeScope())
latch.countDown(); .entries(attrData)
} .callback(new FutureCallback<>() {
@Override
@Override public void onSuccess(@Nullable Void result) {
public void onFailure(Throwable t) { log.debug("sendAttributes callback onSuccess");
log.error("Failed to sendAttributes", t); latch.countDown();
latch.countDown(); }
}
}); @Override
public void onFailure(Throwable t) {
log.error("Failed to sendAttributes", t);
latch.countDown();
}
})
.build());
assertThat(latch.await(TIMEOUT, TimeUnit.SECONDS)).as("await sendAttributes callback").isTrue(); assertThat(latch.await(TIMEOUT, TimeUnit.SECONDS)).as("await sendAttributes callback").isTrue();
} }
} }

241
application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java

@ -27,6 +27,7 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.util.ReflectionTestUtils;
import org.thingsboard.rule.engine.api.AttributesSaveRequest;
import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
@ -72,7 +73,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await; import static org.awaitility.Awaitility.await;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.then; import static org.mockito.BDDMockito.then;
@ -82,6 +83,7 @@ import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset; import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static org.thingsboard.server.service.state.DefaultDeviceStateService.ACTIVITY_STATE; import static org.thingsboard.server.service.state.DefaultDeviceStateService.ACTIVITY_STATE;
@ -208,9 +210,12 @@ public class DefaultDeviceStateServiceTest {
service.onDeviceConnect(tenantId, deviceId, lastConnectTime); service.onDeviceConnect(tenantId, deviceId, lastConnectTime);
// THEN // THEN
then(telemetrySubscriptionService).should().saveAttrAndNotify( then(telemetrySubscriptionService).should().saveAttributes(argThat(request ->
eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(AttributeScope.SERVER_SCOPE), eq(LAST_CONNECT_TIME), eq(lastConnectTime), any() request.getTenantId().equals(TenantId.SYS_TENANT_ID) && request.getEntityId().equals(deviceId) &&
); request.getScope().equals(AttributeScope.SERVER_SCOPE) &&
request.getEntries().get(0).getKey().equals(LAST_CONNECT_TIME) &&
request.getEntries().get(0).getValue().equals(lastConnectTime)
));
var msgCaptor = ArgumentCaptor.forClass(TbMsg.class); var msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
then(clusterService).should().pushMsgToRuleEngine(eq(tenantId), eq(deviceId), msgCaptor.capture(), any()); then(clusterService).should().pushMsgToRuleEngine(eq(tenantId), eq(deviceId), msgCaptor.capture(), any());
@ -292,10 +297,12 @@ public class DefaultDeviceStateServiceTest {
service.onDeviceDisconnect(tenantId, deviceId, lastDisconnectTime); service.onDeviceDisconnect(tenantId, deviceId, lastDisconnectTime);
// THEN // THEN
then(telemetrySubscriptionService).should().saveAttrAndNotify( then(telemetrySubscriptionService).should().saveAttributes(argThat(request ->
eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(AttributeScope.SERVER_SCOPE), request.getTenantId().equals(TenantId.SYS_TENANT_ID) && request.getEntityId().equals(deviceId) &&
eq(LAST_DISCONNECT_TIME), eq(lastDisconnectTime), any() request.getScope().equals(AttributeScope.SERVER_SCOPE) &&
); request.getEntries().get(0).getKey().equals(LAST_DISCONNECT_TIME) &&
request.getEntries().get(0).getValue().equals(lastDisconnectTime)
));
var msgCaptor = ArgumentCaptor.forClass(TbMsg.class); var msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
then(clusterService).should().pushMsgToRuleEngine(eq(tenantId), eq(deviceId), msgCaptor.capture(), any()); then(clusterService).should().pushMsgToRuleEngine(eq(tenantId), eq(deviceId), msgCaptor.capture(), any());
@ -413,14 +420,18 @@ public class DefaultDeviceStateServiceTest {
service.onDeviceInactivity(tenantId, deviceId, lastInactivityTime); service.onDeviceInactivity(tenantId, deviceId, lastInactivityTime);
// THEN // THEN
then(telemetrySubscriptionService).should().saveAttrAndNotify( then(telemetrySubscriptionService).should().saveAttributes(argThat(request ->
eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(AttributeScope.SERVER_SCOPE), request.getTenantId().equals(TenantId.SYS_TENANT_ID) && request.getEntityId().equals(deviceId) &&
eq(INACTIVITY_ALARM_TIME), eq(lastInactivityTime), any() request.getScope().equals(AttributeScope.SERVER_SCOPE) &&
); request.getEntries().get(0).getKey().equals(INACTIVITY_ALARM_TIME) &&
then(telemetrySubscriptionService).should().saveAttrAndNotify( request.getEntries().get(0).getValue().equals(lastInactivityTime)
eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(AttributeScope.SERVER_SCOPE), ));
eq(ACTIVITY_STATE), eq(false), any() then(telemetrySubscriptionService).should().saveAttributes(argThat(request ->
); request.getTenantId().equals(TenantId.SYS_TENANT_ID) && request.getEntityId().equals(deviceId) &&
request.getScope().equals(AttributeScope.SERVER_SCOPE) &&
request.getEntries().get(0).getKey().equals(ACTIVITY_STATE) &&
request.getEntries().get(0).getValue().equals(false)
));
var msgCaptor = ArgumentCaptor.forClass(TbMsg.class); var msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
then(clusterService).should() then(clusterService).should()
@ -453,14 +464,17 @@ public class DefaultDeviceStateServiceTest {
service.updateInactivityStateIfExpired(System.currentTimeMillis(), deviceId, deviceStateData); service.updateInactivityStateIfExpired(System.currentTimeMillis(), deviceId, deviceStateData);
// THEN // THEN
then(telemetrySubscriptionService).should().saveAttrAndNotify( then(telemetrySubscriptionService).should().saveAttributes(argThat(request ->
eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(AttributeScope.SERVER_SCOPE), request.getTenantId().equals(TenantId.SYS_TENANT_ID) && request.getEntityId().equals(deviceId) &&
eq(INACTIVITY_ALARM_TIME), anyLong(), any() request.getScope().equals(AttributeScope.SERVER_SCOPE) &&
); request.getEntries().get(0).getKey().equals(INACTIVITY_ALARM_TIME)
then(telemetrySubscriptionService).should().saveAttrAndNotify( ));
eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(AttributeScope.SERVER_SCOPE), then(telemetrySubscriptionService).should().saveAttributes(argThat(request ->
eq(ACTIVITY_STATE), eq(false), any() request.getTenantId().equals(TenantId.SYS_TENANT_ID) && request.getEntityId().equals(deviceId) &&
); request.getScope().equals(AttributeScope.SERVER_SCOPE) &&
request.getEntries().get(0).getKey().equals(ACTIVITY_STATE) &&
request.getEntries().get(0).getValue().equals(false)
));
var msgCaptor = ArgumentCaptor.forClass(TbMsg.class); var msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
then(clusterService).should() then(clusterService).should()
@ -612,7 +626,9 @@ public class DefaultDeviceStateServiceTest {
long newTimeout = System.currentTimeMillis() - deviceState.getLastActivityTime() + increase; long newTimeout = System.currentTimeMillis() - deviceState.getLastActivityTime() + increase;
service.onDeviceInactivityTimeoutUpdate(tenantId, deviceId, newTimeout); service.onDeviceInactivityTimeoutUpdate(tenantId, deviceId, newTimeout);
verify(telemetrySubscriptionService, never()).saveAttrAndNotify(any(), eq(deviceId), any(AttributeScope.class), eq(ACTIVITY_STATE), any(), any()); verify(telemetrySubscriptionService, never()).saveAttributes(argThat(request ->
request.getEntityId().equals(deviceId) && request.getEntries().get(0).getKey().equals(ACTIVITY_STATE)
));
Thread.sleep(defaultTimeout + increase); Thread.sleep(defaultTimeout + increase);
service.checkStates(); service.checkStates();
activityVerify(false); activityVerify(false);
@ -651,7 +667,9 @@ public class DefaultDeviceStateServiceTest {
long newTimeout = 1; long newTimeout = 1;
Thread.sleep(newTimeout); Thread.sleep(newTimeout);
verify(telemetrySubscriptionService, never()).saveAttrAndNotify(any(), eq(deviceId), any(AttributeScope.class), eq(ACTIVITY_STATE), any(), any()); verify(telemetrySubscriptionService, never()).saveAttributes(argThat(request ->
request.getEntityId().equals(deviceId) && request.getEntries().get(0).getKey().equals(ACTIVITY_STATE)
));
} }
@Test @Test
@ -672,8 +690,6 @@ public class DefaultDeviceStateServiceTest {
service.onDeviceActivity(tenantId, deviceId, System.currentTimeMillis()); service.onDeviceActivity(tenantId, deviceId, System.currentTimeMillis());
activityVerify(true); activityVerify(true);
verify(telemetrySubscriptionService, never()).saveAttrAndNotify(any(), eq(deviceId), any(AttributeScope.class), eq(ACTIVITY_STATE), any(), any());
long newTimeout = 1; long newTimeout = 1;
Thread.sleep(newTimeout); Thread.sleep(newTimeout);
@ -713,11 +729,17 @@ public class DefaultDeviceStateServiceTest {
long newTimeout = 1; long newTimeout = 1;
service.onDeviceInactivityTimeoutUpdate(tenantId, deviceId, newTimeout); service.onDeviceInactivityTimeoutUpdate(tenantId, deviceId, newTimeout);
verify(telemetrySubscriptionService, never()).saveAttrAndNotify(any(), eq(deviceId), any(AttributeScope.class), eq(ACTIVITY_STATE), any(), any()); verify(telemetrySubscriptionService, never()).saveAttributes(argThat(request ->
request.getEntityId().equals(deviceId) && request.getEntries().get(0).getKey().equals(ACTIVITY_STATE)
));
} }
private void activityVerify(boolean isActive) { private void activityVerify(boolean isActive) {
verify(telemetrySubscriptionService).saveAttrAndNotify(any(), eq(deviceId), any(AttributeScope.class), eq(ACTIVITY_STATE), eq(isActive), any()); verify(telemetrySubscriptionService).saveAttributes(argThat(request ->
request.getEntityId().equals(deviceId) &&
request.getEntries().get(0).getKey().equals(ACTIVITY_STATE) &&
request.getEntries().get(0).getValue().equals(isActive)
));
} }
@Test @Test
@ -763,21 +785,27 @@ public class DefaultDeviceStateServiceTest {
// THEN // THEN
assertThat(deviceState.isActive()).isEqualTo(true); assertThat(deviceState.isActive()).isEqualTo(true);
assertThat(deviceState.getLastActivityTime()).isEqualTo(lastReportedActivity); assertThat(deviceState.getLastActivityTime()).isEqualTo(lastReportedActivity);
then(telemetrySubscriptionService).should().saveAttrAndNotify( then(telemetrySubscriptionService).should().saveAttributes(argThat(request ->
any(), eq(deviceId), any(AttributeScope.class), eq(LAST_ACTIVITY_TIME), eq(lastReportedActivity), any() request.getEntityId().equals(deviceId) &&
); request.getEntries().get(0).getKey().equals(LAST_ACTIVITY_TIME) &&
request.getEntries().get(0).getValue().equals(lastReportedActivity)
));
assertThat(deviceState.getLastInactivityAlarmTime()).isEqualTo(expectedInactivityAlarmTime); assertThat(deviceState.getLastInactivityAlarmTime()).isEqualTo(expectedInactivityAlarmTime);
if (shouldSetInactivityAlarmTimeToZero) { if (shouldSetInactivityAlarmTimeToZero) {
then(telemetrySubscriptionService).should().saveAttrAndNotify( then(telemetrySubscriptionService).should().saveAttributes(argThat(request ->
any(), eq(deviceId), any(AttributeScope.class), eq(INACTIVITY_ALARM_TIME), eq(0L), any() request.getEntityId().equals(deviceId) &&
); request.getEntries().get(0).getKey().equals(INACTIVITY_ALARM_TIME) &&
request.getEntries().get(0).getValue().equals(0L)
));
} }
if (shouldUpdateActivityStateToActive) { if (shouldUpdateActivityStateToActive) {
then(telemetrySubscriptionService).should().saveAttrAndNotify( then(telemetrySubscriptionService).should().saveAttributes(argThat(request ->
eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(AttributeScope.SERVER_SCOPE), eq(ACTIVITY_STATE), eq(true), any() request.getEntityId().equals(deviceId) &&
); request.getEntries().get(0).getKey().equals(ACTIVITY_STATE) &&
request.getEntries().get(0).getValue().equals(true)
));
var msgCaptor = ArgumentCaptor.forClass(TbMsg.class); var msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
then(clusterService).should().pushMsgToRuleEngine(eq(tenantId), eq(deviceId), msgCaptor.capture(), any()); then(clusterService).should().pushMsgToRuleEngine(eq(tenantId), eq(deviceId), msgCaptor.capture(), any());
@ -796,28 +824,28 @@ public class DefaultDeviceStateServiceTest {
private static Stream<Arguments> provideParametersForUpdateActivityState() { private static Stream<Arguments> provideParametersForUpdateActivityState() {
return Stream.of( return Stream.of(
Arguments.of(true, 100, 120, 80, 80, false, false), Arguments.of(true, 100, 120, 80, 80, false, false),
Arguments.of(true, 100, 120, 100, 100, false, false), Arguments.of(true, 100, 120, 100, 100, false, false),
Arguments.of(false, 100, 120, 110, 110, false, true), Arguments.of(false, 100, 120, 110, 110, false, true),
Arguments.of(true, 100, 100, 80, 80, false, false), Arguments.of(true, 100, 100, 80, 80, false, false),
Arguments.of(true, 100, 100, 100, 100, false, false), Arguments.of(true, 100, 100, 100, 100, false, false),
Arguments.of(false, 100, 100, 110, 0, true, true), Arguments.of(false, 100, 100, 110, 0, true, true),
Arguments.of(false, 100, 110, 110, 0, true, true), Arguments.of(false, 100, 110, 110, 0, true, true),
Arguments.of(false, 100, 110, 120, 0, true, true), Arguments.of(false, 100, 110, 120, 0, true, true),
Arguments.of(true, 0, 0, 0, 0, false, false), Arguments.of(true, 0, 0, 0, 0, false, false),
Arguments.of(false, 0, 0, 0, 0, true, true) Arguments.of(false, 0, 0, 0, 0, true, true)
); );
} }
@ -857,9 +885,10 @@ public class DefaultDeviceStateServiceTest {
assertThat(deviceState.getInactivityTimeout()).isEqualTo(newInactivityTimeout); assertThat(deviceState.getInactivityTimeout()).isEqualTo(newInactivityTimeout);
assertThat(deviceState.isActive()).isEqualTo(expectedActivityState); assertThat(deviceState.isActive()).isEqualTo(expectedActivityState);
if (activityState && !expectedActivityState) { if (activityState && !expectedActivityState) {
then(telemetrySubscriptionService).should().saveAttrAndNotify( then(telemetrySubscriptionService).should().saveAttributes(argThat(request ->
any(), eq(deviceId), any(AttributeScope.class), eq(ACTIVITY_STATE), eq(false), any() request.getEntityId().equals(deviceId) && request.getEntries().get(0).getKey().equals(ACTIVITY_STATE) &&
); request.getEntries().get(0).getValue().equals(false)
));
} }
} }
@ -954,9 +983,10 @@ public class DefaultDeviceStateServiceTest {
assertThat(state.getLastInactivityAlarmTime()).isEqualTo(expectedLastInactivityAlarmTime); assertThat(state.getLastInactivityAlarmTime()).isEqualTo(expectedLastInactivityAlarmTime);
if (shouldUpdateActivityStateToInactive) { if (shouldUpdateActivityStateToInactive) {
then(telemetrySubscriptionService).should().saveAttrAndNotify( then(telemetrySubscriptionService).should().saveAttributes(argThat(request ->
eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(AttributeScope.SERVER_SCOPE), eq(ACTIVITY_STATE), eq(false), any() request.getEntityId().equals(deviceId) && request.getEntries().get(0).getKey().equals(ACTIVITY_STATE) &&
); request.getEntries().get(0).getValue().equals(false)
));
var msgCaptor = ArgumentCaptor.forClass(TbMsg.class); var msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
then(clusterService).should().pushMsgToRuleEngine(eq(tenantId), eq(deviceId), msgCaptor.capture(), any()); then(clusterService).should().pushMsgToRuleEngine(eq(tenantId), eq(deviceId), msgCaptor.capture(), any());
@ -971,72 +1001,74 @@ public class DefaultDeviceStateServiceTest {
assertThat(actualNotification.getDeviceId()).isEqualTo(deviceId); assertThat(actualNotification.getDeviceId()).isEqualTo(deviceId);
assertThat(actualNotification.isActive()).isFalse(); assertThat(actualNotification.isActive()).isFalse();
then(telemetrySubscriptionService).should().saveAttrAndNotify( then(telemetrySubscriptionService).should().saveAttributes(argThat(request ->
eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(AttributeScope.SERVER_SCOPE), request.getTenantId().equals(TenantId.SYS_TENANT_ID) && request.getEntityId().equals(deviceId) &&
eq(INACTIVITY_ALARM_TIME), eq(expectedLastInactivityAlarmTime), any() request.getScope().equals(AttributeScope.SERVER_SCOPE) &&
); request.getEntries().get(0).getKey().equals(INACTIVITY_ALARM_TIME) &&
request.getEntries().get(0).getValue().equals(expectedLastInactivityAlarmTime)
));
} }
} }
private static Stream<Arguments> provideParametersForUpdateInactivityStateIfExpired() { private static Stream<Arguments> provideParametersForUpdateInactivityStateIfExpired() {
return Stream.of( return Stream.of(
Arguments.of(false, 100, 70, 90, 70, 60, false, 90, false), Arguments.of(false, 100, 70, 90, 70, 60, false, 90, false),
Arguments.of(false, 100, 40, 50, 70, 10, false, 50, false), Arguments.of(false, 100, 40, 50, 70, 10, false, 50, false),
Arguments.of(false, 100, 25, 60, 75, 25, false, 60, false), Arguments.of(false, 100, 25, 60, 75, 25, false, 60, false),
Arguments.of(false, 100, 60, 70, 10, 50, false, 70, false), Arguments.of(false, 100, 60, 70, 10, 50, false, 70, false),
Arguments.of(false, 100, 10, 15, 90, 10, false, 15, false), Arguments.of(false, 100, 10, 15, 90, 10, false, 15, false),
Arguments.of(false, 100, 0, 40, 75, 0, false, 40, false), Arguments.of(false, 100, 0, 40, 75, 0, false, 40, false),
Arguments.of(true, 100, 90, 80, 80, 50, true, 80, false), Arguments.of(true, 100, 90, 80, 80, 50, true, 80, false),
Arguments.of(true, 100, 95, 90, 10, 50, true, 90, false), Arguments.of(true, 100, 95, 90, 10, 50, true, 90, false),
Arguments.of(true, 100, 10, 10, 90, 10, false, 100, true), Arguments.of(true, 100, 10, 10, 90, 10, false, 100, true),
Arguments.of(true, 100, 10, 10, 90, 11, true, 10, false), Arguments.of(true, 100, 10, 10, 90, 11, true, 10, false),
Arguments.of(true, 100, 15, 10, 85, 5, false, 100, true), Arguments.of(true, 100, 15, 10, 85, 5, false, 100, true),
Arguments.of(true, 100, 15, 10, 75, 5, false, 100, true), Arguments.of(true, 100, 15, 10, 75, 5, false, 100, true),
Arguments.of(true, 100, 95, 90, 5, 50, false, 100, true), Arguments.of(true, 100, 95, 90, 5, 50, false, 100, true),
Arguments.of(true, 100, 0, 0, 101, 0, true, 0, false), Arguments.of(true, 100, 0, 0, 101, 0, true, 0, false),
Arguments.of(true, 100, 0, 0, 100, 0, false, 100, true), Arguments.of(true, 100, 0, 0, 100, 0, false, 100, true),
Arguments.of(true, 100, 0, 0, 99, 0, false, 100, true), Arguments.of(true, 100, 0, 0, 99, 0, false, 100, true),
Arguments.of(true, 100, 0, 0, 120, 10, true, 0, false), Arguments.of(true, 100, 0, 0, 120, 10, true, 0, false),
Arguments.of(true, 100, 50, 0, 100, 0, true, 0, false), Arguments.of(true, 100, 50, 0, 100, 0, true, 0, false),
Arguments.of(true, 100, 10, 0, 91, 0, true, 0, false), Arguments.of(true, 100, 10, 0, 91, 0, true, 0, false),
Arguments.of(true, 100, 90, 0, 10, 0, false, 100, true), Arguments.of(true, 100, 90, 0, 10, 0, false, 100, true),
Arguments.of(true, 100, 100, 100, 1, 0, true, 100, false), Arguments.of(true, 100, 100, 100, 1, 0, true, 100, false),
Arguments.of(true, 100, 100, 100, 100, 100, true, 100, false), Arguments.of(true, 100, 100, 100, 100, 100, true, 100, false),
Arguments.of(false, 100, 59, 60, 30, 10, false, 60, false), Arguments.of(false, 100, 59, 60, 30, 10, false, 60, false),
Arguments.of(true, 100, 60, 60, 30, 10, false, 100, true), Arguments.of(true, 100, 60, 60, 30, 10, false, 100, true),
Arguments.of(true, 100, 61, 60, 30, 10, false, 100, true), Arguments.of(true, 100, 61, 60, 30, 10, false, 100, true),
Arguments.of(true, 0, 0, 0, 1, 0, true, 0, false), Arguments.of(true, 0, 0, 0, 1, 0, true, 0, false),
Arguments.of(true, 0, 0, 0, 0, 0, false, 0, true), Arguments.of(true, 0, 0, 0, 0, 0, false, 0, true),
Arguments.of(true, 100, 90, 80, 20, 70, true, 80, false), Arguments.of(true, 100, 90, 80, 20, 70, true, 80, false),
Arguments.of(true, 100, 80, 90, 30, 70, true, 90, false) Arguments.of(true, 100, 80, 90, 30, 70, true, 90, false)
); );
} }
@ -1100,7 +1132,10 @@ public class DefaultDeviceStateServiceTest {
// THEN // THEN
await().atMost(1, TimeUnit.SECONDS).untilAsserted(() -> { await().atMost(1, TimeUnit.SECONDS).untilAsserted(() -> {
assertThat(service.deviceStates.get(deviceId).getState().isActive()).isEqualTo(false); assertThat(service.deviceStates.get(deviceId).getState().isActive()).isEqualTo(false);
then(telemetrySubscriptionService).should().saveAttrAndNotify(eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(AttributeScope.SERVER_SCOPE), eq(ACTIVITY_STATE), eq(false), any()); then(telemetrySubscriptionService).should().saveAttributes(argThat(request ->
request.getEntityId().equals(deviceId) && request.getEntries().get(0).getKey().equals(ACTIVITY_STATE) &&
request.getEntries().get(0).getValue().equals(false)
));
}); });
} }
@ -1127,10 +1162,31 @@ public class DefaultDeviceStateServiceTest {
service.onDeviceActivity(tenantId, deviceId, currentTime); service.onDeviceActivity(tenantId, deviceId, currentTime);
// THEN // THEN
ArgumentCaptor<AttributesSaveRequest> attributeRequestCaptor = ArgumentCaptor.forClass(AttributesSaveRequest.class);
then(telemetrySubscriptionService).should(times(2)).saveAttributes(attributeRequestCaptor.capture());
await().atMost(1, TimeUnit.SECONDS).untilAsserted(() -> { await().atMost(1, TimeUnit.SECONDS).untilAsserted(() -> {
assertThat(service.deviceStates.get(deviceId).getState().isActive()).isEqualTo(true); assertThat(service.deviceStates.get(deviceId).getState().isActive()).isEqualTo(true);
then(telemetrySubscriptionService).should().saveAttrAndNotify(eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(AttributeScope.SERVER_SCOPE), eq(LAST_ACTIVITY_TIME), eq(currentTime), any());
then(telemetrySubscriptionService).should().saveAttrAndNotify(eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(AttributeScope.SERVER_SCOPE), eq(ACTIVITY_STATE), eq(true), any()); assertThat(attributeRequestCaptor.getAllValues()).hasSize(2)
.anySatisfy(request -> {
assertThat(request.getTenantId()).isEqualTo(TenantId.SYS_TENANT_ID);
assertThat(request.getEntityId()).isEqualTo(deviceId);
assertThat(request.getScope()).isEqualTo(AttributeScope.SERVER_SCOPE);
assertThat(request.getEntries()).singleElement().satisfies(attributeKvEntry -> {
assertThat(attributeKvEntry.getKey()).isEqualTo(LAST_ACTIVITY_TIME);
assertThat(attributeKvEntry.getLongValue()).hasValue(currentTime);
});
})
.anySatisfy(request -> {
assertThat(request.getTenantId()).isEqualTo(TenantId.SYS_TENANT_ID);
assertThat(request.getEntityId()).isEqualTo(deviceId);
assertThat(request.getScope()).isEqualTo(AttributeScope.SERVER_SCOPE);
assertThat(request.getEntries()).singleElement().satisfies(attributeKvEntry -> {
assertThat(attributeKvEntry.getKey()).isEqualTo(ACTIVITY_STATE);
assertThat(attributeKvEntry.getBooleanValue()).hasValue(true);
});
});
}); });
} }
@ -1174,7 +1230,10 @@ public class DefaultDeviceStateServiceTest {
// THEN // THEN
await().atMost(1, TimeUnit.SECONDS).untilAsserted(() -> { await().atMost(1, TimeUnit.SECONDS).untilAsserted(() -> {
assertThat(service.deviceStates.get(deviceId).getState().isActive()).isEqualTo(true); assertThat(service.deviceStates.get(deviceId).getState().isActive()).isEqualTo(true);
then(telemetrySubscriptionService).should().saveAttrAndNotify(eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(AttributeScope.SERVER_SCOPE), eq(ACTIVITY_STATE), eq(true), any()); then(telemetrySubscriptionService).should().saveAttributes(argThat(request ->
request.getEntityId().equals(deviceId) && request.getEntries().get(0).getKey().equals(ACTIVITY_STATE) &&
request.getEntries().get(0).getValue().equals(true)
));
}); });
} }

6
common/dao-api/src/main/java/org/thingsboard/server/dao/attributes/AttributesService.java

@ -37,16 +37,10 @@ public interface AttributesService {
ListenableFuture<List<AttributeKvEntry>> findAll(TenantId tenantId, EntityId entityId, AttributeScope scope); ListenableFuture<List<AttributeKvEntry>> findAll(TenantId tenantId, EntityId entityId, AttributeScope scope);
@Deprecated(since = "3.7.0")
ListenableFuture<List<Long>> save(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes);
ListenableFuture<List<Long>> save(TenantId tenantId, EntityId entityId, AttributeScope scope, List<AttributeKvEntry> attributes); ListenableFuture<List<Long>> save(TenantId tenantId, EntityId entityId, AttributeScope scope, List<AttributeKvEntry> attributes);
ListenableFuture<Long> save(TenantId tenantId, EntityId entityId, AttributeScope scope, AttributeKvEntry attribute); ListenableFuture<Long> save(TenantId tenantId, EntityId entityId, AttributeScope scope, AttributeKvEntry attribute);
@Deprecated(since = "3.7.0")
ListenableFuture<List<String>> removeAll(TenantId tenantId, EntityId entityId, String scope, List<String> attributeKeys);
ListenableFuture<List<String>> removeAll(TenantId tenantId, EntityId entityId, AttributeScope scope, List<String> attributeKeys); ListenableFuture<List<String>> removeAll(TenantId tenantId, EntityId entityId, AttributeScope scope, List<String> attributeKeys);
List<String> findAllKeysByDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId); List<String> findAllKeysByDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId);

2
common/dao-api/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java

@ -56,7 +56,7 @@ public interface TimeseriesService {
ListenableFuture<List<TsKvLatestRemovingResult>> removeLatest(TenantId tenantId, EntityId entityId, Collection<String> keys); ListenableFuture<List<TsKvLatestRemovingResult>> removeLatest(TenantId tenantId, EntityId entityId, Collection<String> keys);
ListenableFuture<Collection<String>> removeAllLatest(TenantId tenantId, EntityId entityId); ListenableFuture<List<String>> removeAllLatest(TenantId tenantId, EntityId entityId);
List<String> findAllKeysByDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId); List<String> findAllKeysByDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId);

14
dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java

@ -101,14 +101,6 @@ public class BaseAttributesService implements AttributesService {
return attributesDao.save(tenantId, entityId, scope, attribute); return attributesDao.save(tenantId, entityId, scope, attribute);
} }
@Override
public ListenableFuture<List<Long>> save(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes) {
validate(entityId, scope);
AttributeUtils.validate(attributes, valueNoXssValidation);
List<ListenableFuture<Long>> saveFutures = attributes.stream().map(attribute -> attributesDao.save(tenantId, entityId, AttributeScope.valueOf(scope), attribute)).collect(Collectors.toList());
return Futures.allAsList(saveFutures);
}
@Override @Override
public ListenableFuture<List<Long>> save(TenantId tenantId, EntityId entityId, AttributeScope scope, List<AttributeKvEntry> attributes) { public ListenableFuture<List<Long>> save(TenantId tenantId, EntityId entityId, AttributeScope scope, List<AttributeKvEntry> attributes) {
validate(entityId, scope); validate(entityId, scope);
@ -117,12 +109,6 @@ public class BaseAttributesService implements AttributesService {
return Futures.allAsList(saveFutures); return Futures.allAsList(saveFutures);
} }
@Override
public ListenableFuture<List<String>> removeAll(TenantId tenantId, EntityId entityId, String scope, List<String> attributeKeys) {
validate(entityId, scope);
return Futures.allAsList(attributesDao.removeAll(tenantId, entityId, AttributeScope.valueOf(scope), attributeKeys));
}
@Override @Override
public ListenableFuture<List<String>> removeAll(TenantId tenantId, EntityId entityId, AttributeScope scope, List<String> attributeKeys) { public ListenableFuture<List<String>> removeAll(TenantId tenantId, EntityId entityId, AttributeScope scope, List<String> attributeKeys) {
validate(entityId, scope); validate(entityId, scope);

10
dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java

@ -222,11 +222,6 @@ public class CachedAttributesService implements AttributesService {
return doSave(tenantId, entityId, scope, attribute); return doSave(tenantId, entityId, scope, attribute);
} }
@Override
public ListenableFuture<List<Long>> save(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes) {
return save(tenantId, entityId, AttributeScope.valueOf(scope), attributes);
}
@Override @Override
public ListenableFuture<List<Long>> save(TenantId tenantId, EntityId entityId, AttributeScope scope, List<AttributeKvEntry> attributes) { public ListenableFuture<List<Long>> save(TenantId tenantId, EntityId entityId, AttributeScope scope, List<AttributeKvEntry> attributes) {
validate(entityId, scope); validate(entityId, scope);
@ -255,11 +250,6 @@ public class CachedAttributesService implements AttributesService {
log.trace("[{}][{}][{}] after cache put.", entityId, scope, key); log.trace("[{}][{}][{}] after cache put.", entityId, scope, key);
} }
@Override
public ListenableFuture<List<String>> removeAll(TenantId tenantId, EntityId entityId, String scope, List<String> attributeKeys) {
return removeAll(tenantId, entityId, AttributeScope.valueOf(scope), attributeKeys);
}
@Override @Override
public ListenableFuture<List<String>> removeAll(TenantId tenantId, EntityId entityId, AttributeScope scope, List<String> attributeKeys) { public ListenableFuture<List<String>> removeAll(TenantId tenantId, EntityId entityId, AttributeScope scope, List<String> attributeKeys) {
validate(entityId, scope); validate(entityId, scope);

4
dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java

@ -254,11 +254,11 @@ public class BaseTimeseriesService implements TimeseriesService {
} }
@Override @Override
public ListenableFuture<Collection<String>> removeAllLatest(TenantId tenantId, EntityId entityId) { public ListenableFuture<List<String>> removeAllLatest(TenantId tenantId, EntityId entityId) {
validate(entityId); validate(entityId);
return Futures.transformAsync(this.findAllLatest(tenantId, entityId), latest -> { return Futures.transformAsync(this.findAllLatest(tenantId, entityId), latest -> {
if (latest != null && !latest.isEmpty()) { if (latest != null && !latest.isEmpty()) {
Collection<String> keys = latest.stream().map(TsKvEntry::getKey).collect(Collectors.toList()); List<String> keys = latest.stream().map(TsKvEntry::getKey).collect(Collectors.toList());
return Futures.transform(this.removeLatest(tenantId, entityId, keys), res -> keys, MoreExecutors.directExecutor()); return Futures.transform(this.removeLatest(tenantId, entityId, keys), res -> keys, MoreExecutors.directExecutor());
} else { } else {
return Futures.immediateFuture(Collections.emptyList()); return Futures.immediateFuture(Collections.emptyList());

117
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/AttributesDeleteRequest.java

@ -0,0 +1,117 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.rule.engine.api;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.SettableFuture;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;
import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import java.util.List;
@Getter
@ToString
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class AttributesDeleteRequest {
private final TenantId tenantId;
private final EntityId entityId;
private final AttributeScope scope;
private final List<String> keys;
private final boolean notifyDevice;
private final FutureCallback<Void> callback;
public static Builder builder() {
return new Builder();
}
public static class Builder {
private TenantId tenantId;
private EntityId entityId;
private AttributeScope scope;
private List<String> keys;
private boolean notifyDevice;
private FutureCallback<Void> callback;
Builder() {}
public Builder tenantId(TenantId tenantId) {
this.tenantId = tenantId;
return this;
}
public Builder entityId(EntityId entityId) {
this.entityId = entityId;
return this;
}
public Builder scope(AttributeScope scope) {
this.scope = scope;
return this;
}
@Deprecated
public Builder scope(String scope) {
try {
this.scope = AttributeScope.valueOf(scope);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Invalid attribute scope '" + scope + "'");
}
return this;
}
public Builder keys(List<String> keys) {
this.keys = keys;
return this;
}
public Builder notifyDevice(boolean notifyDevice) {
this.notifyDevice = notifyDevice;
return this;
}
public Builder callback(FutureCallback<Void> callback) {
this.callback = callback;
return this;
}
public Builder future(SettableFuture<Void> future) {
return callback(new FutureCallback<>() {
@Override
public void onSuccess(Void result) {
future.set(result);
}
@Override
public void onFailure(Throwable t) {
future.setException(t);
}
});
}
public AttributesDeleteRequest build() {
return new AttributesDeleteRequest(tenantId, entityId, scope, keys, notifyDevice, callback);
}
}
}

128
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/AttributesSaveRequest.java

@ -0,0 +1,128 @@
/**
* 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.rule.engine.api;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.SettableFuture;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;
import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.KvEntry;
import java.util.List;
@Getter
@ToString
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class AttributesSaveRequest {
private final TenantId tenantId;
private final EntityId entityId;
private final AttributeScope scope;
private final List<AttributeKvEntry> entries;
private final boolean notifyDevice;
private final FutureCallback<Void> callback;
public static Builder builder() {
return new Builder();
}
public static class Builder {
private TenantId tenantId;
private EntityId entityId;
private AttributeScope scope;
private List<AttributeKvEntry> entries;
private boolean notifyDevice = true;
private FutureCallback<Void> callback;
Builder() {}
public Builder tenantId(TenantId tenantId) {
this.tenantId = tenantId;
return this;
}
public Builder entityId(EntityId entityId) {
this.entityId = entityId;
return this;
}
public Builder scope(AttributeScope scope) {
this.scope = scope;
return this;
}
@Deprecated
public Builder scope(String scope) {
try {
this.scope = AttributeScope.valueOf(scope);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Invalid attribute scope '" + scope + "'");
}
return this;
}
public Builder entries(List<AttributeKvEntry> entries) {
this.entries = entries;
return this;
}
public Builder entry(AttributeKvEntry entry) {
return entries(List.of(entry));
}
public Builder entry(KvEntry kvEntry) {
return entry(new BaseAttributeKvEntry(kvEntry, System.currentTimeMillis()));
}
public Builder notifyDevice(boolean notifyDevice) {
this.notifyDevice = notifyDevice;
return this;
}
public Builder callback(FutureCallback<Void> callback) {
this.callback = callback;
return this;
}
public Builder future(SettableFuture<Void> future) {
return callback(new FutureCallback<>() {
@Override
public void onSuccess(Void result) {
future.set(result);
}
@Override
public void onFailure(Throwable t) {
future.setException(t);
}
});
}
public AttributesSaveRequest build() {
return new AttributesSaveRequest(tenantId, entityId, scope, entries, notifyDevice, callback);
}
}
}

88
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineTelemetryService.java

@ -15,97 +15,17 @@
*/ */
package org.thingsboard.rule.engine.api; package org.thingsboard.rule.engine.api;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.DeleteTsKvQuery;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import java.util.Collection;
import java.util.List;
/** /**
* Created by ashvayka on 02.04.18. * Created by ashvayka on 02.04.18.
*/ */
public interface RuleEngineTelemetryService { public interface RuleEngineTelemetryService {
ListenableFuture<Void> saveAndNotify(TenantId tenantId, EntityId entityId, TsKvEntry ts); void saveTimeseries(TimeseriesSaveRequest request);
void saveAndNotify(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts, FutureCallback<Void> callback);
void saveAndNotify(TenantId tenantId, CustomerId id, EntityId entityId, List<TsKvEntry> ts, long ttl, FutureCallback<Void> callback);
void saveWithoutLatestAndNotify(TenantId tenantId, CustomerId id, EntityId entityId, List<TsKvEntry> ts, long ttl, FutureCallback<Void> callback);
@Deprecated(since = "3.7.0")
void saveAndNotify(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, FutureCallback<Void> callback);
void saveAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, List<AttributeKvEntry> attributes, FutureCallback<Void> callback);
@Deprecated(since = "3.7.0")
void saveAndNotify(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, boolean notifyDevice, FutureCallback<Void> callback);
void saveAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, List<AttributeKvEntry> attributes, boolean notifyDevice, FutureCallback<Void> callback);
void saveLatestAndNotify(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts, FutureCallback<Void> callback);
@Deprecated(since = "3.7.0")
ListenableFuture<Void> saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, long value);
ListenableFuture<Void> saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, long value);
@Deprecated(since = "3.7.0")
ListenableFuture<Void> saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, String value);
ListenableFuture<Void> saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, String value);
@Deprecated(since = "3.7.0")
ListenableFuture<Void> saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, double value);
ListenableFuture<Void> saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, double value);
@Deprecated(since = "3.7.0")
ListenableFuture<Void> saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, boolean value);
ListenableFuture<Void> saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, boolean value);
@Deprecated(since = "3.7.0")
void saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, long value, FutureCallback<Void> callback);
void saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, long value, FutureCallback<Void> callback);
@Deprecated(since = "3.7.0")
void saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, String value, FutureCallback<Void> callback);
void saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, String value, FutureCallback<Void> callback);
@Deprecated(since = "3.7.0")
void saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, double value, FutureCallback<Void> callback);
void saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, double value, FutureCallback<Void> callback);
@Deprecated(since = "3.7.0")
void saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, boolean value, FutureCallback<Void> callback);
void saveAttrAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, String key, boolean value, FutureCallback<Void> callback);
@Deprecated(since = "3.7.0")
void deleteAndNotify(TenantId tenantId, EntityId entityId, String scope, List<String> keys, FutureCallback<Void> callback);
void deleteAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, List<String> keys, FutureCallback<Void> callback);
@Deprecated(since = "3.7.0")
void deleteAndNotify(TenantId tenantId, EntityId entityId, String scope, List<String> keys, boolean notifyDevice, FutureCallback<Void> callback);
void deleteAndNotify(TenantId tenantId, EntityId entityId, AttributeScope scope, List<String> keys, boolean notifyDevice, FutureCallback<Void> callback); void saveAttributes(AttributesSaveRequest request);
void deleteLatest(TenantId tenantId, EntityId entityId, List<String> keys, FutureCallback<Void> callback); void deleteTimeseries(TimeseriesDeleteRequest request);
void deleteAllLatest(TenantId tenantId, EntityId entityId, FutureCallback<Collection<String>> callback); void deleteAttributes(AttributesDeleteRequest request);
void deleteTimeseriesAndNotify(TenantId tenantId, EntityId entityId, List<String> keys, List<DeleteTsKvQuery> deleteTsKvQueries, FutureCallback<Void> callback);
} }

85
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TimeseriesDeleteRequest.java

@ -0,0 +1,85 @@
/**
* 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.rule.engine.api;
import com.google.common.util.concurrent.FutureCallback;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.DeleteTsKvQuery;
import java.util.List;
@Getter
@ToString
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class TimeseriesDeleteRequest {
private final TenantId tenantId;
private final EntityId entityId;
private final List<String> keys;
private final List<DeleteTsKvQuery> deleteHistoryQueries;
private final FutureCallback<List<String>> callback;
public static Builder builder() {
return new Builder();
}
public static class Builder {
private TenantId tenantId;
private EntityId entityId;
private List<String> keys;
private List<DeleteTsKvQuery> deleteHistoryQueries;
private FutureCallback<List<String>> callback;
Builder() {}
public Builder tenantId(TenantId tenantId) {
this.tenantId = tenantId;
return this;
}
public Builder entityId(EntityId entityId) {
this.entityId = entityId;
return this;
}
public Builder keys(List<String> keys) {
this.keys = keys;
return this;
}
public Builder deleteHistoryQueries(List<DeleteTsKvQuery> deleteHistoryQueries) {
this.deleteHistoryQueries = deleteHistoryQueries;
return this;
}
public Builder callback(FutureCallback<List<String>> callback) {
this.callback = callback;
return this;
}
public TimeseriesDeleteRequest build() {
return new TimeseriesDeleteRequest(tenantId, entityId, keys, deleteHistoryQueries, callback);
}
}
}

131
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TimeseriesSaveRequest.java

@ -0,0 +1,131 @@
/**
* 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.rule.engine.api;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.SettableFuture;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.KvEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import java.util.List;
@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class TimeseriesSaveRequest {
private final TenantId tenantId;
private final CustomerId customerId;
private final EntityId entityId;
private final List<TsKvEntry> entries;
private final long ttl;
private final boolean saveLatest;
private final boolean onlyLatest;
private final FutureCallback<Void> callback;
public static Builder builder() {
return new Builder();
}
public static class Builder {
private TenantId tenantId;
private CustomerId customerId;
private EntityId entityId;
private List<TsKvEntry> entries;
private long ttl;
private FutureCallback<Void> callback;
private boolean saveLatest = true;
private boolean onlyLatest;
Builder() {}
public Builder tenantId(TenantId tenantId) {
this.tenantId = tenantId;
return this;
}
public Builder customerId(CustomerId customerId) {
this.customerId = customerId;
return this;
}
public Builder entityId(EntityId entityId) {
this.entityId = entityId;
return this;
}
public Builder entries(List<TsKvEntry> entries) {
this.entries = entries;
return this;
}
public Builder entry(TsKvEntry entry) {
return entries(List.of(entry));
}
public Builder entry(KvEntry kvEntry) {
return entry(new BasicTsKvEntry(System.currentTimeMillis(), kvEntry));
}
public Builder ttl(long ttl) {
this.ttl = ttl;
return this;
}
public Builder saveLatest(boolean saveLatest) {
this.saveLatest = saveLatest;
return this;
}
public Builder onlyLatest(boolean onlyLatest) {
this.onlyLatest = onlyLatest;
this.saveLatest = true;
return this;
}
public Builder callback(FutureCallback<Void> callback) {
this.callback = callback;
return this;
}
public Builder future(SettableFuture<Void> future) {
return callback(new FutureCallback<>() {
@Override
public void onSuccess(Void result) {
future.set(result);
}
@Override
public void onFailure(Throwable t) {
future.setException(t);
}
});
}
public TimeseriesSaveRequest build() {
return new TimeseriesSaveRequest(tenantId, customerId, entityId, entries, ttl, saveLatest, onlyLatest, callback);
}
}
}

20
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java

@ -23,6 +23,8 @@ import com.google.gson.JsonPrimitive;
import jakarta.annotation.Nullable; import jakarta.annotation.Nullable;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.common.util.DonAsynchron; import org.thingsboard.common.util.DonAsynchron;
import org.thingsboard.rule.engine.api.AttributesDeleteRequest;
import org.thingsboard.rule.engine.api.AttributesSaveRequest;
import org.thingsboard.rule.engine.api.EmptyNodeConfiguration; import org.thingsboard.rule.engine.api.EmptyNodeConfiguration;
import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.RuleNode;
import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbContext;
@ -105,15 +107,25 @@ public class TbCopyAttributesToEntityViewNode implements TbNode {
List<String> filteredAttributes = List<String> filteredAttributes =
attributes.stream().filter(attr -> attributeContainsInEntityView(scope, attr, entityView)).collect(Collectors.toList()); attributes.stream().filter(attr -> attributeContainsInEntityView(scope, attr, entityView)).collect(Collectors.toList());
if (!filteredAttributes.isEmpty()) { if (!filteredAttributes.isEmpty()) {
ctx.getTelemetryService().deleteAndNotify(ctx.getTenantId(), entityView.getId(), scope, filteredAttributes, ctx.getTelemetryService().deleteAttributes(AttributesDeleteRequest.builder()
getFutureCallback(ctx, msg, entityView)); .tenantId(ctx.getTenantId())
.entityId(entityView.getId())
.scope(scope)
.keys(filteredAttributes)
.callback(getFutureCallback(ctx, msg, entityView))
.build());
} }
} else { } else {
Set<AttributeKvEntry> attributes = JsonConverter.convertToAttributes(JsonParser.parseString(msg.getData())); Set<AttributeKvEntry> attributes = JsonConverter.convertToAttributes(JsonParser.parseString(msg.getData()));
List<AttributeKvEntry> filteredAttributes = List<AttributeKvEntry> filteredAttributes =
attributes.stream().filter(attr -> attributeContainsInEntityView(scope, attr.getKey(), entityView)).collect(Collectors.toList()); attributes.stream().filter(attr -> attributeContainsInEntityView(scope, attr.getKey(), entityView)).collect(Collectors.toList());
ctx.getTelemetryService().saveAndNotify(ctx.getTenantId(), entityView.getId(), scope, filteredAttributes, ctx.getTelemetryService().saveAttributes(AttributesSaveRequest.builder()
getFutureCallback(ctx, msg, entityView)); .tenantId(ctx.getTenantId())
.entityId(entityView.getId())
.scope(scope)
.entries(filteredAttributes)
.callback(getFutureCallback(ctx, msg, entityView))
.build());
} }
} }
} }

32
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/math/TbMathNode.java

@ -19,16 +19,19 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.SettableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.objecthunter.exp4j.Expression; import net.objecthunter.exp4j.Expression;
import net.objecthunter.exp4j.ExpressionBuilder; import net.objecthunter.exp4j.ExpressionBuilder;
import org.springframework.util.ConcurrentReferenceHashMap; import org.springframework.util.ConcurrentReferenceHashMap;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.AttributesSaveRequest;
import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.RuleNode;
import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNode;
import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.rule.engine.api.TimeseriesSaveRequest;
import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.rule.engine.util.SemaphoreWithTbMsgQueue; import org.thingsboard.rule.engine.util.SemaphoreWithTbMsgQueue;
import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.AttributeScope;
@ -37,6 +40,7 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.DoubleDataEntry; import org.thingsboard.server.common.data.kv.DoubleDataEntry;
import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.KvEntry;
import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
@ -139,22 +143,36 @@ public class TbMathNode implements TbNode {
} }
private ListenableFuture<Void> saveTimeSeries(TbContext ctx, TbMsg msg, double result, TbMathResult mathResultDef) { private ListenableFuture<Void> saveTimeSeries(TbContext ctx, TbMsg msg, double result, TbMathResult mathResultDef) {
final BasicTsKvEntry basicTsKvEntry = new BasicTsKvEntry(System.currentTimeMillis(), new DoubleDataEntry(mathResultDef.getKey(), result));
return ctx.getTelemetryService().saveAndNotify(ctx.getTenantId(), msg.getOriginator(), SettableFuture<Void> future = SettableFuture.create();
new BasicTsKvEntry(System.currentTimeMillis(), new DoubleDataEntry(mathResultDef.getKey(), result))); ctx.getTelemetryService().saveTimeseries(TimeseriesSaveRequest.builder()
.tenantId(ctx.getTenantId())
.entityId(msg.getOriginator())
.entry(basicTsKvEntry)
.future(future)
.build());
return future;
} }
private ListenableFuture<Void> saveAttribute(TbContext ctx, TbMsg msg, double result, TbMathResult mathResultDef) { private ListenableFuture<Void> saveAttribute(TbContext ctx, TbMsg msg, double result, TbMathResult mathResultDef) {
AttributeScope attributeScope = getAttributeScope(mathResultDef.getAttributeScope()); AttributeScope attributeScope = getAttributeScope(mathResultDef.getAttributeScope());
KvEntry kvEntry;
if (isIntegerResult(mathResultDef, config.getOperation())) { if (isIntegerResult(mathResultDef, config.getOperation())) {
var value = toIntValue(result); var value = toIntValue(result);
return ctx.getTelemetryService().saveAttrAndNotify( kvEntry = new LongDataEntry(mathResultDef.getKey(), value);
ctx.getTenantId(), msg.getOriginator(), attributeScope, mathResultDef.getKey(), value);
} else { } else {
var value = toDoubleValue(mathResultDef, result); var value = toDoubleValue(mathResultDef, result);
return ctx.getTelemetryService().saveAttrAndNotify( kvEntry = new DoubleDataEntry(mathResultDef.getKey(), value);
ctx.getTenantId(), msg.getOriginator(), attributeScope, mathResultDef.getKey(), value);
} }
SettableFuture<Void> future = SettableFuture.create();
ctx.getTelemetryService().saveAttributes(AttributesSaveRequest.builder()
.tenantId(ctx.getTenantId())
.entityId(msg.getOriginator())
.scope(attributeScope)
.entry(kvEntry)
.future(future)
.build());
return future;
} }
private boolean isIntegerResult(TbMathResult mathResultDef, TbRuleNodeMathFunctionType function) { private boolean isIntegerResult(TbMathResult mathResultDef, TbRuleNodeMathFunctionType function) {

31
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java

@ -17,11 +17,13 @@ package org.thingsboard.rule.engine.telemetry;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.common.util.DonAsynchron; import org.thingsboard.common.util.DonAsynchron;
import org.thingsboard.rule.engine.api.AttributesSaveRequest;
import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.RuleNode;
import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNode;
@ -56,10 +58,10 @@ import static org.thingsboard.server.common.data.msg.TbMsgType.POST_ATTRIBUTES_R
version = 2, version = 2,
nodeDescription = "Saves attributes data", nodeDescription = "Saves attributes data",
nodeDetails = "Saves entity attributes based on configurable scope parameter. Expects messages with 'POST_ATTRIBUTES_REQUEST' message type. " + nodeDetails = "Saves entity attributes based on configurable scope parameter. Expects messages with 'POST_ATTRIBUTES_REQUEST' message type. " +
"If upsert(update/insert) operation is completed successfully rule node will send the incoming message via <b>Success</b> chain, otherwise, <b>Failure</b> chain is used. " + "If upsert(update/insert) operation is completed successfully rule node will send the incoming message via <b>Success</b> chain, otherwise, <b>Failure</b> chain is used. " +
"Additionally if checkbox <b>Send attributes updated notification</b> is set to true, rule node will put the \"Attributes Updated\" " + "Additionally if checkbox <b>Send attributes updated notification</b> is set to true, rule node will put the \"Attributes Updated\" " +
"event for <b>SHARED_SCOPE</b> and <b>SERVER_SCOPE</b> attributes updates to the corresponding rule engine queue." + "event for <b>SHARED_SCOPE</b> and <b>SERVER_SCOPE</b> attributes updates to the corresponding rule engine queue." +
"Performance checkbox 'Save attributes only if the value changes' will skip attributes overwrites for values with no changes (avoid concurrent writes because this check is not transactional; will not update 'Last updated time' for skipped attributes).", "Performance checkbox 'Save attributes only if the value changes' will skip attributes overwrites for values with no changes (avoid concurrent writes because this check is not transactional; will not update 'Last updated time' for skipped attributes).",
uiResources = {"static/rulenode/rulenode-core-config.js"}, uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbActionNodeAttributesConfig", configDirective = "tbActionNodeAttributesConfig",
icon = "file_upload" icon = "file_upload"
@ -114,16 +116,17 @@ public class TbMsgAttributesNode implements TbNode {
ctx.tellSuccess(msg); ctx.tellSuccess(msg);
return; return;
} }
ctx.getTelemetryService().saveAndNotify( FutureCallback<Void> callback = sendAttributesUpdateNotification ?
ctx.getTenantId(), new AttributesUpdateNodeCallback(ctx, msg, scope.name(), attributes) :
msg.getOriginator(), new TelemetryNodeCallback(ctx, msg);
scope, ctx.getTelemetryService().saveAttributes(AttributesSaveRequest.builder()
attributes, .tenantId(ctx.getTenantId())
config.isNotifyDevice() || checkNotifyDeviceMdValue(msg.getMetaData().getValue(NOTIFY_DEVICE_METADATA_KEY)), .entityId(msg.getOriginator())
sendAttributesUpdateNotification ? .scope(scope)
new AttributesUpdateNodeCallback(ctx, msg, scope.name(), attributes) : .entries(attributes)
new TelemetryNodeCallback(ctx, msg) .notifyDevice(config.isNotifyDevice() || checkNotifyDeviceMdValue(msg.getMetaData().getValue(NOTIFY_DEVICE_METADATA_KEY)))
); .callback(callback)
.build());
} }
List<AttributeKvEntry> filterChangedAttr(List<AttributeKvEntry> currentAttributes, List<AttributeKvEntry> newAttributes) { List<AttributeKvEntry> filterChangedAttr(List<AttributeKvEntry> currentAttributes, List<AttributeKvEntry> newAttributes) {

19
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgDeleteAttributesNode.java

@ -16,6 +16,7 @@
package org.thingsboard.rule.engine.telemetry; package org.thingsboard.rule.engine.telemetry;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.rule.engine.api.AttributesDeleteRequest;
import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.RuleNode;
import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNode;
@ -70,16 +71,16 @@ public class TbMsgDeleteAttributesNode implements TbNode {
ctx.tellSuccess(msg); ctx.tellSuccess(msg);
} else { } else {
AttributeScope scope = getScope(msg.getMetaData().getValue(SCOPE)); AttributeScope scope = getScope(msg.getMetaData().getValue(SCOPE));
ctx.getTelemetryService().deleteAndNotify( ctx.getTelemetryService().deleteAttributes(AttributesDeleteRequest.builder()
ctx.getTenantId(), .tenantId(ctx.getTenantId())
msg.getOriginator(), .entityId(msg.getOriginator())
scope, .scope(scope)
keysToDelete, .keys(keysToDelete)
checkNotifyDevice(msg.getMetaData().getValue(NOTIFY_DEVICE_METADATA_KEY), scope), .notifyDevice(checkNotifyDevice(msg.getMetaData().getValue(NOTIFY_DEVICE_METADATA_KEY), scope))
config.isSendAttributesDeletedNotification() ? .callback(config.isSendAttributesDeletedNotification() ?
new AttributesDeleteNodeCallback(ctx, msg, scope.name(), keysToDelete) : new AttributesDeleteNodeCallback(ctx, msg, scope.name(), keysToDelete) :
new TelemetryNodeCallback(ctx, msg) new TelemetryNodeCallback(ctx, msg))
); .build());
} }
} }

15
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java

@ -22,6 +22,7 @@ import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNode;
import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.rule.engine.api.TimeseriesSaveRequest;
import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.server.common.adaptor.JsonConverter; import org.thingsboard.server.common.adaptor.JsonConverter;
import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.StringUtils;
@ -104,11 +105,15 @@ public class TbMsgTimeseriesNode implements TbNode {
if (ttl == 0L) { if (ttl == 0L) {
ttl = tenantProfileDefaultStorageTtl; ttl = tenantProfileDefaultStorageTtl;
} }
if (config.isSkipLatestPersistence()) { ctx.getTelemetryService().saveTimeseries(TimeseriesSaveRequest.builder()
ctx.getTelemetryService().saveWithoutLatestAndNotify(ctx.getTenantId(), msg.getCustomerId(), msg.getOriginator(), tsKvEntryList, ttl, new TelemetryNodeCallback(ctx, msg)); .tenantId(ctx.getTenantId())
} else { .customerId(msg.getCustomerId())
ctx.getTelemetryService().saveAndNotify(ctx.getTenantId(), msg.getCustomerId(), msg.getOriginator(), tsKvEntryList, ttl, new TelemetryNodeCallback(ctx, msg)); .entityId(msg.getOriginator())
} .entries(tsKvEntryList)
.ttl(ttl)
.saveLatest(!config.isSkipLatestPersistence())
.callback(new TelemetryNodeCallback(ctx, msg))
.build());
} }
public static long computeTs(TbMsg msg, boolean ignoreMetadataTs) { public static long computeTs(TbMsg msg, boolean ignoreMetadataTs) {

59
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNodeTest.java

@ -15,7 +15,6 @@
*/ */
package org.thingsboard.rule.engine.action; package org.thingsboard.rule.engine.action;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -26,6 +25,8 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.AttributesDeleteRequest;
import org.thingsboard.rule.engine.api.AttributesSaveRequest;
import org.thingsboard.rule.engine.api.EmptyNodeConfiguration; import org.thingsboard.rule.engine.api.EmptyNodeConfiguration;
import org.thingsboard.rule.engine.api.RuleEngineTelemetryService; import org.thingsboard.rule.engine.api.RuleEngineTelemetryService;
import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbContext;
@ -37,7 +38,6 @@ import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityViewId; import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.msg.TbMsgType;
import org.thingsboard.server.common.data.msg.TbNodeConnectionType; import org.thingsboard.server.common.data.msg.TbNodeConnectionType;
import org.thingsboard.server.common.data.objects.AttributesEntityView; import org.thingsboard.server.common.data.objects.AttributesEntityView;
@ -55,7 +55,7 @@ import java.util.UUID;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.assertArg;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
@ -113,10 +113,10 @@ public class TbCopyAttributesToEntityViewNodeTest {
mockEntityViewLookup(entityView); mockEntityViewLookup(entityView);
when(ctxMock.getTelemetryService()).thenReturn(telemetryServiceMock); when(ctxMock.getTelemetryService()).thenReturn(telemetryServiceMock);
doAnswer(invocation -> { doAnswer(invocation -> {
FutureCallback<Void> callback = invocation.getArgument(4); AttributesSaveRequest request = invocation.getArgument(0);
callback.onSuccess(null); request.getCallback().onSuccess(null);
return null; return null;
}).when(telemetryServiceMock).saveAndNotify(any(), any(), any(AttributeScope.class), anyList(), any(FutureCallback.class)); }).when(telemetryServiceMock).saveAttributes(any(AttributesSaveRequest.class));
TbMsg newMsg = TbMsg.newMsg(msg, msg.getQueueName(), msg.getRuleChainId(), msg.getRuleNodeId()); TbMsg newMsg = TbMsg.newMsg(msg, msg.getQueueName(), msg.getRuleChainId(), msg.getRuleNodeId());
// TODO: use newMsg() with any(TbMsgType.class), replace in other tests as well. // TODO: use newMsg() with any(TbMsgType.class), replace in other tests as well.
doAnswer(invocation -> newMsg).when(ctxMock).newMsg(any(), any(String.class), any(), any(), any(), any()); doAnswer(invocation -> newMsg).when(ctxMock).newMsg(any(), any(String.class), any(), any(), any(), any());
@ -124,13 +124,15 @@ public class TbCopyAttributesToEntityViewNodeTest {
node.onMsg(ctxMock, msg); node.onMsg(ctxMock, msg);
verify(entityViewServiceMock).findEntityViewsByTenantIdAndEntityIdAsync(eq(TENANT_ID), eq(DEVICE_ID)); verify(entityViewServiceMock).findEntityViewsByTenantIdAndEntityIdAsync(eq(TENANT_ID), eq(DEVICE_ID));
ArgumentCaptor<List<AttributeKvEntry>> filteredAttributesCaptor = ArgumentCaptor.forClass(List.class); verify(telemetryServiceMock).saveAttributes(assertArg(request -> {
verify(telemetryServiceMock).saveAndNotify(eq(TENANT_ID), eq(ENTITY_VIEW_ID), eq(AttributeScope.CLIENT_SCOPE), assertThat(request.getTenantId()).isEqualTo(TENANT_ID);
filteredAttributesCaptor.capture(), any(FutureCallback.class)); assertThat(request.getEntityId()).isEqualTo(ENTITY_VIEW_ID);
List<AttributeKvEntry> filteredAttributesCaptorValue = filteredAttributesCaptor.getValue(); assertThat(request.getScope()).isEqualTo(AttributeScope.CLIENT_SCOPE);
assertThat(filteredAttributesCaptorValue.size()).isEqualTo(1);
assertThat(filteredAttributesCaptorValue.get(0).getKey()).isEqualTo("clientAttribute1"); assertThat(request.getEntries().size()).isEqualTo(1);
assertThat(filteredAttributesCaptorValue.get(0).getValue()).isEqualTo(100L); assertThat(request.getEntries().get(0).getKey()).isEqualTo("clientAttribute1");
assertThat(request.getEntries().get(0).getValue()).isEqualTo(100L);
}));
verify(ctxMock).ack(eq(msg)); verify(ctxMock).ack(eq(msg));
verify(ctxMock).enqueueForTellNext(eq(newMsg), eq(TbNodeConnectionType.SUCCESS)); verify(ctxMock).enqueueForTellNext(eq(newMsg), eq(TbNodeConnectionType.SUCCESS));
verifyNoMoreInteractions(ctxMock, entityViewServiceMock, telemetryServiceMock); verifyNoMoreInteractions(ctxMock, entityViewServiceMock, telemetryServiceMock);
@ -147,21 +149,23 @@ public class TbCopyAttributesToEntityViewNodeTest {
mockEntityViewLookup(entityView); mockEntityViewLookup(entityView);
when(ctxMock.getTelemetryService()).thenReturn(telemetryServiceMock); when(ctxMock.getTelemetryService()).thenReturn(telemetryServiceMock);
doAnswer(invocation -> { doAnswer(invocation -> {
FutureCallback<Void> callback = invocation.getArgument(4); AttributesDeleteRequest request = invocation.getArgument(0);
callback.onSuccess(null); request.getCallback().onSuccess(null);
return null; return null;
}).when(telemetryServiceMock).deleteAndNotify(any(), any(), any(AttributeScope.class), anyList(), any(FutureCallback.class)); }).when(telemetryServiceMock).deleteAttributes(any());
TbMsg newMsg = TbMsg.newMsg(msg, msg.getQueueName(), msg.getRuleChainId(), msg.getRuleNodeId()); TbMsg newMsg = TbMsg.newMsg(msg, msg.getQueueName(), msg.getRuleChainId(), msg.getRuleNodeId());
doAnswer(invocation -> newMsg).when(ctxMock).newMsg(any(), any(String.class), any(), any(), any(), any()); doAnswer(invocation -> newMsg).when(ctxMock).newMsg(any(), any(String.class), any(), any(), any(), any());
node.onMsg(ctxMock, msg); node.onMsg(ctxMock, msg);
verify(entityViewServiceMock).findEntityViewsByTenantIdAndEntityIdAsync(eq(TENANT_ID), eq(DEVICE_ID)); verify(entityViewServiceMock).findEntityViewsByTenantIdAndEntityIdAsync(eq(TENANT_ID), eq(DEVICE_ID));
ArgumentCaptor<List<String>> filteredAttributesCaptor = ArgumentCaptor.forClass(List.class); verify(telemetryServiceMock).deleteAttributes(assertArg(request -> {
verify(telemetryServiceMock).deleteAndNotify(eq(TENANT_ID), eq(ENTITY_VIEW_ID), eq(AttributeScope.SERVER_SCOPE), filteredAttributesCaptor.capture(), any(FutureCallback.class)); assertThat(request.getTenantId()).isEqualTo(TENANT_ID);
List<String> filteredAttributesCaptorValue = filteredAttributesCaptor.getValue(); assertThat(request.getEntityId()).isEqualTo(ENTITY_VIEW_ID);
assertThat(filteredAttributesCaptorValue.size()).isEqualTo(1); assertThat(request.getScope()).isEqualTo(AttributeScope.SERVER_SCOPE);
assertThat(filteredAttributesCaptorValue.get(0)).isEqualTo("serverAttribute1"); assertThat(request.getKeys().size()).isEqualTo(1);
assertThat(request.getKeys().get(0)).isEqualTo("serverAttribute1");
}));
verify(ctxMock).ack(eq(msg)); verify(ctxMock).ack(eq(msg));
verify(ctxMock).enqueueForTellNext(eq(newMsg), eq(TbNodeConnectionType.SUCCESS)); verify(ctxMock).enqueueForTellNext(eq(newMsg), eq(TbNodeConnectionType.SUCCESS));
verifyNoMoreInteractions(ctxMock, entityViewServiceMock, telemetryServiceMock); verifyNoMoreInteractions(ctxMock, entityViewServiceMock, telemetryServiceMock);
@ -195,17 +199,22 @@ public class TbCopyAttributesToEntityViewNodeTest {
mockEntityViewLookup(entityView); mockEntityViewLookup(entityView);
when(ctxMock.getTelemetryService()).thenReturn(telemetryServiceMock); when(ctxMock.getTelemetryService()).thenReturn(telemetryServiceMock);
doAnswer(invocation -> { doAnswer(invocation -> {
FutureCallback<Void> callback = invocation.getArgument(4); AttributesSaveRequest request = invocation.getArgument(0);
callback.onSuccess(null); request.getCallback().onSuccess(null);
return null; return null;
}).when(telemetryServiceMock).saveAndNotify(any(), any(), any(AttributeScope.class), anyList(), any(FutureCallback.class)); }).when(telemetryServiceMock).saveAttributes(any(AttributesSaveRequest.class));
TbMsg newMsg = TbMsg.newMsg(msg, msg.getQueueName(), msg.getRuleChainId(), msg.getRuleNodeId()); TbMsg newMsg = TbMsg.newMsg(msg, msg.getQueueName(), msg.getRuleChainId(), msg.getRuleNodeId());
doAnswer(invocation -> newMsg).when(ctxMock).newMsg(any(), any(String.class), any(), any(), any(), any()); doAnswer(invocation -> newMsg).when(ctxMock).newMsg(any(), any(String.class), any(), any(), any(), any());
node.onMsg(ctxMock, msg); node.onMsg(ctxMock, msg);
verify(entityViewServiceMock).findEntityViewsByTenantIdAndEntityIdAsync(eq(TENANT_ID), eq(DEVICE_ID)); verify(entityViewServiceMock).findEntityViewsByTenantIdAndEntityIdAsync(eq(TENANT_ID), eq(DEVICE_ID));
verify(telemetryServiceMock).saveAndNotify(eq(TENANT_ID), eq(ENTITY_VIEW_ID), eq(AttributeScope.CLIENT_SCOPE), eq(Collections.emptyList()), any(FutureCallback.class)); verify(telemetryServiceMock).saveAttributes(assertArg(request -> {
assertThat(request.getTenantId()).isEqualTo(TENANT_ID);
assertThat(request.getEntityId()).isEqualTo(ENTITY_VIEW_ID);
assertThat(request.getScope()).isEqualTo(AttributeScope.CLIENT_SCOPE);
assertThat(request.getEntries().isEmpty()).isTrue();
}));
verify(ctxMock).ack(eq(msg)); verify(ctxMock).ack(eq(msg));
verify(ctxMock).enqueueForTellNext(eq(newMsg), eq(TbNodeConnectionType.SUCCESS)); verify(ctxMock).enqueueForTellNext(eq(newMsg), eq(TbNodeConnectionType.SUCCESS));
verifyNoMoreInteractions(ctxMock, entityViewServiceMock, telemetryServiceMock); verifyNoMoreInteractions(ctxMock, entityViewServiceMock, telemetryServiceMock);

47
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/math/TbMathNodeTest.java

@ -30,14 +30,17 @@ import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.mockito.ThrowingConsumer;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.verification.Timeout; import org.mockito.verification.Timeout;
import org.thingsboard.common.util.AbstractListeningExecutor; import org.thingsboard.common.util.AbstractListeningExecutor;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.AttributesSaveRequest;
import org.thingsboard.rule.engine.api.RuleEngineTelemetryService; import org.thingsboard.rule.engine.api.RuleEngineTelemetryService;
import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.rule.engine.api.TimeseriesSaveRequest;
import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
@ -46,8 +49,8 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.DoubleDataEntry; import org.thingsboard.server.common.data.kv.DoubleDataEntry;
import org.thingsboard.server.common.data.kv.KvEntry;
import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.msg.TbMsgType;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.TbMsgMetaData;
@ -70,13 +73,13 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyDouble;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.assertArg;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.willAnswer; import static org.mockito.BDDMockito.willAnswer;
import static org.mockito.BDDMockito.willReturn; import static org.mockito.BDDMockito.willReturn;
import static org.mockito.BDDMockito.willThrow; import static org.mockito.BDDMockito.willThrow;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
@ -434,15 +437,19 @@ public class TbMathNodeTest {
); );
TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, TbMsgMetaData.EMPTY, JacksonUtil.newObjectNode().put("a", 5).toString()); TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, TbMsgMetaData.EMPTY, JacksonUtil.newObjectNode().put("a", 5).toString());
doAnswer(invocation -> {
when(telemetryService.saveAttrAndNotify(any(), any(), any(AttributeScope.class), anyString(), anyDouble())) AttributesSaveRequest request = invocation.getArgument(0);
.thenReturn(Futures.immediateFuture(null)); request.getCallback().onSuccess(null);
return null;
}).when(telemetryService).saveAttributes(any(AttributesSaveRequest.class));
node.onMsg(ctx, msg); node.onMsg(ctx, msg);
ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class); ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
verify(ctx, timeout(TIMEOUT)).tellSuccess(msgCaptor.capture()); verify(ctx, timeout(TIMEOUT)).tellSuccess(msgCaptor.capture());
verify(telemetryService, times(1)).saveAttrAndNotify(any(), any(), any(AttributeScope.class), anyString(), anyDouble()); verify(telemetryService, times(1)).saveAttributes(assertArg(request -> {
assertThat(request.getEntries()).singleElement().extracting(KvEntry::getValue).isInstanceOf(Double.class);
}));
TbMsg resultMsg = msgCaptor.getValue(); TbMsg resultMsg = msgCaptor.getValue();
assertNotNull(resultMsg); assertNotNull(resultMsg);
@ -460,14 +467,20 @@ public class TbMathNodeTest {
); );
TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, TbMsgMetaData.EMPTY, JacksonUtil.newObjectNode().put("a", 5).toString()); TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, TbMsgMetaData.EMPTY, JacksonUtil.newObjectNode().put("a", 5).toString());
when(telemetryService.saveAndNotify(any(), any(), any(TsKvEntry.class))) doAnswer(invocation -> {
.thenReturn(Futures.immediateFuture(null)); TimeseriesSaveRequest request = invocation.getArgument(0);
request.getCallback().onSuccess(null);
return null;
}).when(telemetryService).saveTimeseries(any(TimeseriesSaveRequest.class));
node.onMsg(ctx, msg); node.onMsg(ctx, msg);
ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class); ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
verify(ctx, timeout(TIMEOUT)).tellSuccess(msgCaptor.capture()); verify(ctx, timeout(TIMEOUT)).tellSuccess(msgCaptor.capture());
verify(telemetryService, times(1)).saveAndNotify(any(), any(), any(TsKvEntry.class)); verify(telemetryService, times(1)).saveTimeseries(assertArg(request -> {
assertThat(request.getEntries()).size().isOne();
assertThat(request.isSaveLatest()).isTrue();
}));
TbMsg resultMsg = msgCaptor.getValue(); TbMsg resultMsg = msgCaptor.getValue();
assertNotNull(resultMsg); assertNotNull(resultMsg);
@ -485,14 +498,20 @@ public class TbMathNodeTest {
); );
TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, TbMsgMetaData.EMPTY, JacksonUtil.newObjectNode().put("a", 5).toString()); TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, TbMsgMetaData.EMPTY, JacksonUtil.newObjectNode().put("a", 5).toString());
when(telemetryService.saveAndNotify(any(), any(), any(TsKvEntry.class))) doAnswer(invocation -> {
.thenReturn(Futures.immediateFuture(null)); TimeseriesSaveRequest request = invocation.getArgument(0);
request.getCallback().onSuccess(null);
return null;
}).when(telemetryService).saveTimeseries(any(TimeseriesSaveRequest.class));
node.onMsg(ctx, msg); node.onMsg(ctx, msg);
ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class); ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
verify(ctx, timeout(TIMEOUT)).tellSuccess(msgCaptor.capture()); verify(ctx, timeout(TIMEOUT)).tellSuccess(msgCaptor.capture());
verify(telemetryService, times(1)).saveAndNotify(any(), any(), any(TsKvEntry.class)); verify(telemetryService, times(1)).saveTimeseries(assertArg(request -> {
assertThat(request.getEntries()).size().isOne();
assertThat(request.isSaveLatest()).isTrue();
}));
TbMsg resultMsg = msgCaptor.getValue(); TbMsg resultMsg = msgCaptor.getValue();
assertNotNull(resultMsg); assertNotNull(resultMsg);
@ -550,7 +569,7 @@ public class TbMathNodeTest {
new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "a") new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "a")
); );
TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_ARRAY); TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, TbMsgMetaData.EMPTY, TbMsg.EMPTY_JSON_ARRAY);
node.onMsg(ctx, msg); node.onMsg(ctx, msg);
ArgumentCaptor<Throwable> tCaptor = ArgumentCaptor.forClass(Throwable.class); ArgumentCaptor<Throwable> tCaptor = ArgumentCaptor.forClass(Throwable.class);

18
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeTest.java

@ -22,7 +22,6 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.ArgumentCaptor;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.AbstractRuleNodeUpgradeTest; import org.thingsboard.rule.engine.AbstractRuleNodeUpgradeTest;
import org.thingsboard.rule.engine.api.RuleEngineTelemetryService; import org.thingsboard.rule.engine.api.RuleEngineTelemetryService;
@ -53,6 +52,7 @@ import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.assertArg;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.willCallRealMethod; import static org.mockito.BDDMockito.willCallRealMethod;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
@ -169,17 +169,15 @@ class TbMsgAttributesNodeTest extends AbstractRuleNodeUpgradeTest {
node.saveAttr(testAttrList, ctxMock, testTbMsg, AttributeScope.SHARED_SCOPE, false); node.saveAttr(testAttrList, ctxMock, testTbMsg, AttributeScope.SHARED_SCOPE, false);
ArgumentCaptor<Boolean> notifyDeviceCaptor = ArgumentCaptor.forClass(Boolean.class); verify(telemetryServiceMock, times(1)).saveAttributes(assertArg(request -> {
assertThat(request.getTenantId()).isEqualTo(tenantId);
verify(telemetryServiceMock, times(1)).saveAndNotify( assertThat(request.getEntityId()).isEqualTo(deviceId);
eq(tenantId), eq(deviceId), eq(AttributeScope.SHARED_SCOPE), assertThat(request.getScope()).isEqualTo(AttributeScope.SHARED_SCOPE);
eq(testAttrList), notifyDeviceCaptor.capture(), any() assertThat(request.getEntries()).isEqualTo(testAttrList);
); assertThat(request.isNotifyDevice()).isEqualTo(expectedArgumentValue);
boolean notifyDevice = notifyDeviceCaptor.getValue(); }));
assertThat(notifyDevice).isEqualTo(expectedArgumentValue);
} }
// Rule nodes upgrade // Rule nodes upgrade
private static Stream<Arguments> givenFromVersionAndConfig_whenUpgrade_thenVerifyHasChangesAndConfig() { private static Stream<Arguments> givenFromVersionAndConfig_whenUpgrade_thenVerifyHasChangesAndConfig() {
return Stream.of( return Stream.of(

16
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgDeleteAttributesNodeTest.java

@ -21,11 +21,11 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.AttributesDeleteRequest;
import org.thingsboard.rule.engine.api.RuleEngineTelemetryService; import org.thingsboard.rule.engine.api.RuleEngineTelemetryService;
import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.msg.TbMsgType;
@ -41,10 +41,9 @@ import java.util.function.Consumer;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.assertArg;
import static org.mockito.BDDMockito.willAnswer; import static org.mockito.BDDMockito.willAnswer;
import static org.mockito.BDDMockito.willReturn; import static org.mockito.BDDMockito.willReturn;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
@ -78,11 +77,10 @@ public class TbMsgDeleteAttributesNodeTest {
willReturn(telemetryService).given(ctx).getTelemetryService(); willReturn(telemetryService).given(ctx).getTelemetryService();
willAnswer(invocation -> { willAnswer(invocation -> {
TelemetryNodeCallback callBack = invocation.getArgument(5); AttributesDeleteRequest request = invocation.getArgument(0);
callBack.onSuccess(null); request.getCallback().onSuccess(null);
return null; return null;
}).given(telemetryService).deleteAndNotify( }).given(telemetryService).deleteAttributes(any());
any(), any(), any(AttributeScope.class), anyList(), anyBoolean(), any());
} }
@AfterEach @AfterEach
@ -153,6 +151,8 @@ public class TbMsgDeleteAttributesNodeTest {
} }
verify(ctx, times(1)).tellSuccess(newMsgCaptor.capture()); verify(ctx, times(1)).tellSuccess(newMsgCaptor.capture());
verify(ctx, never()).tellFailure(any(), any()); verify(ctx, never()).tellFailure(any(), any());
verify(telemetryService, times(1)).deleteAndNotify(any(), any(), any(AttributeScope.class), anyList(), eq(notifyDevice || notifyDeviceMetadata), any()); verify(telemetryService, times(1)).deleteAttributes(assertArg(request -> {
assertThat(request.isNotifyDevice()).isEqualTo(notifyDevice || notifyDeviceMetadata);
}));
} }
} }

54
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNodeTest.java

@ -25,12 +25,14 @@ import org.junit.jupiter.params.provider.EnumSource;
import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.ThrowingConsumer;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.RuleEngineTelemetryService; import org.thingsboard.rule.engine.api.RuleEngineTelemetryService;
import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.rule.engine.api.TimeseriesSaveRequest;
import org.thingsboard.server.common.adaptor.JsonConverter; import org.thingsboard.server.common.adaptor.JsonConverter;
import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
@ -54,10 +56,8 @@ import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.assertArg;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyNoMoreInteractions;
@ -127,19 +127,23 @@ public class TbMsgTimeseriesNodeTest {
when(ctxMock.getTelemetryService()).thenReturn(telemetryServiceMock); when(ctxMock.getTelemetryService()).thenReturn(telemetryServiceMock);
when(ctxMock.getTenantId()).thenReturn(TENANT_ID); when(ctxMock.getTenantId()).thenReturn(TENANT_ID);
doAnswer(invocation -> { doAnswer(invocation -> {
TelemetryNodeCallback callback = invocation.getArgument(5); TimeseriesSaveRequest request = invocation.getArgument(0);
callback.onSuccess(null); request.getCallback().onSuccess(null);
return null; return null;
}).when(telemetryServiceMock).saveAndNotify(any(), any(), any(), anyList(), anyLong(), any()); }).when(telemetryServiceMock).saveTimeseries(any(TimeseriesSaveRequest.class));
node.onMsg(ctxMock, msg); node.onMsg(ctxMock, msg);
List<TsKvEntry> expectedList = getTsKvEntriesListWithTs(data, System.currentTimeMillis()); List<TsKvEntry> expectedList = getTsKvEntriesListWithTs(data, System.currentTimeMillis());
ArgumentCaptor<List<TsKvEntry>> entryListCaptor = ArgumentCaptor.forClass(List.class); verify(telemetryServiceMock).saveTimeseries(assertArg(request -> {
verify(telemetryServiceMock).saveAndNotify(eq(TENANT_ID), isNull(), eq(DEVICE_ID), entryListCaptor.capture(), assertThat(request.getTenantId()).isEqualTo(TENANT_ID);
eq(tenantProfileDefaultStorageTtl), any(TelemetryNodeCallback.class)); assertThat(request.getCustomerId()).isNull();
assertThat(entryListCaptor.getValue()).usingRecursiveFieldByFieldElementComparatorIgnoringFields("ts") assertThat(request.getEntityId()).isEqualTo(DEVICE_ID);
.containsExactlyElementsOf(expectedList); assertThat(request.getEntries()).usingRecursiveFieldByFieldElementComparatorIgnoringFields("ts").containsExactlyElementsOf(expectedList);
assertThat(request.getTtl()).isEqualTo(tenantProfileDefaultStorageTtl);
assertThat(request.isSaveLatest()).isTrue();
assertThat(request.getCallback()).isInstanceOf(TelemetryNodeCallback.class);
}));
verify(ctxMock).tellSuccess(msg); verify(ctxMock).tellSuccess(msg);
verifyNoMoreInteractions(ctxMock, telemetryServiceMock); verifyNoMoreInteractions(ctxMock, telemetryServiceMock);
} }
@ -164,18 +168,23 @@ public class TbMsgTimeseriesNodeTest {
when(ctxMock.getTelemetryService()).thenReturn(telemetryServiceMock); when(ctxMock.getTelemetryService()).thenReturn(telemetryServiceMock);
when(ctxMock.getTenantId()).thenReturn(TENANT_ID); when(ctxMock.getTenantId()).thenReturn(TENANT_ID);
doAnswer(invocation -> { doAnswer(invocation -> {
TelemetryNodeCallback callback = invocation.getArgument(5); TimeseriesSaveRequest request = invocation.getArgument(0);
callback.onSuccess(null); request.getCallback().onSuccess(null);
return null; return null;
}).when(telemetryServiceMock).saveWithoutLatestAndNotify(any(), any(), any(), anyList(), anyLong(), any()); }).when(telemetryServiceMock).saveTimeseries(any(TimeseriesSaveRequest.class));
node.onMsg(ctxMock, msg); node.onMsg(ctxMock, msg);
List<TsKvEntry> expectedList = getTsKvEntriesListWithTs(data, ts); List<TsKvEntry> expectedList = getTsKvEntriesListWithTs(data, ts);
ArgumentCaptor<List<TsKvEntry>> entryListCaptor = ArgumentCaptor.forClass(List.class); verify(telemetryServiceMock).saveTimeseries(assertArg(request -> {
verify(telemetryServiceMock).saveWithoutLatestAndNotify( assertThat(request.getTenantId()).isEqualTo(TENANT_ID);
eq(TENANT_ID), isNull(), eq(DEVICE_ID), entryListCaptor.capture(), eq(ttlFromConfig), any(TelemetryNodeCallback.class)); assertThat(request.getCustomerId()).isNull();
assertThat(entryListCaptor.getValue()).containsExactlyElementsOf(expectedList); assertThat(request.getEntityId()).isEqualTo(DEVICE_ID);
assertThat(request.getEntries()).containsExactlyElementsOf(expectedList);
assertThat(request.getTtl()).isEqualTo(ttlFromConfig);
assertThat(request.isSaveLatest()).isFalse();
assertThat(request.getCallback()).isInstanceOf(TelemetryNodeCallback.class);
}));
verify(ctxMock).tellSuccess(msg); verify(ctxMock).tellSuccess(msg);
verifyNoMoreInteractions(ctxMock, telemetryServiceMock); verifyNoMoreInteractions(ctxMock, telemetryServiceMock);
} }
@ -200,7 +209,14 @@ public class TbMsgTimeseriesNodeTest {
TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DEVICE_ID, metadata, data); TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, DEVICE_ID, metadata, data);
node.onMsg(ctxMock, msg); node.onMsg(ctxMock, msg);
verify(telemetryServiceMock).saveAndNotify(eq(TENANT_ID), isNull(), eq(DEVICE_ID), anyList(), eq(expectedTtl), any(TelemetryNodeCallback.class)); verify(telemetryServiceMock).saveTimeseries(assertArg(request -> {
assertThat(request.getTenantId()).isEqualTo(TENANT_ID);
assertThat(request.getCustomerId()).isNull();
assertThat(request.getEntityId()).isEqualTo(DEVICE_ID);
assertThat(request.getTtl()).isEqualTo(expectedTtl);
assertThat(request.isSaveLatest()).isTrue();
assertThat(request.getCallback()).isInstanceOf(TelemetryNodeCallback.class);
}));
} }
private static Stream<Arguments> givenTtlFromConfigAndTtlFromMd_whenOnMsg_thenVerifyTtl() { private static Stream<Arguments> givenTtlFromConfigAndTtlFromMd_whenOnMsg_thenVerifyTtl() {

Loading…
Cancel
Save