Browse Source

Merge remote-tracking branch 'upstream/rc' into AD/bug-fix/scada-bad-xml

pull/13110/head
Artem Dzhereleiko 1 year ago
parent
commit
9bcdc86d09
  1. 10
      application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityMessageProcessor.java
  2. 16
      application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerMessageProcessor.java
  3. 5
      application/src/main/java/org/thingsboard/server/controller/AuthController.java
  4. 23
      application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java
  5. 12
      application/src/main/java/org/thingsboard/server/controller/EventController.java
  6. 3
      application/src/main/java/org/thingsboard/server/service/cf/DefaultCalculatedFieldCache.java
  7. 3
      application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java
  8. 88
      application/src/main/java/org/thingsboard/server/service/edge/EdgeMsgConstructorUtils.java
  9. 12
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java
  10. 6
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java
  11. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/EdgeProcessor.java
  12. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessor.java
  13. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/comment/AlarmCommentEdgeProcessor.java
  14. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessor.java
  15. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/profile/AssetProfileEdgeProcessor.java
  16. 28
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/customer/CustomerEdgeProcessor.java
  17. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/DashboardEdgeProcessor.java
  18. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java
  19. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/DeviceProfileEdgeProcessor.java
  20. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/edge/EdgeEntityProcessor.java
  21. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/EntityViewEdgeProcessor.java
  22. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/notification/NotificationRuleEdgeProcessor.java
  23. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/notification/NotificationTargetEdgeProcessor.java
  24. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/notification/NotificationTemplateEdgeProcessor.java
  25. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/oauth2/DomainEdgeProcessor.java
  26. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/oauth2/OAuth2ClientEdgeProcessor.java
  27. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/ota/OtaPackageEdgeProcessor.java
  28. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/queue/QueueEdgeProcessor.java
  29. 4
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationEdgeProcessor.java
  30. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/ResourceEdgeProcessor.java
  31. 5
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/RuleChainEdgeProcessor.java
  32. 5
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/RuleChainMetadataEdgeProcessor.java
  33. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/settings/AdminSettingsEdgeProcessor.java
  34. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/tenant/TenantEdgeProcessor.java
  35. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/tenant/TenantProfileEdgeProcessor.java
  36. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/user/UserEdgeProcessor.java
  37. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/widget/WidgetBundleEdgeProcessor.java
  38. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/widget/WidgetTypeEdgeProcessor.java
  39. 1
      application/src/main/java/org/thingsboard/server/service/edge/rpc/utils/EdgeVersionUtils.java
  40. 44
      application/src/main/java/org/thingsboard/server/service/edqs/EdqsSyncService.java
  41. 2
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCalculatedFieldConsumerService.java
  42. 14
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java
  43. 2
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java
  44. 26
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbEdgeConsumerService.java
  45. 2
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java
  46. 6
      application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java
  47. 19
      application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractPartitionBasedConsumerService.java
  48. 26
      application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java
  49. 2
      application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java
  50. 62
      application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java
  51. 16
      application/src/main/resources/banner.txt
  52. 34
      application/src/main/resources/thingsboard.yml
  53. 9
      application/src/test/java/org/thingsboard/server/controller/CalculatedFieldControllerTest.java
  54. 8
      application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java
  55. 4
      application/src/test/java/org/thingsboard/server/controller/EdgeControllerTest.java
  56. 11
      application/src/test/java/org/thingsboard/server/controller/EntityQueryControllerTest.java
  57. 14
      application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java
  58. 11
      application/src/test/java/org/thingsboard/server/controller/UserControllerTest.java
  59. 29
      application/src/test/java/org/thingsboard/server/controller/WebsocketApiTest.java
  60. 4
      application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java
  61. 14
      application/src/test/java/org/thingsboard/server/edge/TenantProfileEdgeTest.java
  62. 156
      application/src/test/java/org/thingsboard/server/service/edge/EdgeMsgConstructorUtilsTest.java
  63. 21
      application/src/test/java/org/thingsboard/server/service/entitiy/EntityServiceTest.java
  64. 2
      application/src/test/java/org/thingsboard/server/service/sync/vc/VersionControlTest.java
  65. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/customer/CustomerService.java
  66. 2
      common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java
  67. 4
      common/data/src/main/java/org/thingsboard/server/common/data/JavaSerDesUtil.java
  68. 26
      common/data/src/main/java/org/thingsboard/server/common/data/cf/CalculatedField.java
  69. 3
      common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/ScriptCalculatedFieldConfiguration.java
  70. 3
      common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/SimpleCalculatedFieldConfiguration.java
  71. 2
      common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/FieldsUtil.java
  72. 5
      common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/UserFields.java
  73. 3
      common/data/src/main/java/org/thingsboard/server/common/data/event/CalculatedFieldDebugEventFilter.java
  74. 2
      common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/EntityExportData.java
  75. 6
      common/edqs/src/main/java/org/thingsboard/server/edqs/data/dp/BoolDataPoint.java
  76. 3
      common/edqs/src/main/java/org/thingsboard/server/edqs/data/dp/CompressedStringDataPoint.java
  77. 6
      common/edqs/src/main/java/org/thingsboard/server/edqs/data/dp/DoubleDataPoint.java
  78. 2
      common/edqs/src/main/java/org/thingsboard/server/edqs/data/dp/JsonDataPoint.java
  79. 8
      common/edqs/src/main/java/org/thingsboard/server/edqs/data/dp/LongDataPoint.java
  80. 2
      common/edqs/src/main/java/org/thingsboard/server/edqs/data/dp/StringDataPoint.java
  81. 5
      common/edqs/src/main/java/org/thingsboard/server/edqs/repo/DefaultEdqsRepository.java
  82. 34
      common/edqs/src/main/java/org/thingsboard/server/edqs/repo/TenantRepo.java
  83. 92
      common/edqs/src/main/java/org/thingsboard/server/edqs/stats/DefaultEdqsStatsService.java
  84. 81
      common/edqs/src/main/java/org/thingsboard/server/edqs/stats/EdqsStatsService.java
  85. 45
      common/edqs/src/main/java/org/thingsboard/server/edqs/util/EdqsConverter.java
  86. 2
      common/edqs/src/main/java/org/thingsboard/server/edqs/util/RepositoryUtils.java
  87. 4
      common/queue/src/main/java/org/thingsboard/server/queue/discovery/TopicService.java
  88. 24
      common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaSettings.java
  89. 1
      common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueProducerProvider.java
  90. 41
      common/stats/src/main/java/org/thingsboard/server/common/stats/DummyEdqsStatsService.java
  91. 33
      common/stats/src/main/java/org/thingsboard/server/common/stats/EdqsStatsService.java
  92. 12
      common/stats/src/main/java/org/thingsboard/server/common/stats/StatsTimer.java
  93. 5
      common/util/pom.xml
  94. 78
      common/util/src/main/java/org/thingsboard/common/util/ExpressionFunctionsUtil.java
  95. 2
      common/util/src/main/java/org/thingsboard/common/util/TbBytePool.java
  96. 2
      common/util/src/main/java/org/thingsboard/common/util/TbStringPool.java
  97. 34
      common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/GitRepository.java
  98. 19
      dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java
  99. 6
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityServiceImpl.java
  100. 48
      dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java

10
application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityMessageProcessor.java

@ -107,7 +107,7 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM
public void process(CalculatedFieldStateRestoreMsg msg) {
CalculatedFieldId cfId = msg.getId().cfId();
log.info("[{}] [{}] Processing CF state restore msg.", msg.getId().entityId(), cfId);
log.debug("[{}] [{}] Processing CF state restore msg.", msg.getId().entityId(), cfId);
if (msg.getState() != null) {
states.put(cfId, msg.getState());
} else {
@ -116,10 +116,10 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM
}
public void process(EntityInitCalculatedFieldMsg msg) throws CalculatedFieldException {
log.info("[{}] Processing entity init CF msg.", msg.getCtx().getCfId());
log.debug("[{}] Processing entity init CF msg.", msg.getCtx().getCfId());
var ctx = msg.getCtx();
if (msg.isForceReinit()) {
log.info("Force reinitialization of CF: [{}].", ctx.getCfId());
log.debug("Force reinitialization of CF: [{}].", ctx.getCfId());
states.remove(ctx.getCfId());
}
try {
@ -138,7 +138,7 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM
}
public void process(CalculatedFieldEntityDeleteMsg msg) {
log.info("[{}] Processing CF entity delete msg.", msg.getEntityId());
log.debug("[{}] Processing CF entity delete msg.", msg.getEntityId());
if (this.entityId.equals(msg.getEntityId())) {
if (states.isEmpty()) {
msg.getCallback().onSuccess();
@ -244,7 +244,7 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM
private void processArgumentValuesUpdate(CalculatedFieldCtx ctx, List<CalculatedFieldId> cfIdList, MultipleTbCallback callback,
Map<String, ArgumentEntry> newArgValues, UUID tbMsgId, TbMsgType tbMsgType) throws CalculatedFieldException {
if (newArgValues.isEmpty()) {
log.info("[{}] No new argument values to process for CF.", ctx.getCfId());
log.debug("[{}] No new argument values to process for CF.", ctx.getCfId());
callback.onSuccess(CALLBACKS_PER_CF);
}
CalculatedFieldState state = states.get(ctx.getCfId());

16
application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldManagerMessageProcessor.java

@ -236,12 +236,12 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
private void onCfCreated(ComponentLifecycleMsg msg, TbCallback callback) throws CalculatedFieldException {
var cfId = new CalculatedFieldId(msg.getEntityId().getId());
if (calculatedFields.containsKey(cfId)) {
log.warn("[{}] CF was already initialized [{}]", tenantId, cfId);
log.debug("[{}] CF was already initialized [{}]", tenantId, cfId);
callback.onSuccess();
} else {
var cf = cfDaoService.findById(msg.getTenantId(), cfId);
if (cf == null) {
log.warn("[{}] Failed to lookup CF by id [{}]", tenantId, cfId);
log.debug("[{}] Failed to lookup CF by id [{}]", tenantId, cfId);
callback.onSuccess();
} else {
var cfCtx = new CalculatedFieldCtx(cf, systemContext.getTbelInvokeService(), systemContext.getApiLimitService());
@ -268,7 +268,7 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
} else {
var newCf = cfDaoService.findById(msg.getTenantId(), cfId);
if (newCf == null) {
log.warn("[{}] Failed to lookup CF by id [{}]", tenantId, cfId);
log.debug("[{}] Failed to lookup CF by id [{}]", tenantId, cfId);
callback.onSuccess();
} else {
var newCfCtx = new CalculatedFieldCtx(newCf, systemContext.getTbelInvokeService(), systemContext.getApiLimitService());
@ -313,7 +313,7 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
var cfId = new CalculatedFieldId(msg.getEntityId().getId());
var cfCtx = calculatedFields.remove(cfId);
if (cfCtx == null) {
log.warn("[{}] CF was already deleted [{}]", tenantId, cfId);
log.debug("[{}] CF was already deleted [{}]", tenantId, cfId);
callback.onSuccess();
} else {
entityIdCalculatedFields.get(cfCtx.getEntityId()).remove(cfCtx);
@ -390,9 +390,11 @@ public class CalculatedFieldManagerMessageProcessor extends AbstractContextAware
msg.getCallback().onSuccess();
}
} else {
log.debug("Pushing linked telemetry msg to specific actor [{}]", targetEntityId);
var newMsg = new EntityCalculatedFieldLinkedTelemetryMsg(tenantId, sourceEntityId, proto.getMsg(), cf, msg.getCallback());
getOrCreateActor(targetEntityId).tell(newMsg);
if (isMyPartition(targetEntityId, msg.getCallback())) {
log.debug("Pushing linked telemetry msg to specific actor [{}]", targetEntityId);
var newMsg = new EntityCalculatedFieldLinkedTelemetryMsg(tenantId, sourceEntityId, proto.getMsg(), cf, msg.getCallback());
getOrCreateActor(targetEntityId).tell(newMsg);
}
}
}
}

5
application/src/main/java/org/thingsboard/server/controller/AuthController.java

@ -228,10 +228,9 @@ public class AuthController extends BaseController {
@ApiOperation(value = "Reset password (resetPassword)",
notes = "Checks the password reset token and updates the password. " +
"If token is valid, returns the object that contains [JWT](https://jwt.io/) access and refresh tokens. " +
"If token is not valid, returns '400 Bad Request'.")
@PostMapping(value = "/noauth/resetPassword")
public JwtPair resetPassword(@Parameter(description = "Reset password request.")
public void resetPassword(@Parameter(description = "Reset password request.")
@RequestBody ResetPasswordRequest resetPasswordRequest,
HttpServletRequest request) throws ThingsboardException {
String resetToken = resetPasswordRequest.getResetToken();
@ -263,8 +262,6 @@ public class AuthController extends BaseController {
}
eventPublisher.publishEvent(new UserCredentialsInvalidationEvent(securityUser.getId()));
return tokenFactory.createTokenPair(securityUser);
} else {
throw new ThingsboardException("Invalid reset token!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
}

23
application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java

@ -189,6 +189,29 @@ public class ControllerConstants {
protected static final String EVENT_DEBUG_RULE_CHAIN_FILTER_OBJ = MARKDOWN_CODE_BLOCK_START + "{\n" +
" \"eventType\":\"DEBUG_RULE_CHAIN\",\n" + DEBUG_FILTER_OBJ + MARKDOWN_CODE_BLOCK_END;
protected static final String EVENT_DEBUG_CALCULATED_FIELD_FILTER_OBJ = MARKDOWN_CODE_BLOCK_START + "{\n" +
" \"eventType\":\"DEBUG_CALCULATED_FIELD\",\n" +
" \"server\":\"ip-172-31-24-152\",\n" +
" \"isError\":\"false\",\n" +
" \"errorStr\":\"Error Message\"\n" +
" \"entityId\":\"cf4b8741-f618-471f-ae08-d881ca7f9fe9\",\n" +
" \"msgId\":\"5cf7d3a0-aee7-40dd-a737-ade05528e7eb\",\n" +
" \"msgType\":\"POST_TELEMETRY_REQUEST\",\n" +
" \"arguments\":\"{\n" +
" \"x\": {\n" +
" \"ts\": 1739432016629,\n" +
" \"value\": 20\n" +
" },\n" +
" \"y\": {\n" +
" \"ts\": 1739429717656,\n" +
" \"value\": 12\n" +
" }\n" +
" }\",\n" +
" \"result\":\"{\n" +
" \"x + y\": 32\n" +
" }\",\n" +
"}" + MARKDOWN_CODE_BLOCK_END;
protected static final String IS_BOOTSTRAP_SERVER_PARAM_DESCRIPTION = "A Boolean value representing the Server SecurityInfo for future Bootstrap client mode settings. Values: 'true' for Bootstrap Server; 'false' for Lwm2m Server. ";
protected static final String DEVICE_WITH_DEVICE_CREDENTIALS_ACCESS_TOKEN_PARAM_DESCRIPTION =

12
application/src/main/java/org/thingsboard/server/controller/EventController.java

@ -47,6 +47,7 @@ import static org.thingsboard.server.controller.ControllerConstants.ENTITY_ID;
import static org.thingsboard.server.controller.ControllerConstants.ENTITY_ID_PARAM_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.ENTITY_TYPE;
import static org.thingsboard.server.controller.ControllerConstants.ENTITY_TYPE_PARAM_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.EVENT_DEBUG_CALCULATED_FIELD_FILTER_OBJ;
import static org.thingsboard.server.controller.ControllerConstants.EVENT_DEBUG_RULE_CHAIN_FILTER_OBJ;
import static org.thingsboard.server.controller.ControllerConstants.EVENT_DEBUG_RULE_NODE_FILTER_OBJ;
import static org.thingsboard.server.controller.ControllerConstants.EVENT_END_TIME_DESCRIPTION;
@ -70,7 +71,7 @@ import static org.thingsboard.server.controller.ControllerConstants.TENANT_ID_PA
public class EventController extends BaseController {
private static final String EVENT_FILTER_DEFINITION = "# Event Filter Definition" + NEW_LINE +
"5 different eventFilter objects could be set for different event types. " +
"6 different eventFilter objects could be set for different event types. " +
"The eventType field is required. Others are optional. If some of them are set, the filtering will be applied according to them. " +
"See the examples below for all the fields used for each event type filtering. " + NEW_LINE +
"Note," + NEW_LINE +
@ -98,6 +99,15 @@ public class EventController extends BaseController {
" * 'relationType' - string value representing the type of message routing;\n" +
" * 'entityId' - string value representing the entity id in the event body (originator of the message);\n" +
" * 'msgType' - string value representing the message type;\n" +
" * 'isError' - boolean value to filter the errors." + NEW_LINE +
"## Debug Calculated Field Event Filter" + NEW_LINE +
EVENT_DEBUG_CALCULATED_FIELD_FILTER_OBJ + NEW_LINE +
" * 'entityId' - string value representing the entity id in the event body;\n" +
" * 'entityType' - string value representing the entity type;\n" +
" * 'msgId' - string value representing the message id in the rule engine;\n" +
" * 'msgType' - string value representing the message type;\n" +
" * 'arguments' - string value representing the arguments that were used in the calculation performed;\n" +
" * 'result' - string value representing the result of a calculation;\n" +
" * 'isError' - boolean value to filter the errors." + NEW_LINE;
@Autowired

3
application/src/main/java/org/thingsboard/server/service/cf/DefaultCalculatedFieldCache.java

@ -144,6 +144,9 @@ public class DefaultCalculatedFieldCache implements CalculatedFieldCache {
calculatedFieldFetchLock.lock();
try {
CalculatedField calculatedField = calculatedFieldService.findById(tenantId, calculatedFieldId);
if (calculatedField == null) {
return;
}
EntityId cfEntityId = calculatedField.getEntityId();
calculatedFields.put(calculatedFieldId, calculatedField);

3
application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java

@ -44,6 +44,8 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.thingsboard.common.util.ExpressionFunctionsUtil.userDefinedFunctions;
@Data
public class CalculatedFieldCtx {
@ -111,6 +113,7 @@ public class CalculatedFieldCtx {
if (isValidExpression(expression)) {
this.customExpression = ThreadLocal.withInitial(() ->
new ExpressionBuilder(expression)
.functions(userDefinedFunctions)
.implicitMultiplication(true)
.variables(this.arguments.keySet())
.build()

88
application/src/main/java/org/thingsboard/server/service/edge/EdgeMsgConstructorUtils.java

@ -16,6 +16,7 @@
package org.thingsboard.server.service.edge;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
@ -24,6 +25,11 @@ import com.google.gson.JsonPrimitive;
import com.google.gson.reflect.TypeToken;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.action.TbSaveToCustomCassandraTableNode;
import org.thingsboard.rule.engine.aws.lambda.TbAwsLambdaNode;
import org.thingsboard.rule.engine.rest.TbSendRestApiCallReplyNode;
import org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode;
import org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode;
import org.thingsboard.server.common.adaptor.JsonConverter;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Dashboard;
@ -89,6 +95,7 @@ import org.thingsboard.server.gen.edge.v1.DeviceProfileUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DeviceRpcCallMsg;
import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg;
import org.thingsboard.server.gen.edge.v1.EdgeConfiguration;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.EntityDataProto;
import org.thingsboard.server.gen.edge.v1.EntityViewUpdateMsg;
import org.thingsboard.server.gen.edge.v1.NotificationRuleUpdateMsg;
@ -113,11 +120,36 @@ import org.thingsboard.server.gen.edge.v1.WidgetTypeUpdateMsg;
import org.thingsboard.server.gen.edge.v1.WidgetsBundleUpdateMsg;
import org.thingsboard.server.gen.transport.TransportProtos;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
@Slf4j
public class EdgeMsgConstructorUtils {
public static final Map<EdgeVersion, Map<String, String>> IGNORED_PARAMS_BY_EDGE_VERSION = Map.of(
EdgeVersion.V_3_8_0,
Map.of(
TbMsgTimeseriesNode.class.getName(), "processingSettings",
TbMsgAttributesNode.class.getName(), "processingSettings",
TbSaveToCustomCassandraTableNode.class.getName(), "defaultTtl"
),
EdgeVersion.V_3_7_0,
Map.of(
TbMsgTimeseriesNode.class.getName(), "processingSettings",
TbMsgAttributesNode.class.getName(), "processingSettings",
TbSaveToCustomCassandraTableNode.class.getName(), "defaultTtl"
)
);
public static final Map<EdgeVersion, Set<String>> EXCLUDED_NODES_BY_EDGE_VERSION = Map.of(
EdgeVersion.V_3_7_0,
Set.of(
TbSendRestApiCallReplyNode.class.getName(),
TbAwsLambdaNode.class.getName()
)
);
public static AlarmUpdateMsg constructAlarmUpdatedMsg(UpdateMsgType msgType, Alarm alarm) {
return AlarmUpdateMsg.newBuilder().setMsgType(msgType)
@ -417,8 +449,50 @@ public class EdgeMsgConstructorUtils {
.setIdLSB(ruleChainId.getId().getLeastSignificantBits()).build();
}
public static RuleChainMetadataUpdateMsg constructRuleChainMetadataUpdatedMsg(UpdateMsgType msgType, RuleChainMetaData ruleChainMetaData) {
return RuleChainMetadataUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(ruleChainMetaData)).build();
public static RuleChainMetadataUpdateMsg constructRuleChainMetadataUpdatedMsg(UpdateMsgType msgType, RuleChainMetaData ruleChainMetaData, EdgeVersion edgeVersion) {
String metaData = sanitizeMetadataForLegacyEdgeVersion(ruleChainMetaData, edgeVersion);
return RuleChainMetadataUpdateMsg.newBuilder()
.setMsgType(msgType)
.setEntity(metaData)
.build();
}
private static String sanitizeMetadataForLegacyEdgeVersion(RuleChainMetaData ruleChainMetaData, EdgeVersion edgeVersion) {
JsonNode jsonNode = JacksonUtil.valueToTree(ruleChainMetaData);
JsonNode nodes = jsonNode.get("nodes");
updateNodeConfigurationsForLegacyEdge(nodes, edgeVersion);
removeExcludedNodesForLegacyEdge(nodes, edgeVersion);
return JacksonUtil.toString(jsonNode);
}
private static void updateNodeConfigurationsForLegacyEdge(JsonNode nodes, EdgeVersion edgeVersion) {
nodes.forEach(node -> {
if (node.isObject() && node.has("configuration")) {
String nodeType = node.get("type").asText();
Map<String, String> ignoredParams = IGNORED_PARAMS_BY_EDGE_VERSION.get(edgeVersion);
if (ignoredParams != null && ignoredParams.containsKey(nodeType)) {
((ObjectNode) node.get("configuration")).remove(ignoredParams.get(nodeType));
}
}
});
}
private static void removeExcludedNodesForLegacyEdge(JsonNode nodes, EdgeVersion edgeVersion) {
Iterator<JsonNode> iterator = nodes.iterator();
while (iterator.hasNext()) {
JsonNode node = iterator.next();
String type = node.get("type").asText();
Set<String> missNodes = EXCLUDED_NODES_BY_EDGE_VERSION.get(edgeVersion);
if (missNodes != null && missNodes.contains(type)) {
iterator.remove();
}
}
}
public static EntityDataProto constructEntityDataMsg(TenantId tenantId, EntityId entityId, EdgeEventActionType actionType, JsonElement entityData) {
@ -433,7 +507,7 @@ public class EdgeMsgConstructorUtils {
JsonObject data = entityData.getAsJsonObject();
builder.setPostTelemetryMsg(JsonConverter.convertToTelemetryProto(data.getAsJsonObject("data"), ts));
} catch (Exception e) {
log.warn("[{}][{}] Can't convert to telemetry proto, entityData [{}]", tenantId, entityId, entityData, e);
log.trace("[{}][{}] Can't convert to telemetry proto, entityData [{}]", tenantId, entityId, entityData, e);
}
break;
case ATTRIBUTES_UPDATED:
@ -448,7 +522,7 @@ public class EdgeMsgConstructorUtils {
builder.setPostAttributeScope(getScopeOfDefault(data));
builder.setAttributeTs(ts);
} catch (Exception e) {
log.warn("[{}][{}] Can't convert to AttributesUpdatedMsg proto, entityData [{}]", tenantId, entityId, entityData, e);
log.trace("[{}][{}] Can't convert to AttributesUpdatedMsg proto, entityData [{}]", tenantId, entityId, entityData, e);
}
break;
case POST_ATTRIBUTES:
@ -459,7 +533,7 @@ public class EdgeMsgConstructorUtils {
builder.setPostAttributeScope(getScopeOfDefault(data));
builder.setAttributeTs(ts);
} catch (Exception e) {
log.warn("[{}][{}] Can't convert to PostAttributesMsg, entityData [{}]", tenantId, entityId, entityData, e);
log.trace("[{}][{}] Can't convert to PostAttributesMsg, entityData [{}]", tenantId, entityId, entityData, e);
}
break;
case ATTRIBUTES_DELETED:
@ -467,12 +541,12 @@ public class EdgeMsgConstructorUtils {
AttributeDeleteMsg.Builder attributeDeleteMsg = AttributeDeleteMsg.newBuilder();
attributeDeleteMsg.setScope(entityData.getAsJsonObject().getAsJsonPrimitive("scope").getAsString());
JsonArray jsonArray = entityData.getAsJsonObject().getAsJsonArray("keys");
List<String> keys = new Gson().fromJson(jsonArray.toString(), new TypeToken<>(){}.getType());
List<String> keys = new Gson().fromJson(jsonArray.toString(), new TypeToken<>() {}.getType());
attributeDeleteMsg.addAllAttributeNames(keys);
attributeDeleteMsg.build();
builder.setAttributeDeleteMsg(attributeDeleteMsg);
} catch (Exception e) {
log.warn("[{}][{}] Can't convert to AttributeDeleteMsg proto, entityData [{}]", tenantId, entityId, entityData, e);
log.trace("[{}][{}] Can't convert to AttributeDeleteMsg proto, entityData [{}]", tenantId, entityId, entityData, e);
}
break;
}

12
application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java

@ -203,7 +203,7 @@ public abstract class EdgeGrpcSession implements Closeable {
@Override
public void onError(Throwable t) {
log.error("[{}][{}] Stream was terminated due to error:", tenantId, sessionId, t);
log.trace("[{}][{}] Stream was terminated due to error:", tenantId, sessionId, t);
closeSession();
}
@ -255,7 +255,7 @@ public abstract class EdgeGrpcSession implements Closeable {
private void doSync(EdgeSyncCursor cursor) {
if (cursor.hasNext()) {
EdgeEventFetcher next = cursor.getNext();
log.info("[{}][{}] starting sync process, cursor current idx = {}, class = {}",
log.debug("[{}][{}] starting sync process, cursor current idx = {}, class = {}",
tenantId, edge.getId(), cursor.getCurrentIdx(), next.getClass().getSimpleName());
ListenableFuture<Pair<Long, Long>> future = startProcessingEdgeEvents(next);
Futures.addCallback(future, new FutureCallback<>() {
@ -651,7 +651,7 @@ public abstract class EdgeGrpcSession implements Closeable {
default -> log.warn("[{}][{}] Unsupported action type [{}]", tenantId, sessionId, edgeEvent.getAction());
}
} catch (Exception e) {
log.error("[{}][{}] Exception during converting edge event to downlink msg", tenantId, sessionId, e);
log.trace("[{}][{}] Exception during converting edge event to downlink msg", tenantId, sessionId, e);
}
if (downlinkMsg != null) {
result.add(downlinkMsg);
@ -763,7 +763,7 @@ public abstract class EdgeGrpcSession implements Closeable {
try {
outputStream.onNext(responseMsg);
} catch (Exception e) {
log.error("[{}][{}] Failed to send downlink message [{}]", tenantId, sessionId, downlinkMsgStr, e);
log.trace("[{}][{}] Failed to send downlink message [{}]", tenantId, sessionId, downlinkMsgStr, e);
connected = false;
sessionCloseListener.accept(edge, sessionId);
} finally {
@ -780,7 +780,7 @@ public abstract class EdgeGrpcSession implements Closeable {
return null;
}
return ctx.getProcessor(edgeEvent.getType()).convertEdgeEventToDownlink(edgeEvent);
return ctx.getProcessor(edgeEvent.getType()).convertEdgeEventToDownlink(edgeEvent, edgeVersion);
}
public void addEventToHighPriorityQueue(EdgeEvent edgeEvent) {
@ -909,7 +909,7 @@ public abstract class EdgeGrpcSession implements Closeable {
}
} catch (Exception e) {
String failureMsg = String.format("Can't process uplink msg [%s] from edge", uplinkMsg);
log.error("[{}][{}] Can't process uplink msg [{}]", edge.getTenantId(), sessionId, uplinkMsg, e);
log.trace("[{}][{}] Can't process uplink msg [{}]", edge.getTenantId(), sessionId, uplinkMsg, e);
ctx.getRuleProcessor().process(EdgeCommunicationFailureTrigger.builder().tenantId(edge.getTenantId()).edgeId(edge.getId())
.customerId(edge.getCustomerId()).edgeName(edge.getName()).failureMsg(failureMsg).error(e.getMessage()).build());
return Futures.immediateFailedFuture(e);

6
application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java

@ -65,8 +65,10 @@ public class EdgeSyncCursor {
fetchers.add(new AdminSettingsEdgeEventFetcher(ctx.getAdminSettingsService()));
fetchers.add(new TenantAdminUsersEdgeEventFetcher(ctx.getUserService()));
}
Customer publicCustomer = ctx.getCustomerService().findOrCreatePublicCustomer(edge.getTenantId());
fetchers.add(new CustomerEdgeEventFetcher(publicCustomer.getId()));
Customer publicCustomer = ctx.getCustomerService().findPublicCustomer(edge.getTenantId());
if (publicCustomer != null) {
fetchers.add(new CustomerEdgeEventFetcher(publicCustomer.getId()));
}
if (edge.getCustomerId() != null && !EntityId.NULL_UUID.equals(edge.getCustomerId().getId())) {
fetchers.add(new CustomerEdgeEventFetcher(edge.getCustomerId()));
fetchers.add(new CustomerUsersEdgeEventFetcher(ctx.getUserService(), edge.getCustomerId()));

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/EdgeProcessor.java

@ -20,13 +20,14 @@ import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.transport.TransportProtos;
public interface EdgeProcessor {
ListenableFuture<Void> processEntityNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto msg);
default DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) {
default DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
return null;
}

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessor.java

@ -30,6 +30,7 @@ import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.util.TbCoreComponent;
@ -55,7 +56,7 @@ public class AlarmEdgeProcessor extends BaseAlarmProcessor implements AlarmProce
}
@Override
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) {
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
AlarmUpdateMsg alarmUpdateMsg = convertAlarmEventToAlarmMsg(edgeEvent);
if (alarmUpdateMsg != null) {
return DownlinkMsg.newBuilder()

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/comment/AlarmCommentEdgeProcessor.java

@ -31,6 +31,7 @@ import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.gen.edge.v1.AlarmCommentUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.util.TbCoreComponent;
@ -57,7 +58,7 @@ public class AlarmCommentEdgeProcessor extends BaseAlarmProcessor implements Ala
}
@Override
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) {
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction());
switch (edgeEvent.getAction()) {
case ADDED_COMMENT:

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessor.java

@ -36,6 +36,7 @@ import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.gen.edge.v1.AssetUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.edge.EdgeMsgConstructorUtils;
@ -107,7 +108,7 @@ public class AssetEdgeProcessor extends BaseAssetProcessor implements AssetProce
}
@Override
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) {
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
AssetId assetId = new AssetId(edgeEvent.getEntityId());
switch (edgeEvent.getAction()) {
case ADDED, UPDATED, ASSIGNED_TO_EDGE, ASSIGNED_TO_CUSTOMER, UNASSIGNED_FROM_CUSTOMER -> {

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/profile/AssetProfileEdgeProcessor.java

@ -36,6 +36,7 @@ import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.gen.edge.v1.AssetProfileUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.edge.EdgeMsgConstructorUtils;
@ -94,7 +95,7 @@ public class AssetProfileEdgeProcessor extends BaseAssetProfileProcessor impleme
}
@Override
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) {
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
AssetProfileId assetProfileId = new AssetProfileId(edgeEvent.getEntityId());
switch (edgeEvent.getAction()) {
case ADDED, UPDATED -> {

28
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/customer/CustomerEdgeProcessor.java

@ -32,6 +32,7 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.gen.edge.v1.CustomerUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.util.TbCoreComponent;
@ -48,7 +49,7 @@ import java.util.UUID;
public class CustomerEdgeProcessor extends BaseEdgeProcessor {
@Override
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) {
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
CustomerId customerId = new CustomerId(edgeEvent.getEntityId());
switch (edgeEvent.getAction()) {
case ADDED, UPDATED -> {
@ -80,13 +81,16 @@ public class CustomerEdgeProcessor extends BaseEdgeProcessor {
UUID uuid = new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB());
CustomerId customerId = new CustomerId(EntityIdFactory.getByEdgeEventTypeAndUuid(type, uuid).getId());
switch (actionType) {
case UPDATED:
List<ListenableFuture<Void>> futures = new ArrayList<>();
PageDataIterable<Edge> edges = new PageDataIterable<>(link -> edgeCtx.getEdgeService().findEdgesByTenantIdAndCustomerId(tenantId, customerId, link), 1024);
for (Edge edge : edges) {
futures.add(saveEdgeEvent(tenantId, edge.getId(), type, actionType, customerId, null));
case ADDED:
Customer customerById = edgeCtx.getCustomerService().findCustomerById(tenantId, customerId);
if (customerById != null && customerById.isPublic()) {
return findEdgesAndSaveEdgeEvents(link -> edgeCtx.getEdgeService().findEdgesByTenantId(tenantId, link),
tenantId, type, actionType, customerId);
}
return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService);
return Futures.immediateFuture(null);
case UPDATED:
return findEdgesAndSaveEdgeEvents(link -> edgeCtx.getEdgeService().findEdgesByTenantIdAndCustomerId(tenantId, customerId, link),
tenantId, type, actionType, customerId);
case DELETED:
EdgeId edgeId = new EdgeId(new UUID(edgeNotificationMsg.getEdgeIdMSB(), edgeNotificationMsg.getEdgeIdLSB()));
return saveEdgeEvent(tenantId, edgeId, type, actionType, customerId, null);
@ -95,6 +99,16 @@ public class CustomerEdgeProcessor extends BaseEdgeProcessor {
}
}
public ListenableFuture<Void> findEdgesAndSaveEdgeEvents(PageDataIterable.FetchFunction<Edge> edgeFetcher, TenantId tenantId,
EdgeEventType type, EdgeEventActionType actionType, CustomerId customerId) {
List<ListenableFuture<Void>> futures = new ArrayList<>();
PageDataIterable<Edge> edges = new PageDataIterable<>(edgeFetcher, 1024);
for (Edge edge : edges) {
futures.add(saveEdgeEvent(tenantId, edge.getId(), type, actionType, customerId, null));
}
return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService);
}
@Override
public EdgeEventType getEdgeEventType() {
return EdgeEventType.CUSTOMER;

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/dashboard/DashboardEdgeProcessor.java

@ -33,6 +33,7 @@ import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.gen.edge.v1.DashboardUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.edge.EdgeMsgConstructorUtils;
@ -100,7 +101,7 @@ public class DashboardEdgeProcessor extends BaseDashboardProcessor implements Da
}
@Override
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) {
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
DashboardId dashboardId = new DashboardId(edgeEvent.getEntityId());
switch (edgeEvent.getAction()) {
case ADDED, UPDATED, ASSIGNED_TO_EDGE, ASSIGNED_TO_CUSTOMER, UNASSIGNED_FROM_CUSTOMER -> {

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java

@ -49,6 +49,7 @@ import org.thingsboard.server.gen.edge.v1.DeviceCredentialsUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DeviceRpcCallMsg;
import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.TbQueueCallback;
import org.thingsboard.server.queue.TbQueueMsgMetadata;
@ -222,7 +223,7 @@ public class DeviceEdgeProcessor extends BaseDeviceProcessor implements DevicePr
}
@Override
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) {
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
DeviceId deviceId = new DeviceId(edgeEvent.getEntityId());
switch (edgeEvent.getAction()) {
case ADDED:

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/DeviceProfileEdgeProcessor.java

@ -36,6 +36,7 @@ import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.gen.edge.v1.DeviceProfileUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.edge.EdgeMsgConstructorUtils;
@ -94,7 +95,7 @@ public class DeviceProfileEdgeProcessor extends BaseDeviceProfileProcessor imple
}
@Override
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) {
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
DeviceProfileId deviceProfileId = new DeviceProfileId(edgeEvent.getEntityId());
switch (edgeEvent.getAction()) {
case ADDED, UPDATED -> {

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/edge/EdgeEntityProcessor.java

@ -32,6 +32,7 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.edge.EdgeMsgConstructorUtils;
@ -101,7 +102,7 @@ public class EdgeEntityProcessor extends BaseEdgeProcessor {
}
@Override
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) {
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
EdgeId edgeId = new EdgeId(edgeEvent.getEntityId());
switch (edgeEvent.getAction()) {
case ASSIGNED_TO_CUSTOMER, UNASSIGNED_FROM_CUSTOMER -> {

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/entityview/EntityViewEdgeProcessor.java

@ -34,6 +34,7 @@ import org.thingsboard.server.common.data.msg.TbMsgType;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.EntityViewUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent;
@ -106,7 +107,7 @@ public class EntityViewEdgeProcessor extends BaseEntityViewProcessor implements
}
@Override
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) {
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
EntityViewId entityViewId = new EntityViewId(edgeEvent.getEntityId());
switch (edgeEvent.getAction()) {
case ADDED, UPDATED, ASSIGNED_TO_EDGE, ASSIGNED_TO_CUSTOMER, UNASSIGNED_FROM_CUSTOMER -> {

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/notification/NotificationRuleEdgeProcessor.java

@ -25,6 +25,7 @@ import org.thingsboard.server.common.data.id.NotificationRuleId;
import org.thingsboard.server.common.data.notification.rule.NotificationRule;
import org.thingsboard.server.dao.notification.NotificationRuleService;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.NotificationRuleUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent;
@ -40,7 +41,7 @@ public class NotificationRuleEdgeProcessor extends BaseEdgeProcessor {
private NotificationRuleService notificationRuleService;
@Override
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) {
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
NotificationRuleId notificationRuleId = new NotificationRuleId(edgeEvent.getEntityId());
DownlinkMsg downlinkMsg = null;
switch (edgeEvent.getAction()) {

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/notification/NotificationTargetEdgeProcessor.java

@ -25,6 +25,7 @@ import org.thingsboard.server.common.data.id.NotificationTargetId;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.dao.notification.NotificationTargetService;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.NotificationTargetUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent;
@ -40,7 +41,7 @@ public class NotificationTargetEdgeProcessor extends BaseEdgeProcessor {
private NotificationTargetService notificationTargetService;
@Override
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) {
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
NotificationTargetId notificationTargetId = new NotificationTargetId(edgeEvent.getEntityId());
DownlinkMsg downlinkMsg = null;
switch (edgeEvent.getAction()) {

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/notification/NotificationTemplateEdgeProcessor.java

@ -25,6 +25,7 @@ import org.thingsboard.server.common.data.id.NotificationTemplateId;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
import org.thingsboard.server.dao.notification.NotificationTemplateService;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.NotificationTemplateUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent;
@ -40,7 +41,7 @@ public class NotificationTemplateEdgeProcessor extends BaseEdgeProcessor {
private NotificationTemplateService notificationTemplateService;
@Override
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) {
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
NotificationTemplateId notificationTemplateId = new NotificationTemplateId(edgeEvent.getEntityId());
DownlinkMsg downlinkMsg = null;
switch (edgeEvent.getAction()) {

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/oauth2/DomainEdgeProcessor.java

@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.id.DomainId;
import org.thingsboard.server.common.data.oauth2.OAuth2Client;
import org.thingsboard.server.dao.domain.DomainService;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.OAuth2ClientUpdateMsg;
import org.thingsboard.server.gen.edge.v1.OAuth2DomainUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
@ -42,7 +43,7 @@ public class DomainEdgeProcessor extends BaseEdgeProcessor {
private DomainService domainService;
@Override
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) {
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
DomainId domainId = new DomainId(edgeEvent.getEntityId());
switch (edgeEvent.getAction()) {
case ADDED, UPDATED -> {

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/oauth2/OAuth2ClientEdgeProcessor.java

@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.OAuth2ClientId;
import org.thingsboard.server.common.data.oauth2.OAuth2Client;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.OAuth2ClientUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent;
@ -35,7 +36,7 @@ import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor;
public class OAuth2ClientEdgeProcessor extends BaseEdgeProcessor {
@Override
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) {
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
OAuth2ClientId oAuth2ClientId = new OAuth2ClientId(edgeEvent.getEntityId());
switch (edgeEvent.getAction()) {

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/ota/OtaPackageEdgeProcessor.java

@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.OtaPackageId;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.OtaPackageUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent;
@ -35,7 +36,7 @@ import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor;
public class OtaPackageEdgeProcessor extends BaseEdgeProcessor {
@Override
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) {
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
OtaPackageId otaPackageId = new OtaPackageId(edgeEvent.getEntityId());
switch (edgeEvent.getAction()) {
case ADDED, UPDATED -> {

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/queue/QueueEdgeProcessor.java

@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.QueueId;
import org.thingsboard.server.common.data.queue.Queue;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.QueueUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent;
@ -35,7 +36,7 @@ import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor;
public class QueueEdgeProcessor extends BaseEdgeProcessor {
@Override
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) {
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
QueueId queueId = new QueueId(edgeEvent.getEntityId());
switch (edgeEvent.getAction()) {
case ADDED, UPDATED -> {

4
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationEdgeProcessor.java

@ -21,7 +21,6 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
@ -30,6 +29,7 @@ import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.RelationUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.gen.transport.TransportProtos;
@ -85,7 +85,7 @@ public class RelationEdgeProcessor extends BaseRelationProcessor implements Rela
}
@Override
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) {
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
EntityRelation entityRelation = JacksonUtil.convertValue(edgeEvent.getBody(), EntityRelation.class);
UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction());
RelationUpdateMsg relationUpdateMsg = EdgeMsgConstructorUtils.constructRelationUpdatedMsg(msgType, entityRelation);

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/ResourceEdgeProcessor.java

@ -29,6 +29,7 @@ import org.thingsboard.server.common.data.id.TbResourceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.ResourceUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent;
@ -73,7 +74,7 @@ public class ResourceEdgeProcessor extends BaseResourceProcessor implements Reso
}
@Override
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) {
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
TbResourceId tbResourceId = new TbResourceId(edgeEvent.getEntityId());
switch (edgeEvent.getAction()) {
case ADDED, UPDATED -> {

5
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/RuleChainEdgeProcessor.java

@ -33,6 +33,7 @@ import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.RuleChainMetadataUpdateMsg;
import org.thingsboard.server.gen.edge.v1.RuleChainUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
@ -134,7 +135,7 @@ public class RuleChainEdgeProcessor extends BaseRuleChainProcessor {
}
@Override
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) {
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
RuleChainId ruleChainId = new RuleChainId(edgeEvent.getEntityId());
DownlinkMsg downlinkMsg = null;
switch (edgeEvent.getAction()) {
@ -161,7 +162,7 @@ public class RuleChainEdgeProcessor extends BaseRuleChainProcessor {
RuleChainMetaData ruleChainMetaData = edgeCtx.getRuleChainService().loadRuleChainMetaData(edgeEvent.getTenantId(), ruleChainId);
RuleChainMetadataUpdateMsg ruleChainMetadataUpdateMsg = EdgeMsgConstructorUtils
.constructRuleChainMetadataUpdatedMsg(msgType, ruleChainMetaData);
.constructRuleChainMetadataUpdatedMsg(msgType, ruleChainMetaData, edgeVersion);
builder.addRuleChainMetadataUpdateMsg(ruleChainMetadataUpdateMsg);
downlinkMsg = builder.build();
}

5
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/RuleChainMetadataEdgeProcessor.java

@ -24,6 +24,7 @@ import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.RuleChainMetadataUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent;
@ -36,14 +37,14 @@ import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor;
public class RuleChainMetadataEdgeProcessor extends BaseEdgeProcessor {
@Override
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) {
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
RuleChainId ruleChainId = new RuleChainId(edgeEvent.getEntityId());
RuleChain ruleChain = edgeCtx.getRuleChainService().findRuleChainById(edgeEvent.getTenantId(), ruleChainId);
if (ruleChain != null) {
RuleChainMetaData ruleChainMetaData = edgeCtx.getRuleChainService().loadRuleChainMetaData(edgeEvent.getTenantId(), ruleChainId);
UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction());
RuleChainMetadataUpdateMsg ruleChainMetadataUpdateMsg = EdgeMsgConstructorUtils
.constructRuleChainMetadataUpdatedMsg(msgType, ruleChainMetaData);
.constructRuleChainMetadataUpdatedMsg(msgType, ruleChainMetaData, edgeVersion);
return DownlinkMsg.newBuilder()
.setDownlinkMsgId(EdgeUtils.nextPositiveInt())
.addRuleChainMetadataUpdateMsg(ruleChainMetadataUpdateMsg)

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/settings/AdminSettingsEdgeProcessor.java

@ -24,6 +24,7 @@ import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.gen.edge.v1.AdminSettingsUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor;
@ -33,7 +34,7 @@ import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor;
public class AdminSettingsEdgeProcessor extends BaseEdgeProcessor {
@Override
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) {
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
AdminSettings adminSettings = JacksonUtil.convertValue(edgeEvent.getBody(), AdminSettings.class);
if (adminSettings == null) {
return null;

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/tenant/TenantEdgeProcessor.java

@ -25,6 +25,7 @@ import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.TenantProfileUpdateMsg;
import org.thingsboard.server.gen.edge.v1.TenantUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
@ -38,7 +39,7 @@ import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor;
public class TenantEdgeProcessor extends BaseEdgeProcessor {
@Override
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) {
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
TenantId tenantId = TenantId.fromUUID(edgeEvent.getEntityId());
if (EdgeEventActionType.UPDATED.equals(edgeEvent.getAction())) {
Tenant tenant = edgeCtx.getTenantService().findTenantById(tenantId);

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/tenant/TenantProfileEdgeProcessor.java

@ -24,6 +24,7 @@ import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.TenantProfileId;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.TenantProfileUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent;
@ -36,7 +37,7 @@ import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor;
public class TenantProfileEdgeProcessor extends BaseEdgeProcessor {
@Override
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) {
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
TenantProfileId tenantProfileId = new TenantProfileId(edgeEvent.getEntityId());
if (EdgeEventActionType.UPDATED.equals(edgeEvent.getAction())) {
TenantProfile tenantProfile = edgeCtx.getTenantProfileService().findTenantProfileById(edgeEvent.getTenantId(), tenantProfileId);

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/user/UserEdgeProcessor.java

@ -24,6 +24,7 @@ import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.security.UserCredentials;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.gen.edge.v1.UserCredentialsUpdateMsg;
import org.thingsboard.server.queue.util.TbCoreComponent;
@ -36,7 +37,7 @@ import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor;
public class UserEdgeProcessor extends BaseEdgeProcessor {
@Override
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) {
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
UserId userId = new UserId(edgeEvent.getEntityId());
switch (edgeEvent.getAction()) {
case ADDED, UPDATED -> {

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/widget/WidgetBundleEdgeProcessor.java

@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.WidgetsBundleId;
import org.thingsboard.server.common.data.widget.WidgetsBundle;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.gen.edge.v1.WidgetsBundleUpdateMsg;
import org.thingsboard.server.queue.util.TbCoreComponent;
@ -37,7 +38,7 @@ import java.util.List;
public class WidgetBundleEdgeProcessor extends BaseEdgeProcessor {
@Override
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) {
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
WidgetsBundleId widgetsBundleId = new WidgetsBundleId(edgeEvent.getEntityId());
switch (edgeEvent.getAction()) {
case ADDED, UPDATED -> {

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/widget/WidgetTypeEdgeProcessor.java

@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.WidgetTypeId;
import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.gen.edge.v1.WidgetTypeUpdateMsg;
import org.thingsboard.server.queue.util.TbCoreComponent;
@ -35,7 +36,7 @@ import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor;
public class WidgetTypeEdgeProcessor extends BaseEdgeProcessor {
@Override
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) {
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
WidgetTypeId widgetTypeId = new WidgetTypeId(edgeEvent.getEntityId());
switch (edgeEvent.getAction()) {
case ADDED, UPDATED -> {

1
application/src/main/java/org/thingsboard/server/service/edge/rpc/utils/EdgeVersionUtils.java

@ -24,4 +24,5 @@ public final class EdgeVersionUtils {
public static boolean isEdgeVersionOlderThan(EdgeVersion currentVersion, EdgeVersion requiredVersion) {
return currentVersion.ordinal() < requiredVersion.ordinal();
}
}

44
application/src/main/java/org/thingsboard/server/service/edqs/EdqsSyncService.java

@ -161,13 +161,17 @@ public abstract class EdqsSyncService {
private void processRelationBatch(List<RelationEntity> relations) {
for (RelationEntity relation : relations) {
if (RelationTypeGroup.COMMON.name().equals(relation.getRelationTypeGroup())) {
EntityIdInfo entityIdInfo = entityInfoMap.get(relation.getFromId());
if (entityIdInfo != null) {
process(entityIdInfo.tenantId(), RELATION, relation.toData());
} else {
log.info("Relation from id not found: {} ", relation);
try {
if (RelationTypeGroup.COMMON.name().equals(relation.getRelationTypeGroup())) {
EntityIdInfo entityIdInfo = entityInfoMap.get(relation.getFromId());
if (entityIdInfo != null) {
process(entityIdInfo.tenantId(), RELATION, relation.toData());
} else {
log.info("Relation from id not found: {} ", relation);
}
}
} catch (Exception e) {
log.error("Failed to sync relation: {}", relation, e);
}
}
}
@ -207,19 +211,23 @@ public abstract class EdqsSyncService {
private void processAttributeBatch(List<AttributeKvEntity> batch) {
for (AttributeKvEntity attribute : batch) {
attribute.setStrKey(getStrKeyOrFetchFromDb(attribute.getId().getAttributeKey()));
UUID entityId = attribute.getId().getEntityId();
EntityIdInfo entityIdInfo = entityInfoMap.get(entityId);
if (entityIdInfo == null) {
log.debug("Skipping attribute with entity UUID {} as it is not found in entityInfoMap", entityId);
continue;
try {
attribute.setStrKey(getStrKeyOrFetchFromDb(attribute.getId().getAttributeKey()));
UUID entityId = attribute.getId().getEntityId();
EntityIdInfo entityIdInfo = entityInfoMap.get(entityId);
if (entityIdInfo == null) {
log.debug("Skipping attribute with entity UUID {} as it is not found in entityInfoMap", entityId);
continue;
}
AttributeKv attributeKv = new AttributeKv(
EntityIdFactory.getByTypeAndUuid(entityIdInfo.entityType(), entityId),
AttributeScope.valueOf(attribute.getId().getAttributeType()),
attribute.toData(),
attribute.getVersion());
process(entityIdInfo.tenantId(), ATTRIBUTE_KV, attributeKv);
} catch (Exception e) {
log.error("Failed to sync attribute: {}", attribute, e);
}
AttributeKv attributeKv = new AttributeKv(
EntityIdFactory.getByTypeAndUuid(entityIdInfo.entityType(), entityId),
AttributeScope.valueOf(attribute.getId().getAttributeType()),
attribute.toData(),
attribute.getVersion());
process(entityIdInfo.tenantId(), ATTRIBUTE_KV, attributeKv);
}
}

2
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCalculatedFieldConsumerService.java

@ -16,7 +16,6 @@
package org.thingsboard.server.service.queue;
import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher;
@ -70,7 +69,6 @@ import java.util.stream.Collectors;
@Service
@TbRuleEngineComponent
@Slf4j
public class DefaultTbCalculatedFieldConsumerService extends AbstractPartitionBasedConsumerService<ToCalculatedFieldNotificationMsg> implements TbCalculatedFieldConsumerService {
@Value("${queue.calculated_fields.poll_interval:25}")

14
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java

@ -551,11 +551,15 @@ public class DefaultTbClusterService implements TbClusterService {
}
private void processEdgeNotification(EdgeId edgeId, ToEdgeNotificationMsg toEdgeNotificationMsg) {
var serviceIdOpt = Optional.ofNullable(edgeIdServiceIdCache.get(edgeId));
serviceIdOpt.ifPresentOrElse(
serviceId -> pushMsgToEdgeNotification(toEdgeNotificationMsg, serviceId.get()),
() -> broadcastEdgeNotification(edgeId, toEdgeNotificationMsg)
);
if (edgesEnabled) {
var serviceIdOpt = Optional.ofNullable(edgeIdServiceIdCache.get(edgeId));
serviceIdOpt.ifPresentOrElse(
serviceId -> pushMsgToEdgeNotification(toEdgeNotificationMsg, serviceId.get()),
() -> broadcastEdgeNotification(edgeId, toEdgeNotificationMsg)
);
} else {
log.trace("Edges disabled. Ignoring edge notification {} for edgeId: {}", toEdgeNotificationMsg, edgeId);
}
}
private void pushMsgToEdgeNotification(ToEdgeNotificationMsg toEdgeNotificationMsg, String serviceId) {

2
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java

@ -20,7 +20,6 @@ import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher;
@ -118,7 +117,6 @@ import java.util.stream.Collectors;
@Service
@TbCoreComponent
@Slf4j
public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCoreNotificationMsg> implements TbCoreConsumerService {
@Value("${queue.core.poll-interval}")

26
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbEdgeConsumerService.java

@ -19,8 +19,6 @@ import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Value;
@ -45,12 +43,13 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeNotificationMsg;
import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.common.consumer.MainQueueConsumerManager;
import org.thingsboard.server.queue.discovery.QueueKey;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import org.thingsboard.server.queue.provider.TbCoreQueueFactory;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.edge.EdgeContextComponent;
import org.thingsboard.server.queue.common.consumer.MainQueueConsumerManager;
import org.thingsboard.server.service.edge.rpc.EdgeRpcService;
import org.thingsboard.server.service.queue.processing.AbstractConsumerService;
import org.thingsboard.server.service.queue.processing.IdMsgPair;
@ -65,7 +64,6 @@ import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@Slf4j
@Service
@TbCoreComponent
public class DefaultTbEdgeConsumerService extends AbstractConsumerService<ToEdgeNotificationMsg> implements TbEdgeConsumerService {
@ -195,36 +193,42 @@ public class DefaultTbEdgeConsumerService extends AbstractConsumerService<ToEdge
protected void handleNotification(UUID id, TbProtoQueueMsg<ToEdgeNotificationMsg> msg, TbCallback callback) {
ToEdgeNotificationMsg toEdgeNotificationMsg = msg.getValue();
try {
EdgeRpcService edgeRpcService = edgeCtx.getEdgeRpcService();
if (edgeRpcService == null) {
log.debug("No EdgeRpcService available (edge functionality disabled), ignoring msg: {}", toEdgeNotificationMsg);
callback.onSuccess();
return;
}
if (toEdgeNotificationMsg.hasEdgeHighPriority()) {
EdgeSessionMsg edgeSessionMsg = ProtoUtils.fromProto(toEdgeNotificationMsg.getEdgeHighPriority());
edgeCtx.getEdgeRpcService().onToEdgeSessionMsg(edgeSessionMsg.getTenantId(), edgeSessionMsg);
edgeRpcService.onToEdgeSessionMsg(edgeSessionMsg.getTenantId(), edgeSessionMsg);
callback.onSuccess();
} else if (toEdgeNotificationMsg.hasEdgeEventUpdate()) {
EdgeSessionMsg edgeSessionMsg = ProtoUtils.fromProto(toEdgeNotificationMsg.getEdgeEventUpdate());
edgeCtx.getEdgeRpcService().onToEdgeSessionMsg(edgeSessionMsg.getTenantId(), edgeSessionMsg);
edgeRpcService.onToEdgeSessionMsg(edgeSessionMsg.getTenantId(), edgeSessionMsg);
callback.onSuccess();
} else if (toEdgeNotificationMsg.hasToEdgeSyncRequest()) {
EdgeSessionMsg edgeSessionMsg = ProtoUtils.fromProto(toEdgeNotificationMsg.getToEdgeSyncRequest());
edgeCtx.getEdgeRpcService().onToEdgeSessionMsg(edgeSessionMsg.getTenantId(), edgeSessionMsg);
edgeRpcService.onToEdgeSessionMsg(edgeSessionMsg.getTenantId(), edgeSessionMsg);
callback.onSuccess();
} else if (toEdgeNotificationMsg.hasFromEdgeSyncResponse()) {
EdgeSessionMsg edgeSessionMsg = ProtoUtils.fromProto(toEdgeNotificationMsg.getFromEdgeSyncResponse());
edgeCtx.getEdgeRpcService().onToEdgeSessionMsg(edgeSessionMsg.getTenantId(), edgeSessionMsg);
edgeRpcService.onToEdgeSessionMsg(edgeSessionMsg.getTenantId(), edgeSessionMsg);
callback.onSuccess();
} else if (toEdgeNotificationMsg.hasComponentLifecycle()) {
ComponentLifecycleMsg componentLifecycle = ProtoUtils.fromProto(toEdgeNotificationMsg.getComponentLifecycle());
TenantId tenantId = componentLifecycle.getTenantId();
EdgeId edgeId = new EdgeId(componentLifecycle.getEntityId().getId());
if (ComponentLifecycleEvent.DELETED.equals(componentLifecycle.getEvent())) {
edgeCtx.getEdgeRpcService().deleteEdge(tenantId, edgeId);
edgeRpcService.deleteEdge(tenantId, edgeId);
} else if (ComponentLifecycleEvent.UPDATED.equals(componentLifecycle.getEvent())) {
Edge edge = edgeCtx.getEdgeService().findEdgeById(tenantId, edgeId);
edgeCtx.getEdgeRpcService().updateEdge(tenantId, edge);
edgeRpcService.updateEdge(tenantId, edge);
}
callback.onSuccess();
}
} catch (Exception e) {
log.error("Error processing edge notification message", e);
log.error("Error processing edge notification message {}", toEdgeNotificationMsg, e);
callback.onFailure(e);
}

2
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java

@ -15,7 +15,6 @@
*/
package org.thingsboard.server.service.queue;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Scheduled;
@ -65,7 +64,6 @@ import java.util.stream.Collectors;
@Service
@TbRuleEngineComponent
@Slf4j
public class DefaultTbRuleEngineConsumerService extends AbstractPartitionBasedConsumerService<ToRuleEngineNotificationMsg> implements TbRuleEngineConsumerService {
private final TbRuleEngineConsumerContext ctx;

6
application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java

@ -17,7 +17,8 @@ package org.thingsboard.server.service.queue.processing;
import jakarta.annotation.PreDestroy;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEventPublisher;
import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
@ -62,10 +63,11 @@ import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@Slf4j
@RequiredArgsConstructor
public abstract class AbstractConsumerService<N extends com.google.protobuf.GeneratedMessageV3> extends TbApplicationEventListener<PartitionChangeEvent> {
protected final Logger log = LoggerFactory.getLogger(getClass());
protected final ActorSystemContext actorContext;
protected final TbTenantProfileCache tenantProfileCache;
protected final TbDeviceProfileCache deviceProfileCache;

19
application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractPartitionBasedConsumerService.java

@ -28,6 +28,8 @@ import org.thingsboard.server.service.profile.TbAssetProfileCache;
import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@ -35,7 +37,7 @@ public abstract class AbstractPartitionBasedConsumerService<N extends com.google
private final Lock startupLock = new ReentrantLock();
private volatile boolean started = false;
private PartitionChangeEvent lastPartitionChangeEvent;
private List<PartitionChangeEvent> pendingEvents = new ArrayList<>();
public AbstractPartitionBasedConsumerService(ActorSystemContext actorContext,
TbTenantProfileCache tenantProfileCache,
@ -61,8 +63,16 @@ public abstract class AbstractPartitionBasedConsumerService<N extends com.google
onStartUp();
startupLock.lock();
try {
onPartitionChangeEvent(lastPartitionChangeEvent);
for (PartitionChangeEvent partitionChangeEvent : pendingEvents) {
log.info("Handling partition change event: {}", partitionChangeEvent);
try {
onPartitionChangeEvent(partitionChangeEvent);
} catch (Throwable t) {
log.error("Failed to handle partition change event: {}", partitionChangeEvent, t);
}
}
started = true;
pendingEvents = null;
} finally {
startupLock.unlock();
}
@ -70,17 +80,20 @@ public abstract class AbstractPartitionBasedConsumerService<N extends com.google
@Override
protected void onTbApplicationEvent(PartitionChangeEvent event) {
log.debug("Received partition change event: {}", event);
if (!started) {
startupLock.lock();
try {
if (!started) {
lastPartitionChangeEvent = event;
log.debug("App not started yet, storing event for later: {}", event);
pendingEvents.add(event);
return;
}
} finally {
startupLock.unlock();
}
}
log.info("Handling partition change event: {}", event);
onPartitionChangeEvent(event);
}

26
application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java

@ -343,15 +343,31 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer
s -> {
TbTimeSeriesSubscription sub = (TbTimeSeriesSubscription) s;
List<TsKvEntry> updateData = null;
Map<String, Long> keyStates = sub.getKeyStates();
if (sub.isAllKeys()) {
updateData = data;
if (sub.isLatestValues()) {
for (TsKvEntry kv : data) {
Long stateTs = keyStates.get(kv.getKey());
if (stateTs == null || kv.getTs() > stateTs) {
if (updateData == null) {
updateData = new ArrayList<>();
}
updateData.add(kv);
}
}
} else {
updateData = data;
}
} else {
for (TsKvEntry kv : data) {
if (sub.getKeyStates().containsKey((kv.getKey()))) {
if (updateData == null) {
updateData = new ArrayList<>();
Long stateTs = keyStates.get(kv.getKey());
if (stateTs != null) {
if (!sub.isLatestValues() || kv.getTs() > stateTs) {
if (updateData == null) {
updateData = new ArrayList<>();
}
updateData.add(kv);
}
updateData.add(kv);
}
}
}

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

@ -31,7 +31,6 @@ import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.sync.ie.EntityExportData;
import org.thingsboard.server.common.data.sync.ie.EntityImportResult;
import org.thingsboard.server.common.data.util.ThrowingRunnable;
import org.thingsboard.server.dao.cf.CalculatedFieldService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.queue.util.TbCoreComponent;
@ -62,7 +61,6 @@ public class DefaultEntitiesExportImportService implements EntitiesExportImportS
private final Map<EntityType, EntityImportService<?, ?, ?>> importServices = new HashMap<>();
private final RelationService relationService;
private final CalculatedFieldService calculatedFieldService;
private final RateLimitService rateLimitService;
private final TbLogEntityActionService logEntityActionService;

62
application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java

@ -105,6 +105,8 @@ import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import static org.thingsboard.server.common.data.DataConstants.LATEST_TELEMETRY_SCOPE;
/**
* Created by ashvayka on 27.03.18.
*/
@ -667,24 +669,7 @@ public class DefaultWebSocketService implements WebSocketService {
data.forEach(v -> subState.put(v.getKey(), v.getTs()));
Lock subLock = new ReentrantLock();
TbTimeSeriesSubscription sub = TbTimeSeriesSubscription.builder()
.serviceId(serviceId)
.sessionId(sessionId)
.subscriptionId(registerNewSessionSubId(sessionId, sessionRef, cmd.getCmdId()))
.tenantId(sessionRef.getSecurityCtx().getTenantId())
.entityId(entityId)
.updateProcessor((subscription, update) -> {
subLock.lock();
try {
sendUpdate(subscription.getSessionId(), cmd.getCmdId(), update);
} finally {
subLock.unlock();
}
})
.queryTs(queryTs)
.allKeys(true)
.keyStates(subState)
.build();
TbTimeSeriesSubscription sub = getTsSubscription(subState, subLock, sessionId, sessionRef, cmd, entityId, queryTs, true);
subLock.lock();
try {
@ -712,6 +697,28 @@ public class DefaultWebSocketService implements WebSocketService {
on(r -> Futures.addCallback(tsService.findAllLatest(sessionRef.getSecurityCtx().getTenantId(), entityId), callback, executor), callback::onFailure));
}
private TbTimeSeriesSubscription getTsSubscription(Map<String, Long> subState, Lock subLock, String sessionId, WebSocketSessionRef sessionRef, TimeseriesSubscriptionCmd cmd, EntityId entityId, long queryTs, boolean allKeys) {
return TbTimeSeriesSubscription.builder()
.serviceId(serviceId)
.sessionId(sessionId)
.subscriptionId(registerNewSessionSubId(sessionId, sessionRef, cmd.getCmdId()))
.tenantId(sessionRef.getSecurityCtx().getTenantId())
.entityId(entityId)
.updateProcessor((subscription, update) -> {
subLock.lock();
try {
sendUpdate(subscription.getSessionId(), cmd.getCmdId(), update);
} finally {
subLock.unlock();
}
})
.queryTs(queryTs)
.allKeys(allKeys)
.keyStates(subState)
.latestValues(LATEST_TELEMETRY_SCOPE.equals(cmd.getScope()))
.build();
}
private FutureCallback<List<TsKvEntry>> getSubscriptionCallback(final WebSocketSessionRef sessionRef, final TimeseriesSubscriptionCmd cmd,
final String sessionId, final EntityId entityId, final long queryTs, final long startTs, final List<String> keys) {
return new FutureCallback<>() {
@ -722,24 +729,7 @@ public class DefaultWebSocketService implements WebSocketService {
data.forEach(v -> subState.put(v.getKey(), v.getTs()));
Lock subLock = new ReentrantLock();
TbTimeSeriesSubscription sub = TbTimeSeriesSubscription.builder()
.serviceId(serviceId)
.sessionId(sessionId)
.subscriptionId(registerNewSessionSubId(sessionId, sessionRef, cmd.getCmdId()))
.tenantId(sessionRef.getSecurityCtx().getTenantId())
.entityId(entityId)
.updateProcessor((subscription, update) -> {
subLock.lock();
try {
sendUpdate(subscription.getSessionId(), cmd.getCmdId(), update);
} finally {
subLock.unlock();
}
})
.queryTs(queryTs)
.allKeys(false)
.keyStates(subState)
.build();
TbTimeSeriesSubscription sub = getTsSubscription(subState, subLock, sessionId, sessionRef, cmd, entityId, queryTs, false);
subLock.lock();
try {

16
application/src/main/resources/banner.txt

@ -1,10 +1,10 @@
______ __ _ ____ __
/_ __/ / /_ (_) ____ ____ _ _____ / __ ) ____ ____ _ _____ ____/ /
/ / / __ \ / / / __ \ / __ `/ / ___/ / __ | / __ \ / __ `/ / ___/ / __ /
/ / / / / / / / / / / / / /_/ / (__ ) / /_/ / / /_/ // /_/ / / / / /_/ /
/_/ /_/ /_/ /_/ /_/ /_/ \__, / /____/ /_____/ \____/ \__,_/ /_/ \__,_/
/____/
_____ _ _ ____ _
|_ _| |__ (_)_ __ __ _ ___| __ ) ___ __ _ _ __ __| |
| | | '_ \| | '_ \ / _` / __| _ \ / _ \ / _` | '__/ _` |
| | | | | | | | | | (_| \__ \ |_) | (_) | (_| | | | (_| |
|_| |_| |_|_|_| |_|\__, |___/____/ \___/ \__,_|_| \__,_|
|___/
===================================================
===========================================================
:: ${application.title} :: ${application.formatted-version}
===================================================
===========================================================

34
application/src/main/resources/thingsboard.yml

@ -1421,6 +1421,9 @@ device:
port: "${DEVICE_CONNECTIVITY_COAPS_PORT:5684}"
# Path to the COAP CA root certificate file
pem_cert_file: "${DEVICE_CONNECTIVITY_COAPS_CA_ROOT_CERT:cafile.pem}"
gateway:
# The docker tag for thingsboard/tb-gateway image used in docker-compose file for gateway launch
image_version: "${DEVICE_CONNECTIVITY_GATEWAY_IMAGE_VERSION:3.7-stable}"
# Edges parameters
edges:
@ -1587,11 +1590,24 @@ queue:
# tb_rule_engine.sq:
# - key: max.poll.records
# value: "${TB_QUEUE_KAFKA_SQ_MAX_POLL_RECORDS:1024}"
tb_edge:
# Properties for consumers targeting edge service update topics.
- key: max.poll.records
# Define the maximum number of records that can be polled from tb_edge topics per request.
value: "${TB_QUEUE_KAFKA_EDGE_EVENTS_MAX_POLL_RECORDS:10}"
tb_edge.notifications:
# Properties for consumers targeting high-priority edge notifications.
# These notifications include RPC calls, lifecycle events, and new queue messages,
# requiring minimal latency and swift processing.
- key: max.poll.records
# Define the maximum number of records that can be polled from tb_edge.notifications.<SERVICE_ID> topics.
value: "${TB_QUEUE_KAFKA_EDGE_HP_EVENTS_MAX_POLL_RECORDS:10}"
tb_edge_event.notifications:
# Example of specific consumer properties value per topic for edge event
# Properties for consumers targeting downlinks meant for specific edge topics.
# Topic names are dynamically constructed using tenant and edge identifiers.
- key: max.poll.records
# Example of specific consumer properties value per topic for edge event
value: "${TB_QUEUE_KAFKA_EDGE_EVENT_MAX_POLL_RECORDS:50}"
# Define the maximum number of records that can be polled from tb_edge_event.notifications.<TENANT_ID>.<EDGE_ID> topics.
value: "${TB_QUEUE_KAFKA_EDGE_NOTIFICATIONS_MAX_POLL_RECORDS:10}"
tb_housekeeper:
# Consumer properties for Housekeeper tasks topic
- key: max.poll.records
@ -1762,9 +1778,13 @@ queue:
max_pending_requests: "${TB_EDQS_MAX_PENDING_REQUESTS:10000}"
# Maximum timeout for requests to EDQS
max_request_timeout: "${TB_EDQS_MAX_REQUEST_TIMEOUT:20000}"
# Strings longer than this threshold will be compressed
string_compression_length_threshold: "${TB_EDQS_STRING_COMPRESSION_LENGTH_THRESHOLD:512}"
stats:
# Enable/disable statistics for EDQS
enabled: "${TB_EDQS_STATS_ENABLED:true}"
# Threshold for slow queries to log, in milliseconds
slow_query_threshold: "${TB_EDQS_SLOW_QUERY_THRESHOLD_MS:3000}"
vc:
# Default topic name
topic: "${TB_QUEUE_VC_TOPIC:tb_version_control}"
@ -1834,11 +1854,13 @@ queue:
# Interval in milliseconds to poll messages
poll_interval: "${TB_QUEUE_TRANSPORT_NOTIFICATIONS_POLL_INTERVAL_MS:25}"
edge:
# Default topic name
# Topic name to notify edge service on entity updates, assignment, etc.
topic: "${TB_QUEUE_EDGE_TOPIC:tb_edge}"
# For high-priority notifications that require minimum latency and processing time
# Topic prefix for high-priority edge notifications (rpc, lifecycle, new messages in queue) that require minimum latency and processing time.
# Each tb-core has its own topic: <PREFIX>.<SERVICE_ID>
notifications_topic: "${TB_QUEUE_EDGE_NOTIFICATIONS_TOPIC:tb_edge.notifications}"
# For edge events messages
# Topic prefix for downlinks to be pushed to specific edge.
# Every edge has its own unique topic: <PREFIX>.<TENANT_ID>.<EDGE_ID>
event_notifications_topic: "${TB_QUEUE_EDGE_EVENT_NOTIFICATIONS_TOPIC:tb_edge_event.notifications}"
# Amount of partitions used by Edge services
partitions: "${TB_QUEUE_EDGE_PARTITIONS:10}"

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

@ -31,7 +31,6 @@ import org.thingsboard.server.common.data.cf.configuration.OutputType;
import org.thingsboard.server.common.data.cf.configuration.ReferencedEntityKey;
import org.thingsboard.server.common.data.cf.configuration.SimpleCalculatedFieldConfiguration;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.service.DaoSqlTest;
@ -85,7 +84,7 @@ public class CalculatedFieldControllerTest extends AbstractControllerTest {
assertThat(savedCalculatedField.getEntityId()).isEqualTo(calculatedField.getEntityId());
assertThat(savedCalculatedField.getType()).isEqualTo(calculatedField.getType());
assertThat(savedCalculatedField.getName()).isEqualTo(calculatedField.getName());
assertThat(savedCalculatedField.getConfiguration()).isEqualTo(getCalculatedFieldConfig(testDevice.getId()));
assertThat(savedCalculatedField.getConfiguration()).isEqualTo(getCalculatedFieldConfig());
assertThat(savedCalculatedField.getVersion()).isEqualTo(1L);
savedCalculatedField.setName("Test CF");
@ -134,16 +133,16 @@ public class CalculatedFieldControllerTest extends AbstractControllerTest {
calculatedField.setType(CalculatedFieldType.SIMPLE);
calculatedField.setName("Test Calculated Field");
calculatedField.setConfigurationVersion(1);
calculatedField.setConfiguration(getCalculatedFieldConfig(null));
calculatedField.setConfiguration(getCalculatedFieldConfig());
calculatedField.setVersion(1L);
return calculatedField;
}
private CalculatedFieldConfiguration getCalculatedFieldConfig(EntityId referencedEntityId) {
private CalculatedFieldConfiguration getCalculatedFieldConfig() {
SimpleCalculatedFieldConfiguration config = new SimpleCalculatedFieldConfiguration();
Argument argument = new Argument();
argument.setRefEntityId(referencedEntityId);
argument.setRefEntityId(null);
ReferencedEntityKey refEntityKey = new ReferencedEntityKey("temperature", ArgumentType.TS_LATEST, null);
argument.setRefEntityKey(refEntityKey);

8
application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java

@ -23,6 +23,7 @@ import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.TestPropertySource;
import org.thingsboard.common.util.JacksonUtil;
@ -94,6 +95,9 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest {
private DeviceProfileId mqttDeviceProfileId;
private DeviceProfileId coapDeviceProfileId;
@Value("${device.connectivity.gateway.image_version:3.7-stable}")
private String gatewayImageVersion;
@Before
public void beforeTest() throws Exception {
loginSysAdmin();
@ -298,7 +302,7 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest {
"services:\n" +
" # ThingsBoard IoT Gateway Service Configuration\n" +
" tb-gateway:\n" +
" image: thingsboard/tb-gateway\n" +
" image: thingsboard/tb-gateway:" + gatewayImageVersion + "\n" +
" container_name: tb-gateway\n" +
" restart: always\n" +
"\n" +
@ -847,7 +851,7 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest {
"-t \"application/json\" -e \"{temperature:25}\" coap://test.domain:5683/api/v1/%s/telemetry", credentials.getCredentialsId()));
assertThat(linuxCoapCommands.get(COAPS).get(0).asText()).isEqualTo("curl -f -S -o " + CA_ROOT_CERT_PEM + " http://localhost:80/api/device-connectivity/coaps/certificate/download");
assertThat(linuxCoapCommands.get(COAPS).get(1).asText()).isEqualTo(String.format("coap-client-openssl -v 6 -m POST " +
"-R "+ CA_ROOT_CERT_PEM + " -t \"application/json\" -e \"{temperature:25}\" coaps://test.domain:5684/api/v1/%s/telemetry", credentials.getCredentialsId()));
"-R " + CA_ROOT_CERT_PEM + " -t \"application/json\" -e \"{temperature:25}\" coaps://test.domain:5684/api/v1/%s/telemetry", credentials.getCredentialsId()));
JsonNode dockerCoapCommands = commands.get(COAP).get(DOCKER);
assertThat(dockerCoapCommands.get(COAP).asText()).isEqualTo(String.format("docker run --rm -it " +

4
application/src/test/java/org/thingsboard/server/controller/EdgeControllerTest.java

@ -885,6 +885,10 @@ public class EdgeControllerTest extends AbstractControllerTest {
device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class);
// create public customer
doPost("/api/customer/public/device/" + savedDevice.getId().getId(), Device.class);
doDelete("/api/customer/device/" + savedDevice.getId().getId(), Device.class);
simulateEdgeActivation(edge);
doPost("/api/edge/" + edge.getId().getId().toString()

11
application/src/test/java/org/thingsboard/server/controller/EntityQueryControllerTest.java

@ -499,7 +499,8 @@ public class EntityQueryControllerTest extends AbstractControllerTest {
);
EntityDataPageLink pageLink = new EntityDataPageLink(10, 0, null, sortOrder);
List<EntityKey> entityFields = Collections.singletonList(new EntityKey(EntityKeyType.ENTITY_FIELD, "name"));
List<EntityKey> latestValues = Collections.singletonList(new EntityKey(EntityKeyType.ATTRIBUTE, "temperature"));
List<EntityKey> latestValues = List.of(new EntityKey(EntityKeyType.ATTRIBUTE, "temperature"),
new EntityKey(EntityKeyType.ATTRIBUTE, "non-existing-attribute"));
EntityDataQuery query = new EntityDataQuery(filter, pageLink, entityFields, latestValues, null);
PageData<EntityData> data = findByQueryAndCheck(query, 67);
@ -517,6 +518,14 @@ public class EntityQueryControllerTest extends AbstractControllerTest {
List<String> deviceTemperatures = temperatures.stream().map(aLong -> Long.toString(aLong)).collect(Collectors.toList());
Assert.assertEquals(deviceTemperatures, loadedTemperatures);
// check ts value == 0, value is empty string for non-existing data points
List<TsValue> loadedNonExistingAttributes = loadedEntities.stream().map(entityData ->
entityData.getLatest().get(EntityKeyType.ATTRIBUTE).get("non-existing-attribute")).toList();
loadedNonExistingAttributes.forEach(tsValue -> {
assertThat(tsValue.getTs()).isEqualTo(0L);
assertThat(tsValue.getValue()).isEqualTo("");
});
pageLink = new EntityDataPageLink(10, 0, null, sortOrder);
KeyFilter highTemperatureFilter = new KeyFilter();
highTemperatureFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "temperature"));

14
application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java

@ -31,6 +31,7 @@ import org.thingsboard.server.service.ws.AuthCmd;
import org.thingsboard.server.service.ws.WsCmd;
import org.thingsboard.server.service.ws.WsCommandsWrapper;
import org.thingsboard.server.service.ws.telemetry.cmd.v1.AttributesSubscriptionCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v1.TimeseriesSubscriptionCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmCountUpdate;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountUpdate;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataCmd;
@ -38,6 +39,7 @@ import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataUpdate;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityHistoryCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.LatestValueCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.TimeSeriesCmd;
import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate;
import java.net.URI;
import java.nio.channels.NotYetConnectedException;
@ -271,6 +273,18 @@ public class TbTestWebSocketClient extends WebSocketClient {
return sendEntityDataQuery(edq);
}
public JsonNode sendTimeseriesCmd(EntityId entityId, String scope) {
log.warn("sendTimeseriesCmd entityId: {}, scope: {}", entityId, scope);
TimeseriesSubscriptionCmd cmd = new TimeseriesSubscriptionCmd(0, 0, 0, 10, null);
cmd.setEntityId(entityId.getId().toString());
cmd.setEntityType(entityId.getEntityType().toString());
cmd.setCmdId(1);
cmd.setScope(scope);
send(cmd);
String msg = this.waitForReply();
return JacksonUtil.fromString(msg, JsonNode.class);
}
public void send(WsCmd... cmds) {
WsCommandsWrapper cmdsWrapper = new WsCommandsWrapper();
cmdsWrapper.setCmds(List.of(cmds));

11
application/src/test/java/org/thingsboard/server/controller/UserControllerTest.java

@ -233,16 +233,9 @@ public class UserControllerTest extends AbstractControllerTest {
.put("password", "testPassword2");
Mockito.doNothing().when(mailService).sendPasswordWasResetEmail(anyString(), anyString());
JsonNode tokenInfo = readResponse(
doPost("/api/noauth/resetPassword", resetPasswordRequest)
.andExpect(status().isOk()), JsonNode.class);
doPost("/api/noauth/resetPassword", resetPasswordRequest)
.andExpect(status().isOk());
Mockito.verify(mailService).sendPasswordWasResetEmail(anyString(), anyString());
validateAndSetJwtToken(tokenInfo, email);
doGet("/api/auth/user")
.andExpect(status().isOk())
.andExpect(jsonPath("$.authority", is(Authority.TENANT_ADMIN.name())))
.andExpect(jsonPath("$.email", is(email)));
resetTokens();

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

@ -68,11 +68,13 @@ import org.thingsboard.server.service.ws.telemetry.cmd.v2.AlarmStatusUpdate;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountCmd;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountUpdate;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataUpdate;
import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@ -584,6 +586,33 @@ public class WebsocketApiTest extends AbstractControllerTest {
Assert.assertNull(msg);
}
@Test
public void testTimeseriesSubscriptionCmd() throws Exception {
long now = System.currentTimeMillis() - 100;
long lastTs = now - TimeUnit.MINUTES.toMillis(1);
TsKvEntry dataPoint1 = new BasicTsKvEntry(lastTs, new LongDataEntry("temperature", 42L));
sendTelemetry(device, List.of(dataPoint1));
JsonNode update = getWsClient().sendTimeseriesCmd(device.getId(), "LATEST_TELEMETRY");
JsonNode data = update.get("data");
Assert.assertEquals(1, data.size());
Assert.assertEquals(JacksonUtil.newArrayNode().add(lastTs).add("42"), data.get("temperature").get(0));
//Sending update from the past, while latest value has new timestamp;
TsKvEntry dataPoint4 = new BasicTsKvEntry(now - TimeUnit.MINUTES.toMillis(5), new LongDataEntry("temperature", 45L));
getWsClient().registerWaitForUpdate();
sendTelemetry(device, List.of(dataPoint4));
String msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1));
Assert.assertNull(msg);
//Sending duplicate update again
getWsClient().registerWaitForUpdate();
sendTelemetry(device, List.of(dataPoint4));
msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1));
Assert.assertNull(msg);
}
@Test
public void testEntityDataLatestTsWsCmd() throws Exception {
long now = System.currentTimeMillis();

4
application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java

@ -168,6 +168,10 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest {
Device savedDevice = saveDevice("Edge Device 1", THERMOSTAT_DEVICE_PROFILE_NAME);
// create public customer
doPost("/api/customer/public/device/" + savedDevice.getId().getId(), Device.class);
doDelete("/api/customer/device/" + savedDevice.getId().getId(), Device.class);
Asset savedAsset = saveAsset("Edge Asset 1");
updateRootRuleChainMetadata();

14
application/src/test/java/org/thingsboard/server/edge/TenantProfileEdgeTest.java

@ -44,9 +44,8 @@ public class TenantProfileEdgeTest extends AbstractEdgeTest {
@Test
public void testTenantProfiles() throws Exception {
loginSysAdmin();
// save current values into tmp to revert after test
TenantProfile edgeTenantProfile = doGet("/api/tenantProfile/" + tenantProfileId.getId(), TenantProfile.class);
TenantProfile originalTenantProfile = doGet("/api/tenantProfile/" + tenantProfileId.getId(), TenantProfile.class);
TenantProfile edgeTenantProfile = new TenantProfile(originalTenantProfile);
// updated edge tenant profile
edgeTenantProfile.setName("Tenant Profile Edge Test");
@ -64,14 +63,15 @@ public class TenantProfileEdgeTest extends AbstractEdgeTest {
Assert.assertEquals("Updated tenant profile Edge Test", tenantProfileMsg.getDescription());
Assert.assertEquals("Tenant Profile Edge Test", tenantProfileMsg.getName());
doPost("/api/tenantProfile", originalTenantProfile, TenantProfile.class);
loginTenantAdmin();
}
@Test
public void testIsolatedTenantProfile() throws Exception {
loginSysAdmin();
TenantProfile edgeTenantProfile = doGet("/api/tenantProfile/" + tenantProfileId.getId(), TenantProfile.class);
TenantProfile originalTenantProfile = doGet("/api/tenantProfile/" + tenantProfileId.getId(), TenantProfile.class);
TenantProfile edgeTenantProfile = new TenantProfile(originalTenantProfile);
// set tenant profile isolated and add 2 queues - main and isolated
edgeTenantProfile.setIsolatedTbRuleEngine(true);
@ -110,6 +110,10 @@ public class TenantProfileEdgeTest extends AbstractEdgeTest {
Assert.assertNotNull(queue);
Assert.assertEquals(tenantId, queue.getTenantId());
}
loginSysAdmin();
doPost("/api/tenantProfile", originalTenantProfile, TenantProfile.class);
loginTenantAdmin();
}
private TenantProfileQueueConfiguration createQueueConfig(String queueName, String queueTopic) {

156
application/src/test/java/org/thingsboard/server/service/edge/EdgeMsgConstructorUtilsTest.java

@ -0,0 +1,156 @@
/**
* Copyright © 2016-2025 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.edge;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.action.TbSaveToCustomCassandraTableNode;
import org.thingsboard.rule.engine.api.NodeConfiguration;
import org.thingsboard.rule.engine.api.TbNode;
import org.thingsboard.rule.engine.aws.lambda.TbAwsLambdaNode;
import org.thingsboard.rule.engine.filter.TbCheckRelationNode;
import org.thingsboard.rule.engine.flow.TbAckNode;
import org.thingsboard.rule.engine.math.TbMathNode;
import org.thingsboard.rule.engine.metadata.CalculateDeltaNode;
import org.thingsboard.rule.engine.metadata.TbGetTelemetryNode;
import org.thingsboard.rule.engine.rest.TbSendRestApiCallReplyNode;
import org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode;
import org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode;
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import java.lang.reflect.Constructor;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
import static org.thingsboard.server.service.edge.EdgeMsgConstructorUtils.EXCLUDED_NODES_BY_EDGE_VERSION;
import static org.thingsboard.server.service.edge.EdgeMsgConstructorUtils.IGNORED_PARAMS_BY_EDGE_VERSION;
@Slf4j
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class EdgeMsgConstructorUtilsTest {
private static final int CONFIGURATION_VERSION = 5;
static Stream<EdgeVersion> provideEdgeVersions() {
return Stream.of(
EdgeVersion.V_4_0_0,
EdgeVersion.V_3_9_0,
EdgeVersion.V_3_8_0,
EdgeVersion.V_3_7_0
);
}
private static final RuleChainMetaData RULE_CHAIN_META_DATA = new RuleChainMetaData();
private static final List<TbNode> TEST_NODES =
List.of(
new TbSaveToCustomCassandraTableNode(),
new TbMsgAttributesNode(),
new TbMsgTimeseriesNode(),
new TbSendRestApiCallReplyNode(),
new TbAwsLambdaNode(),
new TbMathNode(),
new CalculateDeltaNode(),
new TbAckNode(),
new TbCheckRelationNode(),
new TbGetTelemetryNode()
);
@BeforeAll
static void setUp() {
List<RuleNode> ruleNodes = TEST_NODES.stream()
.map(node -> {
RuleNode ruleNode = new RuleNode();
ruleNode.setName(node.getClass().getName());
ruleNode.setType(node.getClass().getName());
ruleNode.setConfigurationVersion(CONFIGURATION_VERSION);
ruleNode.setConfiguration(JacksonUtil.valueToTree(createDefaultConfiguration(node)));
return ruleNode;
})
.toList();
RULE_CHAIN_META_DATA.setFirstNodeIndex(0);
RULE_CHAIN_META_DATA.setNodes(ruleNodes);
}
private static NodeConfiguration<?> createDefaultConfiguration(TbNode node) {
try {
org.thingsboard.rule.engine.api.RuleNode annotation = node.getClass().getAnnotation(org.thingsboard.rule.engine.api.RuleNode.class);
Constructor<?> constructor = annotation.configClazz().getConstructor();
NodeConfiguration<?> configInstance = (NodeConfiguration<?>) constructor.newInstance();
return configInstance.defaultConfiguration();
} catch (Exception e) {
throw new RuntimeException("Exception during creating RuleNodeConfiguration for node - " + node, e);
}
}
@ParameterizedTest(name = "Test Sanitize Metadata For Edge: {0}")
@MethodSource("provideEdgeVersions")
@DisplayName("Test Sanitize Metadata For Legacy Edge Version")
public void testSanitizeMetadataForLegacyEdgeVersion(EdgeVersion edgeVersion) {
// WHEN
List<RuleNode> ruleNodes = sanitizeMetadataForLegacyEdgeVersion(edgeVersion);
// THEN
ruleNodes.forEach(ruleNode -> {
checkUpdateNodeConfigurationsForLegacyEdge(ruleNode, edgeVersion);
checkRemoveExcludedNodesForLegacyEdge(ruleNode, edgeVersion);
});
}
private List<RuleNode> sanitizeMetadataForLegacyEdgeVersion(EdgeVersion edgeVersion) {
String metadataUpdateMsg = EdgeMsgConstructorUtils.constructRuleChainMetadataUpdatedMsg(
UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE,
RULE_CHAIN_META_DATA,
edgeVersion
).getEntity();
RuleChainMetaData updatedMetaData = JacksonUtil.fromString(metadataUpdateMsg, RuleChainMetaData.class, true);
Assertions.assertNotNull(updatedMetaData, "RuleChainMetaData should not be null after update.");
return updatedMetaData.getNodes();
}
private void checkUpdateNodeConfigurationsForLegacyEdge(RuleNode ruleNode, EdgeVersion edgeVersion) {
if (IGNORED_PARAMS_BY_EDGE_VERSION.containsKey(edgeVersion) && IGNORED_PARAMS_BY_EDGE_VERSION.get(edgeVersion).containsKey(ruleNode.getType())) {
String ignoredParam = IGNORED_PARAMS_BY_EDGE_VERSION.get(edgeVersion).get(ruleNode.getType());
Assertions.assertFalse(ruleNode.getConfiguration().has(ignoredParam),
String.format("RuleNode '%s' for EdgeVersion '%s' should ignore '%s' config parameter.", ruleNode.getName(), edgeVersion, ignoredParam));
}
}
private void checkRemoveExcludedNodesForLegacyEdge(RuleNode ruleNode, EdgeVersion edgeVersion) {
boolean isNodeExcluded = Optional.ofNullable(EXCLUDED_NODES_BY_EDGE_VERSION.get(edgeVersion))
.map(excludedNodes -> !excludedNodes.contains(ruleNode.getType()))
.orElse(true);
Assertions.assertTrue(isNodeExcluded,
String.format("For EdgeVersion '%s', ruleNode '%s' should not be included.", edgeVersion, ruleNode.getType()));
}
}

21
application/src/test/java/org/thingsboard/server/service/entitiy/EntityServiceTest.java

@ -95,7 +95,6 @@ import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.dao.entityview.EntityViewDao;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.model.sqlts.ts.TsKvEntity;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.dao.sql.relation.RelationRepository;
@ -1664,7 +1663,7 @@ public class EntityServiceTest extends AbstractControllerTest {
List<ListenableFuture<TimeseriesSaveResult>> timeseriesFutures = new ArrayList<>();
for (int i = 0; i < devices.size(); i++) {
Device device = devices.get(i);
timeseriesFutures.add(saveLongTimeseries(device.getId(), "temperature", temperatures.get(i)));
timeseriesFutures.add(saveTimeseries(device.getId(), "temperature", temperatures.get(i)));
}
Futures.allAsList(timeseriesFutures).get();
@ -1711,6 +1710,13 @@ public class EntityServiceTest extends AbstractControllerTest {
List<String> entitiesTelemetry = loadedEntities.stream().map(entityData -> entityData.getLatest().get(EntityKeyType.TIME_SERIES).get("temperature").getValue()).toList();
assertThat(entitiesTelemetry).containsExactlyElementsOf(expectedSortedList);
// update temperature to long value for one of device
long longTempValue = -100L;
saveTimeseries(devices.get(new Random().nextInt(66)).getId(), "temperature", longTempValue).get();
loadedEntities = loadAllData(querySortByTemp, deviceTemperatures.size());
entitiesTelemetry = loadedEntities.stream().map(entityData -> entityData.getLatest().get(EntityKeyType.TIME_SERIES).get("temperature").getValue()).toList();
assertThat(entitiesTelemetry.get(0)).isEqualTo(String.valueOf(longTempValue));
deviceService.deleteDevicesByTenantId(tenantId);
}
@ -2252,15 +2258,18 @@ public class EntityServiceTest extends AbstractControllerTest {
return attributesService.save(tenantId, entityId, scope, Collections.singletonList(attr));
}
private ListenableFuture<TimeseriesSaveResult> saveLongTimeseries(EntityId entityId, String key, Double value) {
TsKvEntity tsKv = new TsKvEntity();
tsKv.setStrKey(key);
tsKv.setDoubleValue(value);
private ListenableFuture<TimeseriesSaveResult> saveTimeseries(EntityId entityId, String key, Double value) {
KvEntry telemetryValue = new DoubleDataEntry(key, value);
BasicTsKvEntry timeseries = new BasicTsKvEntry(42L, telemetryValue);
return timeseriesService.save(tenantId, entityId, timeseries);
}
private ListenableFuture<TimeseriesSaveResult> saveTimeseries(EntityId entityId, String key, Long value) {
KvEntry telemetryValue = new LongDataEntry(key, value);
BasicTsKvEntry timeseries = new BasicTsKvEntry(42L, telemetryValue);
return timeseriesService.save(tenantId, entityId, timeseries);
}
protected void createMultiRootHierarchy(List<Asset> buildings, List<Asset> apartments,
Map<String, Map<UUID, String>> entityNameByTypeMap,
Map<UUID, UUID> childParentRelationMap) throws InterruptedException {

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

@ -621,6 +621,7 @@ public class VersionControlTest extends AbstractControllerTest {
assertThat(importedField.getName()).isEqualTo(deviceCalculatedField.getName());
assertThat(importedField.getType()).isEqualTo(deviceCalculatedField.getType());
assertThat(importedField.getId()).isNotEqualTo(deviceCalculatedField.getId());
assertThat(importedField.getConfiguration().getArguments().get("T").getRefEntityId()).isEqualTo(importedAsset.getId());
});
List<CalculatedField> importedAssetCalculatedFields = findCalculatedFieldsByEntityId(importedAsset.getId());
@ -629,6 +630,7 @@ public class VersionControlTest extends AbstractControllerTest {
assertThat(importedField.getName()).isEqualTo(assetCalculatedField.getName());
assertThat(importedField.getType()).isEqualTo(assetCalculatedField.getType());
assertThat(importedField.getId()).isNotEqualTo(assetCalculatedField.getId());
assertThat(importedField.getConfiguration().getArguments().get("T").getRefEntityId()).isEqualTo(importedDevice.getId());
});
}

2
common/dao-api/src/main/java/org/thingsboard/server/dao/customer/CustomerService.java

@ -41,6 +41,8 @@ public interface CustomerService extends EntityDaoService {
Customer findOrCreatePublicCustomer(TenantId tenantId);
Customer findPublicCustomer(TenantId tenantId);
PageData<Customer> findCustomersByTenantId(TenantId tenantId, PageLink pageLink);
void deleteCustomersByTenantId(TenantId tenantId);

2
common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java

@ -148,4 +148,6 @@ public class DataConstants {
public static final String CF_QUEUE_NAME = "CalculatedFields";
public static final String CF_STATES_QUEUE_NAME = "CalculatedFieldStates";
public static final String LATEST_TELEMETRY_SCOPE = "LATEST_TELEMETRY";
}

4
common/data/src/main/java/org/thingsboard/server/common/data/JavaSerDesUtil.java

@ -36,7 +36,7 @@ public class JavaSerDesUtil {
try (ObjectInputStream ois = new ObjectInputStream(is)) {
return (T) ois.readObject();
} catch (IOException | ClassNotFoundException e) {
log.error("Error during deserialization message, [{}]", e.getMessage());
log.error("Error during deserialization", e);
return null;
}
}
@ -50,7 +50,7 @@ public class JavaSerDesUtil {
ois.writeObject(msq);
return boas.toByteArray();
} catch (IOException e) {
log.error("Error during serialization message, [{}]", e.getMessage());
log.error("Error during serialization", e);
throw new RuntimeException(e);
}
}

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

@ -64,7 +64,7 @@ public class CalculatedField extends BaseData<CalculatedFieldId> implements HasN
@Schema(description = "Version of calculated field configuration.", example = "0")
private int configurationVersion;
@Schema(implementation = SimpleCalculatedFieldConfiguration.class)
private transient CalculatedFieldConfiguration configuration;
private CalculatedFieldConfiguration configuration;
@Getter
@Setter
private Long version;
@ -112,6 +112,18 @@ public class CalculatedField extends BaseData<CalculatedFieldId> implements HasN
return super.getCreatedTime();
}
// Getter is ignored for serialization
@JsonIgnore
public boolean isDebugMode() {
return debugMode;
}
// Setter is annotated for deserialization
@JsonSetter
public void setDebugMode(boolean debugMode) {
this.debugMode = debugMode;
}
@Override
public String toString() {
return new StringBuilder()
@ -128,16 +140,4 @@ public class CalculatedField extends BaseData<CalculatedFieldId> implements HasN
.toString();
}
// Getter is ignored for serialization
@JsonIgnore
public boolean isDebugMode() {
return debugMode;
}
// Setter is annotated for deserialization
@JsonSetter
public void setDebugMode(boolean debugMode) {
this.debugMode = debugMode;
}
}

3
common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/ScriptCalculatedFieldConfiguration.java

@ -16,13 +16,16 @@
package org.thingsboard.server.common.data.cf.configuration;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.cf.CalculatedFieldType;
@Data
@EqualsAndHashCode(callSuper = true)
public class ScriptCalculatedFieldConfiguration extends BaseCalculatedFieldConfiguration implements CalculatedFieldConfiguration {
@Override
public CalculatedFieldType getType() {
return CalculatedFieldType.SCRIPT;
}
}

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

@ -16,13 +16,16 @@
package org.thingsboard.server.common.data.cf.configuration;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.cf.CalculatedFieldType;
@Data
@EqualsAndHashCode(callSuper = true)
public class SimpleCalculatedFieldConfiguration extends BaseCalculatedFieldConfiguration implements CalculatedFieldConfiguration {
@Override
public CalculatedFieldType getType() {
return CalculatedFieldType.SIMPLE;
}
}

2
common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/FieldsUtil.java

@ -289,7 +289,7 @@ public class FieldsUtil {
}
public static String getText(JsonNode node) {
return node != null ? node.toString() : "";
return node != null && !node.isNull() ? node.toString() : "";
}
private static UUID getCustomerId(CustomerId customerId) {

5
common/data/src/main/java/org/thingsboard/server/common/data/edqs/fields/UserFields.java

@ -35,6 +35,11 @@ public class UserFields extends AbstractEntityFields {
private String phone;
private String additionalInfo;
@Override
public String getName() {
return super.getEmail();
}
public UserFields(UUID id, long createdTime, UUID tenantId, UUID customerId,
Long version, String firstName, String lastName, String email,
String phone, JsonNode additionalInfo) {

3
common/data/src/main/java/org/thingsboard/server/common/data/event/CalculatedFieldDebugEventFilter.java

@ -37,10 +37,9 @@ public class CalculatedFieldDebugEventFilter extends DebugEventFilter {
example = "{\"x\":{\"ts\":1739432016629,\"value\":20},\"y\":{\"ts\":1739429717656,\"value\":12}}")
protected String arguments;
@Schema(description = "String value representing the result of a calculation",
example = "{\"x + y\":54}")
example = "{\"x + y\":32}")
protected String result;
@Override
public EventType getEventType() {
return EventType.DEBUG_CALCULATED_FIELD;

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

@ -107,7 +107,7 @@ public class EntityExportData<E extends ExportableEntity<? extends EntityId>> {
@JsonIgnore
public boolean hasCalculatedFields() {
return calculatedFields != null;
return calculatedFields != null && !calculatedFields.isEmpty();
}
}

6
common/edqs/src/main/java/org/thingsboard/server/edqs/data/dp/BoolDataPoint.java

@ -46,6 +46,10 @@ public class BoolDataPoint extends AbstractDataPoint {
@Override
public int compareTo(DataPoint dataPoint) {
return Boolean.compare(value, dataPoint.getBool());
if (dataPoint.getType() == DataType.BOOLEAN) {
return Boolean.compare(value, dataPoint.getBool());
} else {
return super.compareTo(dataPoint);
}
}
}

3
common/edqs/src/main/java/org/thingsboard/server/edqs/data/dp/CompressedStringDataPoint.java

@ -17,13 +17,12 @@ package org.thingsboard.server.edqs.data.dp;
import lombok.Getter;
import lombok.SneakyThrows;
import org.thingsboard.common.util.TbBytePool;
import org.thingsboard.server.common.data.kv.DataType;
import org.thingsboard.server.edqs.util.TbBytePool;
import org.xerial.snappy.Snappy;
public class CompressedStringDataPoint extends AbstractDataPoint {
public static final int MIN_STR_SIZE_TO_COMPRESS = 512;
@Getter
private final byte[] compressedValue;

6
common/edqs/src/main/java/org/thingsboard/server/edqs/data/dp/DoubleDataPoint.java

@ -46,6 +46,10 @@ public class DoubleDataPoint extends AbstractDataPoint {
@Override
public int compareTo(DataPoint dataPoint) {
return Double.compare(value, dataPoint.getDouble());
if (dataPoint.getType() == DataType.DOUBLE || dataPoint.getType() == DataType.LONG) {
return Double.compare(value, dataPoint.getDouble());
} else {
return super.compareTo(dataPoint);
}
}
}

2
common/edqs/src/main/java/org/thingsboard/server/edqs/data/dp/JsonDataPoint.java

@ -17,7 +17,7 @@ package org.thingsboard.server.edqs.data.dp;
import lombok.Getter;
import org.thingsboard.server.common.data.kv.DataType;
import org.thingsboard.server.edqs.util.TbStringPool;
import org.thingsboard.common.util.TbStringPool;
public class JsonDataPoint extends AbstractDataPoint {

8
common/edqs/src/main/java/org/thingsboard/server/edqs/data/dp/LongDataPoint.java

@ -51,6 +51,12 @@ public class LongDataPoint extends AbstractDataPoint {
@Override
public int compareTo(DataPoint dataPoint) {
return Long.compare(value, dataPoint.getLong());
if (dataPoint.getType() == DataType.DOUBLE) {
return Double.compare(getDouble(), dataPoint.getDouble());
} else if (dataPoint.getType() == DataType.LONG) {
return Long.compare(value, dataPoint.getLong());
} else {
return super.compareTo(dataPoint);
}
}
}

2
common/edqs/src/main/java/org/thingsboard/server/edqs/data/dp/StringDataPoint.java

@ -17,7 +17,7 @@ package org.thingsboard.server.edqs.data.dp;
import lombok.Getter;
import org.thingsboard.server.common.data.kv.DataType;
import org.thingsboard.server.edqs.util.TbStringPool;
import org.thingsboard.common.util.TbStringPool;
public class StringDataPoint extends AbstractDataPoint {

5
common/edqs/src/main/java/org/thingsboard/server/edqs/repo/DefaultEdqsRepository.java

@ -27,10 +27,9 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.EntityCountQuery;
import org.thingsboard.server.common.data.query.EntityDataQuery;
import org.thingsboard.server.edqs.stats.EdqsStatsService;
import org.thingsboard.server.common.stats.EdqsStatsService;
import org.thingsboard.server.queue.edqs.EdqsComponent;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Predicate;
@ -42,7 +41,7 @@ import java.util.function.Predicate;
public class DefaultEdqsRepository implements EdqsRepository {
private final static ConcurrentMap<TenantId, TenantRepo> repos = new ConcurrentHashMap<>();
private final Optional<EdqsStatsService> statsService;
private final EdqsStatsService statsService;
public TenantRepo get(TenantId tenantId) {
return repos.computeIfAbsent(tenantId, id -> new TenantRepo(id, statsService));

34
common/edqs/src/main/java/org/thingsboard/server/edqs/repo/TenantRepo.java

@ -25,7 +25,6 @@ import org.thingsboard.server.common.data.edqs.EdqsEventType;
import org.thingsboard.server.common.data.edqs.EdqsObject;
import org.thingsboard.server.common.data.edqs.Entity;
import org.thingsboard.server.common.data.edqs.LatestTsKv;
import org.thingsboard.server.common.data.edqs.fields.AssetFields;
import org.thingsboard.server.common.data.edqs.fields.EntityFields;
import org.thingsboard.server.common.data.edqs.query.QueryResult;
import org.thingsboard.server.common.data.id.CustomerId;
@ -41,6 +40,7 @@ import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.common.data.query.TsValue;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.stats.EdqsStatsService;
import org.thingsboard.server.edqs.data.ApiUsageStateData;
import org.thingsboard.server.edqs.data.AssetData;
import org.thingsboard.server.edqs.data.CustomerData;
@ -55,9 +55,7 @@ import org.thingsboard.server.edqs.query.EdqsQuery;
import org.thingsboard.server.edqs.query.SortableEntityData;
import org.thingsboard.server.edqs.query.processor.EntityQueryProcessor;
import org.thingsboard.server.edqs.query.processor.EntityQueryProcessorFactory;
import org.thingsboard.server.edqs.stats.EdqsStatsService;
import org.thingsboard.server.edqs.util.RepositoryUtils;
import org.thingsboard.server.edqs.util.TbStringPool;
import java.util.ArrayList;
import java.util.Collections;
@ -65,7 +63,6 @@ import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.UUID;
@ -96,9 +93,9 @@ public class TenantRepo {
private final Lock entityUpdateLock = new ReentrantLock();
private final TenantId tenantId;
private final Optional<EdqsStatsService> edqsStatsService;
private final EdqsStatsService edqsStatsService;
public TenantRepo(TenantId tenantId, Optional<EdqsStatsService> edqsStatsService) {
public TenantRepo(TenantId tenantId, EdqsStatsService edqsStatsService) {
this.tenantId = tenantId;
this.edqsStatsService = edqsStatsService;
}
@ -144,9 +141,9 @@ public class TenantRepo {
RelationsRepo repo = relations.computeIfAbsent(entity.getTypeGroup(), tg -> new RelationsRepo());
EntityData<?> from = getOrCreate(entity.getFrom());
EntityData<?> to = getOrCreate(entity.getTo());
boolean added = repo.add(from, to, TbStringPool.intern(entity.getType()));
boolean added = repo.add(from, to, entity.getType());
if (added) {
edqsStatsService.ifPresent(statService -> statService.reportEvent(tenantId, ObjectType.RELATION, EdqsEventType.UPDATED));
edqsStatsService.reportAdded(ObjectType.RELATION);
}
} else if (RelationTypeGroup.DASHBOARD.equals(entity.getTypeGroup())) {
if (EntityRelation.CONTAINS_TYPE.equals(entity.getType()) && entity.getFrom().getEntityType() == EntityType.CUSTOMER) {
@ -166,7 +163,7 @@ public class TenantRepo {
if (relationsRepo != null) {
boolean removed = relationsRepo.remove(entityRelation.getFrom().getId(), entityRelation.getTo().getId(), entityRelation.getType());
if (removed) {
edqsStatsService.ifPresent(statService -> statService.reportEvent(tenantId, ObjectType.RELATION, EdqsEventType.DELETED));
edqsStatsService.reportRemoved(ObjectType.RELATION);
}
}
} else if (RelationTypeGroup.DASHBOARD.equals(entityRelation.getTypeGroup())) {
@ -188,7 +185,6 @@ public class TenantRepo {
EntityType entityType = entity.getType();
EntityData entityData = getOrCreate(entityType, entityId);
processFields(fields);
EntityFields oldFields = entityData.getFields();
entityData.setFields(fields);
if (oldFields == null) {
@ -225,7 +221,7 @@ public class TenantRepo {
if (removed.getFields() != null) {
getEntitySet(entityType).remove(removed);
}
edqsStatsService.ifPresent(statService -> statService.reportEvent(tenantId, ObjectType.fromEntityType(entityType), EdqsEventType.DELETED));
edqsStatsService.reportRemoved(entity.type());
UUID customerId = removed.getCustomerId();
if (customerId != null) {
@ -246,7 +242,7 @@ public class TenantRepo {
Integer keyId = KeyDictionary.get(attributeKv.getKey());
boolean added = entityData.putAttr(keyId, attributeKv.getScope(), attributeKv.getDataPoint());
if (added) {
edqsStatsService.ifPresent(statService -> statService.reportEvent(tenantId, ObjectType.ATTRIBUTE_KV, EdqsEventType.UPDATED));
edqsStatsService.reportAdded(ObjectType.ATTRIBUTE_KV);
}
}
}
@ -256,7 +252,7 @@ public class TenantRepo {
if (entityData != null) {
boolean removed = entityData.removeAttr(KeyDictionary.get(attributeKv.getKey()), attributeKv.getScope());
if (removed) {
edqsStatsService.ifPresent(statService -> statService.reportEvent(tenantId, ObjectType.ATTRIBUTE_KV, EdqsEventType.DELETED));
edqsStatsService.reportRemoved(ObjectType.ATTRIBUTE_KV);
}
}
}
@ -267,7 +263,7 @@ public class TenantRepo {
Integer keyId = KeyDictionary.get(latestTsKv.getKey());
boolean added = entityData.putTs(keyId, latestTsKv.getDataPoint());
if (added) {
edqsStatsService.ifPresent(statService -> statService.reportEvent(tenantId, ObjectType.LATEST_TS_KV, EdqsEventType.UPDATED));
edqsStatsService.reportAdded(ObjectType.LATEST_TS_KV);
}
}
}
@ -277,17 +273,11 @@ public class TenantRepo {
if (entityData != null) {
boolean removed = entityData.removeTs(KeyDictionary.get(latestTsKv.getKey()));
if (removed) {
edqsStatsService.ifPresent(statService -> statService.reportEvent(tenantId, ObjectType.LATEST_TS_KV, EdqsEventType.DELETED));
edqsStatsService.reportRemoved(ObjectType.LATEST_TS_KV);
}
}
}
public void processFields(EntityFields fields) {
if (fields instanceof AssetFields assetFields) {
assetFields.setType(TbStringPool.intern(assetFields.getType()));
}
}
public ConcurrentMap<UUID, EntityData<?>> getEntityMap(EntityType entityType) {
return entityMapByType.computeIfAbsent(entityType, et -> new ConcurrentHashMap<>());
}
@ -301,7 +291,7 @@ public class TenantRepo {
return getEntityMap(entityType).computeIfAbsent(entityId, id -> {
log.debug("[{}] Adding {} {}", tenantId, entityType, id);
EntityData<?> entityData = constructEntityData(entityType, entityId);
edqsStatsService.ifPresent(statService -> statService.reportEvent(tenantId, ObjectType.fromEntityType(entityType), EdqsEventType.UPDATED));
edqsStatsService.reportAdded(ObjectType.fromEntityType(entityType));
return entityData;
});
}

92
common/edqs/src/main/java/org/thingsboard/server/edqs/stats/DefaultEdqsStatsService.java

@ -0,0 +1,92 @@
/**
* Copyright © 2016-2025 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.edqs.stats;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.ObjectType;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.query.EntityCountQuery;
import org.thingsboard.server.common.data.query.EntityDataQuery;
import org.thingsboard.server.common.stats.EdqsStatsService;
import org.thingsboard.server.common.stats.StatsFactory;
import org.thingsboard.server.common.stats.StatsTimer;
import org.thingsboard.server.common.stats.StatsType;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@Service
@Slf4j
@ConditionalOnExpression("'${queue.edqs.api.supported:true}' == 'true' && '${queue.edqs.stats.enabled:true}' == 'true'")
public class DefaultEdqsStatsService implements EdqsStatsService {
private final StatsFactory statsFactory;
@Value("${queue.edqs.stats.slow_query_threshold:3000}")
private int slowQueryThreshold;
private final ConcurrentHashMap<ObjectType, AtomicInteger> objectCounters = new ConcurrentHashMap<>();
private final StatsTimer dataQueryTimer;
private final StatsTimer countQueryTimer;
private DefaultEdqsStatsService(StatsFactory statsFactory) {
this.statsFactory = statsFactory;
dataQueryTimer = statsFactory.createTimer(StatsType.EDQS, "entityDataQueryTimer");
countQueryTimer = statsFactory.createTimer(StatsType.EDQS, "entityCountQueryTimer");
}
@Override
public void reportAdded(ObjectType objectType) {
getObjectCounter(objectType).incrementAndGet();
}
@Override
public void reportRemoved(ObjectType objectType) {
getObjectCounter(objectType).decrementAndGet();
}
@Override
public void reportDataQuery(TenantId tenantId, EntityDataQuery query, long timingNanos) {
double timingMs = timingNanos / 1000_000.0;
if (timingMs < slowQueryThreshold) {
log.debug("[{}] Executed data query in {} ms: {}", tenantId, timingMs, query);
} else {
log.warn("[{}] Executed slow data query in {} ms: {}", tenantId, timingMs, query);
}
dataQueryTimer.record(timingNanos, TimeUnit.NANOSECONDS);
}
@Override
public void reportCountQuery(TenantId tenantId, EntityCountQuery query, long timingNanos) {
double timingMs = timingNanos / 1000_000.0;
if (timingMs < slowQueryThreshold) {
log.debug("[{}] Executed count query in {} ms: {}", tenantId, timingMs, query);
} else {
log.warn("[{}] Executed slow count query in {} ms: {}", tenantId, timingMs, query);
}
countQueryTimer.record(timingNanos, TimeUnit.NANOSECONDS);
}
private AtomicInteger getObjectCounter(ObjectType objectType) {
return objectCounters.computeIfAbsent(objectType, type ->
statsFactory.createGauge("edqsObjectsCount", new AtomicInteger(), "objectType", type.name()));
}
}

81
common/edqs/src/main/java/org/thingsboard/server/edqs/stats/EdqsStatsService.java

@ -1,81 +0,0 @@
/**
* Copyright © 2016-2025 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.edqs.stats;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.ObjectType;
import org.thingsboard.server.common.data.edqs.EdqsEventType;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.stats.StatsFactory;
import org.thingsboard.server.common.stats.StatsType;
import org.thingsboard.server.queue.edqs.EdqsComponent;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
@EdqsComponent
@Service
@Slf4j
@RequiredArgsConstructor
@ConditionalOnProperty(name = "queue.edqs.stats.enabled", havingValue = "true", matchIfMissing = true)
public class EdqsStatsService {
private final ConcurrentHashMap<TenantId, EdqsStats> statsMap = new ConcurrentHashMap<>();
private final StatsFactory statsFactory;
public void reportEvent(TenantId tenantId, ObjectType objectType, EdqsEventType eventType) {
statsMap.computeIfAbsent(tenantId, id -> new EdqsStats(tenantId, statsFactory))
.reportEvent(objectType, eventType);
}
@Getter
@AllArgsConstructor
static class EdqsStats {
private final TenantId tenantId;
private final ConcurrentHashMap<ObjectType, AtomicInteger> entityCounters = new ConcurrentHashMap<>();
private final StatsFactory statsFactory;
private AtomicInteger getOrCreateObjectCounter(ObjectType objectType) {
return entityCounters.computeIfAbsent(objectType,
type -> statsFactory.createGauge(StatsType.EDQS.getName() + "_object_count", new AtomicInteger(),
"tenantId", tenantId.toString(), "objectType", type.name()));
}
@Override
public String toString() {
return entityCounters.entrySet().stream()
.map(counters -> counters.getKey().name()+ " total = [" + counters.getValue() + "]")
.collect(Collectors.joining(", "));
}
public void reportEvent(ObjectType objectType, EdqsEventType eventType) {
AtomicInteger objectCounter = getOrCreateObjectCounter(objectType);
if (eventType == EdqsEventType.UPDATED){
objectCounter.incrementAndGet();
} else if (eventType == EdqsEventType.DELETED) {
objectCounter.decrementAndGet();
}
}
}
}

45
common/edqs/src/main/java/org/thingsboard/server/edqs/util/EdqsConverter.java

@ -17,13 +17,19 @@ package org.thingsboard.server.edqs.util;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.google.protobuf.ByteString;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.TbStringPool;
import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.ObjectType;
@ -49,6 +55,7 @@ import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.DataPointProto;
import org.xerial.snappy.Snappy;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@ -57,6 +64,9 @@ import java.util.UUID;
@Slf4j
public class EdqsConverter {
@Value("${queue.edqs.string_compression_length_threshold:512}")
private int stringCompressionLengthThreshold;
private final Map<ObjectType, Converter<? extends EdqsObject>> converters = new HashMap<>();
private final Converter<Entity> defaultConverter = new JsonConverter<>(Entity.class);
@ -125,7 +135,7 @@ public class EdqsConverter {
});
}
public static DataPointProto toDataPointProto(long ts, KvEntry kvEntry) {
public DataPointProto toDataPointProto(long ts, KvEntry kvEntry) {
DataPointProto.Builder proto = DataPointProto.newBuilder();
proto.setTs(ts);
switch (kvEntry.getDataType()) {
@ -134,7 +144,7 @@ public class EdqsConverter {
case DOUBLE -> proto.setDoubleV(kvEntry.getDoubleValue().get());
case STRING -> {
String strValue = kvEntry.getStrValue().get();
if (strValue.length() < CompressedStringDataPoint.MIN_STR_SIZE_TO_COMPRESS) {
if (strValue.length() < stringCompressionLengthThreshold) {
proto.setStringV(strValue);
} else {
proto.setCompressedStringV(ByteString.copyFrom(compress(strValue)));
@ -142,7 +152,7 @@ public class EdqsConverter {
}
case JSON -> {
String jsonValue = kvEntry.getJsonValue().get();
if (jsonValue.length() < CompressedStringDataPoint.MIN_STR_SIZE_TO_COMPRESS) {
if (jsonValue.length() < stringCompressionLengthThreshold) {
proto.setJsonV(jsonValue);
} else {
proto.setCompressedJsonV(ByteString.copyFrom(compress(jsonValue)));
@ -152,7 +162,7 @@ public class EdqsConverter {
return proto.build();
}
public static DataPoint fromDataPointProto(DataPointProto proto) {
public DataPoint fromDataPointProto(DataPointProto proto) {
long ts = proto.getTs();
if (proto.hasBoolV()) {
return new BoolDataPoint(ts, proto.getBoolV());
@ -188,14 +198,6 @@ public class EdqsConverter {
return edqsEntity;
}
public EdqsObject check(ObjectType type, Object object) {
if (object instanceof EdqsObject edqsObject) {
return edqsObject;
} else {
return toEntity(type.toEntityType(), object);
}
}
@SuppressWarnings("unchecked")
@SneakyThrows
public <T extends EdqsObject> byte[] serialize(ObjectType type, T value) {
@ -220,12 +222,18 @@ public class EdqsConverter {
@RequiredArgsConstructor
private static class JsonConverter<T> implements Converter<T> {
private static final SimpleModule module = new SimpleModule();
private static final ObjectMapper mapper = JsonMapper.builder()
.visibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY)
.visibility(PropertyAccessor.GETTER, JsonAutoDetect.Visibility.NONE)
.visibility(PropertyAccessor.IS_GETTER, JsonAutoDetect.Visibility.NONE)
.build();
static {
module.addDeserializer(String.class, new InterningStringDeserializer());
mapper.registerModule(module);
}
private final Class<T> type;
@SneakyThrows
@ -250,4 +258,17 @@ public class EdqsConverter {
}
public static class InterningStringDeserializer extends StdDeserializer<String> {
public InterningStringDeserializer() {
super(String.class);
}
@Override
public String deserialize(JsonParser p, DeserializationContext ctx) throws IOException {
return TbStringPool.intern(p.getText());
}
}
}

2
common/edqs/src/main/java/org/thingsboard/server/edqs/util/RepositoryUtils.java

@ -344,7 +344,7 @@ public class RepositoryUtils {
if (dp != null) {
return new TsValue(dp.getTs() > 0 ? dp.getTs() : ts, dp.valueToString());
} else {
return new TsValue(ts, "");
return TsValue.EMPTY;
}
}

4
common/queue/src/main/java/org/thingsboard/server/queue/discovery/TopicService.java

@ -38,7 +38,7 @@ public class TopicService {
@Value("${queue.rule-engine.notifications-topic:tb_rule_engine.notifications}")
private String tbRuleEngineNotificationsTopic;
@Value("${queue.transport.notifications-topics:tb_transport.notifications}")
@Value("${queue.transport.notifications-topic:tb_transport.notifications}")
private String tbTransportNotificationsTopic;
@Value("${queue.edge.notifications-topic:tb_edge.notifications}")
@ -47,7 +47,7 @@ public class TopicService {
@Value("${queue.edge.event-notifications-topic:tb_edge_event.notifications}")
private String tbEdgeEventNotificationsTopic;
@Value("${queue.calculated_fields.notifications-topic:calculated_field.notifications}")
@Value("${queue.calculated-fields.notifications-topic:calculated_field.notifications}")
private String tbCalculatedFieldNotificationsTopic;
private final ConcurrentMap<String, TopicPartitionInfo> tbCoreNotificationTopics = new ConcurrentHashMap<>();

24
common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaSettings.java

@ -50,8 +50,6 @@ import java.util.Properties;
@Component
public class TbKafkaSettings {
private static final List<String> DYNAMIC_TOPICS = List.of("tb_edge_event.notifications");
@Value("${queue.kafka.bootstrap.servers}")
private String servers;
@ -163,18 +161,20 @@ public class TbKafkaSettings {
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, ByteArrayDeserializer.class);
consumerPropertiesPerTopic
.getOrDefault(topic, Collections.emptyList())
.forEach(kv -> props.put(kv.getKey(), kv.getValue()));
if (topic != null) {
DYNAMIC_TOPICS.stream()
.filter(topic::startsWith)
.findFirst()
.ifPresent(prefix -> consumerPropertiesPerTopic.getOrDefault(prefix, Collections.emptyList())
.forEach(kv -> props.put(kv.getKey(), kv.getValue())));
List<TbProperty> properties = consumerPropertiesPerTopic.get(topic);
if (properties == null) {
for (Map.Entry<String, List<TbProperty>> entry : consumerPropertiesPerTopic.entrySet()) {
if (topic.startsWith(entry.getKey())) {
properties = entry.getValue();
break;
}
}
}
if (properties != null) {
properties.forEach(kv -> props.put(kv.getKey(), kv.getValue()));
}
}
return props;
}

1
common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueProducerProvider.java

@ -115,7 +115,6 @@ public class TbCoreQueueProducerProvider implements TbQueueProducerProvider {
return toHousekeeper;
}
@Override
public TbQueueProducer<TbProtoQueueMsg<ToEdgeMsg>> getTbEdgeMsgProducer() {
return toEdge;

41
common/stats/src/main/java/org/thingsboard/server/common/stats/DummyEdqsStatsService.java

@ -0,0 +1,41 @@
/**
* Copyright © 2016-2025 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.stats;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.ObjectType;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.query.EntityCountQuery;
import org.thingsboard.server.common.data.query.EntityDataQuery;
@Service
@ConditionalOnMissingBean(value = EdqsStatsService.class, ignored = DummyEdqsStatsService.class)
public class DummyEdqsStatsService implements EdqsStatsService {
@Override
public void reportAdded(ObjectType objectType) {}
@Override
public void reportRemoved(ObjectType objectType) {}
@Override
public void reportDataQuery(TenantId tenantId, EntityDataQuery query, long timingNanos) {}
@Override
public void reportCountQuery(TenantId tenantId, EntityCountQuery query, long timingNanos) {}
}

33
common/stats/src/main/java/org/thingsboard/server/common/stats/EdqsStatsService.java

@ -0,0 +1,33 @@
/**
* Copyright © 2016-2025 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.stats;
import org.thingsboard.server.common.data.ObjectType;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.query.EntityCountQuery;
import org.thingsboard.server.common.data.query.EntityDataQuery;
public interface EdqsStatsService {
void reportAdded(ObjectType objectType);
void reportRemoved(ObjectType objectType);
void reportDataQuery(TenantId tenantId, EntityDataQuery query, long timingNanos);
void reportCountQuery(TenantId tenantId, EntityCountQuery query, long timingNanos);
}

12
common/stats/src/main/java/org/thingsboard/server/common/stats/StatsTimer.java

@ -34,10 +34,14 @@ public class StatsTimer {
this.timer = micrometerTimer;
}
public void record(long timeMs) {
public void record(long timeMs) {
record(timeMs, TimeUnit.MILLISECONDS);
}
public void record(long timing, TimeUnit timeUnit) {
count++;
totalTime += timeMs;
timer.record(timeMs, TimeUnit.MILLISECONDS);
totalTime += timeUnit.toMillis(timing);
timer.record(timing, timeUnit);
}
public double getAvg() {
@ -47,7 +51,7 @@ public class StatsTimer {
return (double) totalTime / count;
}
public void reset() {
public void reset() {
count = 0;
totalTime = 0;
}

5
common/util/pom.xml

@ -111,6 +111,11 @@
<groupId>org.locationtech.jts</groupId>
<artifactId>jts-core</artifactId>
</dependency>
<dependency>
<groupId>net.objecthunter</groupId>
<artifactId>exp4j</artifactId>
<version>${exp4j.version}</version>
</dependency>
</dependencies>
<build>

78
common/util/src/main/java/org/thingsboard/common/util/ExpressionFunctionsUtil.java

@ -0,0 +1,78 @@
/**
* Copyright © 2016-2025 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.common.util;
import net.objecthunter.exp4j.function.Function;
import net.objecthunter.exp4j.function.Functions;
import java.util.ArrayList;
import java.util.List;
public class ExpressionFunctionsUtil {
public static final List<Function> userDefinedFunctions = new ArrayList<>();
static {
userDefinedFunctions.add(
new Function("ln") {
@Override
public double apply(double... args) {
return Math.log(args[0]);
}
}
);
userDefinedFunctions.add(
new Function("lg") {
@Override
public double apply(double... args) {
return Math.log10(args[0]);
}
}
);
userDefinedFunctions.add(
new Function("logab", 2) {
@Override
public double apply(double... args) {
return Math.log(args[1]) / Math.log(args[0]);
}
}
);
userDefinedFunctions.add(Functions.getBuiltinFunction("sin"));
userDefinedFunctions.add(Functions.getBuiltinFunction("cos"));
userDefinedFunctions.add(Functions.getBuiltinFunction("tan"));
userDefinedFunctions.add(Functions.getBuiltinFunction("cot"));
userDefinedFunctions.add(Functions.getBuiltinFunction("log"));
userDefinedFunctions.add(Functions.getBuiltinFunction("log2"));
userDefinedFunctions.add(Functions.getBuiltinFunction("log10"));
userDefinedFunctions.add(Functions.getBuiltinFunction("log1p"));
userDefinedFunctions.add(Functions.getBuiltinFunction("abs"));
userDefinedFunctions.add(Functions.getBuiltinFunction("acos"));
userDefinedFunctions.add(Functions.getBuiltinFunction("asin"));
userDefinedFunctions.add(Functions.getBuiltinFunction("atan"));
userDefinedFunctions.add(Functions.getBuiltinFunction("cbrt"));
userDefinedFunctions.add(Functions.getBuiltinFunction("floor"));
userDefinedFunctions.add(Functions.getBuiltinFunction("sinh"));
userDefinedFunctions.add(Functions.getBuiltinFunction("sqrt"));
userDefinedFunctions.add(Functions.getBuiltinFunction("tanh"));
userDefinedFunctions.add(Functions.getBuiltinFunction("cosh"));
userDefinedFunctions.add(Functions.getBuiltinFunction("ceil"));
userDefinedFunctions.add(Functions.getBuiltinFunction("pow"));
userDefinedFunctions.add(Functions.getBuiltinFunction("exp"));
userDefinedFunctions.add(Functions.getBuiltinFunction("expm1"));
userDefinedFunctions.add(Functions.getBuiltinFunction("signum"));
}
}

2
common/edqs/src/main/java/org/thingsboard/server/edqs/util/TbBytePool.java → common/util/src/main/java/org/thingsboard/common/util/TbBytePool.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.edqs.util;
package org.thingsboard.common.util;
import com.google.common.hash.Hashing;
import org.springframework.util.ConcurrentReferenceHashMap;

2
common/edqs/src/main/java/org/thingsboard/server/edqs/util/TbStringPool.java → common/util/src/main/java/org/thingsboard/common/util/TbStringPool.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.edqs.util;
package org.thingsboard.common.util;
import org.springframework.util.ConcurrentReferenceHashMap;

34
common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/GitRepository.java

@ -41,6 +41,7 @@ import org.eclipse.jgit.diff.HistogramDiff;
import org.eclipse.jgit.diff.RawText;
import org.eclipse.jgit.diff.RawTextComparator;
import org.eclipse.jgit.errors.LargeObjectException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader;
@ -147,22 +148,31 @@ public class GitRepository {
}
public static GitRepository openOrClone(Path directory, RepositorySettings settings, boolean fetch) throws IOException, GitAPIException {
GitRepository repository;
if (GitRepository.exists(directory.toString())) {
repository = GitRepository.open(directory.toFile(), settings);
if (fetch) {
repository.fetch();
try {
GitRepository repository = GitRepository.open(directory.toFile(), settings);
if (fetch) {
repository.fetch();
}
return repository;
} catch (RepositoryNotFoundException e) {
log.warn("{} not a git repository, reinitializing", directory);
} catch (org.eclipse.jgit.errors.TransportException | org.eclipse.jgit.api.errors.TransportException e) {
if (StringUtils.containsIgnoreCase(e.getMessage(), "missing commit")) {
log.warn("Couldn't fetch {} due to {}, reinitializing", directory, e.getMessage());
} else {
throw e;
}
}
}
FileUtils.deleteDirectory(directory.toFile());
Files.createDirectories(directory);
if (settings.isLocalOnly()) {
return GitRepository.create(settings, directory.toFile());
} else {
FileUtils.deleteDirectory(directory.toFile());
Files.createDirectories(directory);
if (settings.isLocalOnly()) {
repository = GitRepository.create(settings, directory.toFile());
} else {
repository = GitRepository.clone(settings, directory.toFile());
}
return GitRepository.clone(settings, directory.toFile());
}
return repository;
}
public static void test(RepositorySettings settings, File directory) throws Exception {

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

@ -213,12 +213,11 @@ public class CustomerServiceImpl extends AbstractCachedEntityService<CustomerCac
@Override
public Customer findOrCreatePublicCustomer(TenantId tenantId) {
log.trace("Executing findOrCreatePublicCustomer, tenantId [{}]", tenantId);
Validator.validateId(tenantId, id -> INCORRECT_TENANT_ID + id);
Optional<Customer> publicCustomerOpt = customerDao.findPublicCustomerByTenantId(tenantId.getId());
if (publicCustomerOpt.isPresent()) {
return publicCustomerOpt.get();
var publicCustomer = findPublicCustomer(tenantId);
if (publicCustomer != null) {
return publicCustomer;
}
var publicCustomer = new Customer();
publicCustomer = new Customer();
publicCustomer.setTenantId(tenantId);
publicCustomer.setTitle(PUBLIC_CUSTOMER_TITLE);
try {
@ -230,7 +229,7 @@ public class CustomerServiceImpl extends AbstractCachedEntityService<CustomerCac
return saveCustomer(publicCustomer, false);
} catch (DataValidationException e) {
if (CUSTOMER_UNIQUE_TITLE_EX_MSG.equals(e.getMessage())) {
publicCustomerOpt = customerDao.findPublicCustomerByTenantId(tenantId.getId());
Optional<Customer> publicCustomerOpt = customerDao.findPublicCustomerByTenantId(tenantId.getId());
if (publicCustomerOpt.isPresent()) {
return publicCustomerOpt.get();
}
@ -239,6 +238,14 @@ public class CustomerServiceImpl extends AbstractCachedEntityService<CustomerCac
}
}
@Override
public Customer findPublicCustomer(TenantId tenantId) {
log.trace("Executing findPublicCustomer, tenantId [{}]", tenantId);
Validator.validateId(tenantId, id -> INCORRECT_TENANT_ID + id);
Optional<Customer> publicCustomerOpt = customerDao.findPublicCustomerByTenantId(tenantId.getId());
return publicCustomerOpt.orElse(null);
}
@Override
public PageData<Customer> findCustomersByTenantId(TenantId tenantId, PageLink pageLink) {
log.trace("Executing findCustomersByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink);

6
dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityServiceImpl.java

@ -41,7 +41,6 @@ import org.thingsboard.server.common.data.security.DeviceCredentialsType;
import org.thingsboard.server.dao.settings.AdminSettingsService;
import org.thingsboard.server.dao.util.DeviceConnectivityUtil;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URISyntaxException;
@ -86,6 +85,8 @@ public class DeviceConnectivityServiceImpl implements DeviceConnectivityService
private String mqttsPemCertFile;
@Value("${device.connectivity.coaps.pem_cert_file:}")
private String coapsPemCertFile;
@Value("${device.connectivity.gateway.image_version:3.7-stable}")
private String gatewayImageVersion;
@Override
public JsonNode findDevicePublishTelemetryCommands(String baseUrl, Device device) throws URISyntaxException {
@ -157,7 +158,8 @@ public class DeviceConnectivityServiceImpl implements DeviceConnectivityService
String mqttType = isEnabled(MQTTS) ? MQTTS : MQTT;
DeviceConnectivityInfo properties = getConnectivity(mqttType);
DeviceCredentials creds = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), device.getId());
return DeviceConnectivityUtil.getGatewayDockerComposeFile(baseUrl, properties, creds, mqttType);
String host = getHost(baseUrl, properties, mqttType);
return DeviceConnectivityUtil.getGatewayDockerComposeFile(host, gatewayImageVersion, creds);
}
private DeviceConnectivityInfo getConnectivity(String protocol) {

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

@ -20,6 +20,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.thingsboard.common.util.TbStopWatch;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.HasCustomerId;
import org.thingsboard.server.common.data.HasEmail;
@ -46,6 +47,7 @@ import org.thingsboard.server.common.data.query.EntityTypeFilter;
import org.thingsboard.server.common.data.query.KeyFilter;
import org.thingsboard.server.common.data.query.RelationsQueryFilter;
import org.thingsboard.server.common.msg.edqs.EdqsApiService;
import org.thingsboard.server.common.stats.EdqsStatsService;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
import java.util.ArrayList;
@ -89,6 +91,9 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe
@Lazy
private EdqsApiService edqsApiService;
@Autowired
private EdqsStatsService edqsStatsService;
@Override
public long countEntitiesByQuery(TenantId tenantId, CustomerId customerId, EntityCountQuery query) {
log.trace("Executing countEntitiesByQuery, tenantId [{}], customerId [{}], query [{}]", tenantId, customerId, query);
@ -96,14 +101,19 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe
validateId(customerId, id -> INCORRECT_CUSTOMER_ID + id);
validateEntityCountQuery(query);
TbStopWatch stopWatch = TbStopWatch.create();
Long result;
if (edqsApiService.isEnabled() && validForEdqs(query) && !tenantId.isSysTenantId()) {
EdqsRequest request = EdqsRequest.builder()
.entityCountQuery(query)
.build();
EdqsResponse response = processEdqsRequest(tenantId, customerId, request);
return response.getEntityCountQueryResult();
result = response.getEntityCountQueryResult();
} else {
result = entityQueryDao.countEntitiesByQuery(tenantId, customerId, query);
}
return this.entityQueryDao.countEntitiesByQuery(tenantId, customerId, query);
edqsStatsService.reportCountQuery(tenantId, query, stopWatch.stopAndGetTotalTimeNanos());
return result;
}
@Override
@ -113,27 +123,31 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe
validateId(customerId, id -> INCORRECT_CUSTOMER_ID + id);
validateEntityDataQuery(query);
TbStopWatch stopWatch = TbStopWatch.create();
PageData<EntityData> result;
if (edqsApiService.isEnabled() && validForEdqs(query)) {
EdqsRequest request = EdqsRequest.builder()
.entityDataQuery(query)
.build();
EdqsResponse response = processEdqsRequest(tenantId, customerId, request);
return response.getEntityDataQueryResult();
}
if (!isValidForOptimization(query)) {
return this.entityQueryDao.findEntityDataByQuery(tenantId, customerId, query);
}
// 1 step - find entity data by filter and sort columns
PageData<EntityData> entityDataByQuery = findEntityIdsByFilterAndSorterColumns(tenantId, customerId, query);
if (entityDataByQuery == null || entityDataByQuery.getData().isEmpty()) {
return entityDataByQuery;
result = response.getEntityDataQueryResult();
} else {
if (!isValidForOptimization(query)) {
result = entityQueryDao.findEntityDataByQuery(tenantId, customerId, query);
} else {
// 1 step - find entity data by filter and sort columns
PageData<EntityData> entityDataByQuery = findEntityIdsByFilterAndSorterColumns(tenantId, customerId, query);
if (entityDataByQuery == null || entityDataByQuery.getData().isEmpty()) {
result = entityDataByQuery;
} else {
// 2 step - find entity data by entity ids from the 1st step
List<EntityData> entities = fetchEntityDataByIdsFromInitialQuery(tenantId, customerId, query, entityDataByQuery.getData());
result = new PageData<>(entities, entityDataByQuery.getTotalPages(), entityDataByQuery.getTotalElements(), entityDataByQuery.hasNext());
}
}
}
// 2 step - find entity data by entity ids from the 1st step
List<EntityData> result = fetchEntityDataByIdsFromInitialQuery(tenantId, customerId, query, entityDataByQuery.getData());
return new PageData<>(result, entityDataByQuery.getTotalPages(), entityDataByQuery.getTotalElements(), entityDataByQuery.hasNext());
edqsStatsService.reportDataQuery(tenantId, query, stopWatch.stopAndGetTotalTimeNanos());
return result;
}
private boolean validForEdqs(EntityCountQuery query) { // for compatibility with PE

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save