Browse Source

Merge branch 'develop/3.4' into tests-speedup-tenant

pull/6431/head
Sergey Matvienko 4 years ago
committed by GitHub
parent
commit
5c6a5bcb6a
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 15
      application/src/main/data/json/tenant/edge_management/rule_chains/edge_root_rule_chain.json
  2. 30
      application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
  3. 2
      application/src/main/java/org/thingsboard/server/controller/EdgeController.java
  4. 24
      application/src/main/java/org/thingsboard/server/controller/OtaPackageController.java
  5. 74
      application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java
  6. 4
      application/src/main/java/org/thingsboard/server/service/edge/EdgeNotificationService.java
  7. 48
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeEventUtils.java
  8. 4
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java
  9. 11
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java
  10. 10
      application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AdminSettingsEdgeEventFetcher.java
  11. 4
      application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AssetsEdgeEventFetcher.java
  12. 4
      application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/BaseUsersEdgeEventFetcher.java
  13. 4
      application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/BaseWidgetsBundlesEdgeEventFetcher.java
  14. 4
      application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/CustomerEdgeEventFetcher.java
  15. 4
      application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/DashboardsEdgeEventFetcher.java
  16. 4
      application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/DeviceProfilesEdgeEventFetcher.java
  17. 4
      application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/RuleChainsEdgeEventFetcher.java
  18. 67
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/AlarmEdgeProcessor.java
  19. 41
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java
  20. 16
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/CustomerEdgeProcessor.java
  21. 26
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DeviceEdgeProcessor.java
  22. 71
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/EdgeProcessor.java
  23. 130
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/EntityEdgeProcessor.java
  24. 37
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/RelationEdgeProcessor.java
  25. 283
      application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/DefaultEdgeRequestsService.java
  26. 5
      application/src/main/resources/thingsboard.yml
  27. 10
      application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
  28. 34
      application/src/test/java/org/thingsboard/server/controller/BaseAssetControllerTest.java
  29. 57
      application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java
  30. 17
      application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java
  31. 2
      application/src/test/java/org/thingsboard/server/controller/BaseOtaPackageControllerTest.java
  32. 20
      application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java
  33. 24
      application/src/test/java/org/thingsboard/server/transport/mqtt/AbstractMqttIntegrationTest.java
  34. 8
      application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/AbstractMqttAttributesRequestBackwardCompatibilityIntegrationTest.java
  35. 6
      application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java
  36. 14
      application/src/test/java/org/thingsboard/server/transport/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java
  37. 14
      application/src/test/java/org/thingsboard/server/transport/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java
  38. 20
      application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcBackwardCompatibilityIntegrationTest.java
  39. 2
      application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcProtoIntegrationTest.java
  40. 33
      application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/AbstractMqttTimeseriesIntegrationTest.java
  41. 66
      application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/AbstractMqttTimeseriesJsonIntegrationTest.java
  42. 85
      application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java
  43. 3
      common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeEventService.java
  44. 24
      common/data/src/main/java/org/thingsboard/server/common/data/EdgeUtils.java
  45. 1
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java
  46. 11
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java
  47. 7
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java
  48. 5
      dao/src/main/java/org/thingsboard/server/dao/edge/BaseEdgeEventService.java
  49. 2
      dao/src/main/java/org/thingsboard/server/dao/edge/EdgeEventDao.java
  50. 28
      dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java
  51. 79
      dao/src/main/java/org/thingsboard/server/dao/sql/edge/EdgeEventInsertRepository.java
  52. 164
      dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaBaseEdgeEventDao.java
  53. 76
      dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultQueryLogComponent.java
  54. 15
      dao/src/main/resources/sql/schema-entities.sql
  55. 1
      dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java
  56. 44
      dao/src/test/java/org/thingsboard/server/dao/service/BaseEdgeEventServiceTest.java
  57. 72
      dao/src/test/java/org/thingsboard/server/dao/service/install/sql/EntitiesSchemaSqlTest.java
  58. 154
      dao/src/test/java/org/thingsboard/server/dao/sql/query/DefaultQueryLogComponentTest.java
  59. 178
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/AbstractTbMsgPushNode.java
  60. 33
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/BaseTbMsgPushNodeConfiguration.java
  61. 39
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/TbMsgPushToCloudNode.java
  62. 7
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/TbMsgPushToCloudNodeConfiguration.java
  63. 246
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNode.java
  64. 7
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNodeConfiguration.java
  65. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java
  66. 76
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNodeTest.java
  67. 1
      ui-ngx/.yarnrc
  68. 11
      ui-ngx/generate-types.js
  69. 6
      ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html
  70. 1
      ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts
  71. 2
      ui-ngx/src/app/shared/models/device.models.ts
  72. 2
      ui-ngx/src/assets/locale/locale.constant-en_US.json

15
application/src/main/data/json/tenant/edge_management/rule_chains/edge_root_rule_chain.json

@ -156,6 +156,21 @@
"toIndex": 7, "toIndex": 7,
"type": "Attributes Updated" "type": "Attributes Updated"
}, },
{
"fromIndex": 3,
"toIndex": 7,
"type": "Attributes Deleted"
},
{
"fromIndex": 3,
"toIndex": 7,
"type": "Timeseries Deleted"
},
{
"fromIndex": 3,
"toIndex": 7,
"type": "Timeseries Updated"
},
{ {
"fromIndex": 4, "fromIndex": 4,
"toIndex": 7, "toIndex": 7,

30
application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java

@ -35,6 +35,7 @@ import org.thingsboard.server.actors.TbActorCtx;
import org.thingsboard.server.actors.shared.AbstractContextAwareMsgProcessor; import org.thingsboard.server.actors.shared.AbstractContextAwareMsgProcessor;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventActionType;
@ -97,7 +98,6 @@ import javax.annotation.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.ConcurrentModificationException;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
@ -107,6 +107,7 @@ import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -200,8 +201,13 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
boolean sent = false; boolean sent = false;
if (systemContext.isEdgesEnabled() && edgeId != null) { if (systemContext.isEdgesEnabled() && edgeId != null) {
log.debug("[{}][{}] device is related to edge [{}]. Saving RPC request to edge queue", tenantId, deviceId, edgeId.getId()); log.debug("[{}][{}] device is related to edge [{}]. Saving RPC request to edge queue", tenantId, deviceId, edgeId.getId());
saveRpcRequestToEdgeQueue(request, rpcRequest.getRequestId()); try {
sent = true; saveRpcRequestToEdgeQueue(request, rpcRequest.getRequestId()).get();
sent = true;
} catch (InterruptedException | ExecutionException e) {
String errMsg = String.format("[%s][%s][%s] Failed to save rpc request to edge queue %s", tenantId, deviceId, edgeId.getId(), request);
log.error(errMsg, e);
}
} else if (isSendNewRpcAvailable()) { } else if (isSendNewRpcAvailable()) {
sent = rpcSubscriptions.size() > 0; sent = rpcSubscriptions.size() > 0;
Set<UUID> syncSessionSet = new HashSet<>(); Set<UUID> syncSessionSet = new HashSet<>();
@ -810,13 +816,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
systemContext.getTbCoreToTransportService().process(nodeId, msg); systemContext.getTbCoreToTransportService().process(nodeId, msg);
} }
private void saveRpcRequestToEdgeQueue(ToDeviceRpcRequest msg, Integer requestId) { private ListenableFuture<Void> saveRpcRequestToEdgeQueue(ToDeviceRpcRequest msg, Integer requestId) {
EdgeEvent edgeEvent = new EdgeEvent();
edgeEvent.setTenantId(tenantId);
edgeEvent.setAction(EdgeEventActionType.RPC_CALL);
edgeEvent.setEntityId(deviceId.getId());
edgeEvent.setType(EdgeEventType.DEVICE);
ObjectNode body = mapper.createObjectNode(); ObjectNode body = mapper.createObjectNode();
body.put("requestId", requestId); body.put("requestId", requestId);
body.put("requestUUID", msg.getId().toString()); body.put("requestUUID", msg.getId().toString());
@ -824,11 +824,13 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
body.put("expirationTime", msg.getExpirationTime()); body.put("expirationTime", msg.getExpirationTime());
body.put("method", msg.getBody().getMethod()); body.put("method", msg.getBody().getMethod());
body.put("params", msg.getBody().getParams()); body.put("params", msg.getBody().getParams());
edgeEvent.setBody(body);
edgeEvent.setEdgeId(edgeId); EdgeEvent edgeEvent = EdgeUtils.constructEdgeEvent(tenantId, edgeId, EdgeEventType.DEVICE, EdgeEventActionType.RPC_CALL, deviceId, body);
systemContext.getEdgeEventService().save(edgeEvent);
systemContext.getClusterService().onEdgeEventUpdate(tenantId, edgeId); return Futures.transform(systemContext.getEdgeEventService().saveAsync(edgeEvent), unused -> {
systemContext.getClusterService().onEdgeEventUpdate(tenantId, edgeId);
return null;
}, systemContext.getDbCallbackExecutor());
} }
private List<TsKvProto> toTsKvProtos(@Nullable List<AttributeKvEntry> result) { private List<TsKvProto> toTsKvProtos(@Nullable List<AttributeKvEntry> result) {

2
application/src/main/java/org/thingsboard/server/controller/EdgeController.java

@ -180,7 +180,7 @@ public class EdgeController extends BaseController {
} }
} }
private void onEdgeCreatedOrUpdated(TenantId tenantId, Edge edge, RuleChain edgeTemplateRootRuleChain, boolean updated, SecurityUser user) throws IOException, ThingsboardException { private void onEdgeCreatedOrUpdated(TenantId tenantId, Edge edge, RuleChain edgeTemplateRootRuleChain, boolean updated, SecurityUser user) throws Exception {
if (!updated) { if (!updated) {
ruleChainService.assignRuleChainToEdge(tenantId, edgeTemplateRootRuleChain.getId(), edge.getId()); ruleChainService.assignRuleChainToEdge(tenantId, edgeTemplateRootRuleChain.getId(), edge.getId());
edgeNotificationService.setEdgeRootRuleChain(tenantId, edge, edgeTemplateRootRuleChain.getId()); edgeNotificationService.setEdgeRootRuleChain(tenantId, edge, edgeTemplateRootRuleChain.getId());

24
application/src/main/java/org/thingsboard/server/controller/OtaPackageController.java

@ -28,6 +28,7 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
@ -49,6 +50,8 @@ import org.thingsboard.server.service.security.permission.Resource;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE;
import static org.thingsboard.server.controller.ControllerConstants.DEVICE_PROFILE_ID_PARAM_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.DEVICE_PROFILE_ID_PARAM_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.OTA_PACKAGE_CHECKSUM_ALGORITHM_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.OTA_PACKAGE_CHECKSUM_ALGORITHM_ALLOWABLE_VALUES;
import static org.thingsboard.server.controller.ControllerConstants.OTA_PACKAGE_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.OTA_PACKAGE_DESCRIPTION;
@ -105,7 +108,7 @@ public class OtaPackageController extends BaseController {
@ApiOperation(value = "Get OTA Package Info (getOtaPackageInfoById)", @ApiOperation(value = "Get OTA Package Info (getOtaPackageInfoById)",
notes = "Fetch the OTA Package Info object based on the provided OTA Package Id. " + notes = "Fetch the OTA Package Info object based on the provided OTA Package Id. " +
OTA_PACKAGE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, OTA_PACKAGE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
produces = "application/json") produces = APPLICATION_JSON_VALUE)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/otaPackage/info/{otaPackageId}", method = RequestMethod.GET) @RequestMapping(value = "/otaPackage/info/{otaPackageId}", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -123,7 +126,7 @@ public class OtaPackageController extends BaseController {
@ApiOperation(value = "Get OTA Package (getOtaPackageById)", @ApiOperation(value = "Get OTA Package (getOtaPackageById)",
notes = "Fetch the OTA Package object based on the provided OTA Package Id. " + notes = "Fetch the OTA Package object based on the provided OTA Package Id. " +
"The server checks that the OTA Package is owned by the same tenant. " + OTA_PACKAGE_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH, "The server checks that the OTA Package is owned by the same tenant. " + OTA_PACKAGE_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH,
produces = "application/json") produces = APPLICATION_JSON_VALUE)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/otaPackage/{otaPackageId}", method = RequestMethod.GET) @RequestMapping(value = "/otaPackage/{otaPackageId}", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -144,8 +147,8 @@ public class OtaPackageController extends BaseController {
"Specify existing OTA Package id to update the OTA Package Info. " + "Specify existing OTA Package id to update the OTA Package Info. " +
"Referencing non-existing OTA Package Id will cause 'Not Found' error. " + "Referencing non-existing OTA Package Id will cause 'Not Found' error. " +
"\n\nOTA Package combination of the title with the version is unique in the scope of tenant. " + TENANT_AUTHORITY_PARAGRAPH, "\n\nOTA Package combination of the title with the version is unique in the scope of tenant. " + TENANT_AUTHORITY_PARAGRAPH,
produces = "application/json", produces = APPLICATION_JSON_VALUE,
consumes = "application/json") consumes = APPLICATION_JSON_VALUE)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/otaPackage", method = RequestMethod.POST) @RequestMapping(value = "/otaPackage", method = RequestMethod.POST)
@ResponseBody @ResponseBody
@ -168,9 +171,10 @@ public class OtaPackageController extends BaseController {
@ApiOperation(value = "Save OTA Package data (saveOtaPackageData)", @ApiOperation(value = "Save OTA Package data (saveOtaPackageData)",
notes = "Update the OTA Package. Adds the date to the existing OTA Package Info" + TENANT_AUTHORITY_PARAGRAPH, notes = "Update the OTA Package. Adds the date to the existing OTA Package Info" + TENANT_AUTHORITY_PARAGRAPH,
produces = "application/json") produces = APPLICATION_JSON_VALUE,
consumes = MULTIPART_FORM_DATA_VALUE)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/otaPackage/{otaPackageId}", method = RequestMethod.POST) @RequestMapping(value = "/otaPackage/{otaPackageId}", method = RequestMethod.POST, consumes = MULTIPART_FORM_DATA_VALUE)
@ResponseBody @ResponseBody
public OtaPackageInfo saveOtaPackageData(@ApiParam(value = OTA_PACKAGE_ID_PARAM_DESCRIPTION) public OtaPackageInfo saveOtaPackageData(@ApiParam(value = OTA_PACKAGE_ID_PARAM_DESCRIPTION)
@PathVariable(OTA_PACKAGE_ID) String strOtaPackageId, @PathVariable(OTA_PACKAGE_ID) String strOtaPackageId,
@ -179,7 +183,7 @@ public class OtaPackageController extends BaseController {
@ApiParam(value = "OTA Package checksum algorithm.", allowableValues = OTA_PACKAGE_CHECKSUM_ALGORITHM_ALLOWABLE_VALUES) @ApiParam(value = "OTA Package checksum algorithm.", allowableValues = OTA_PACKAGE_CHECKSUM_ALGORITHM_ALLOWABLE_VALUES)
@RequestParam(CHECKSUM_ALGORITHM) String checksumAlgorithmStr, @RequestParam(CHECKSUM_ALGORITHM) String checksumAlgorithmStr,
@ApiParam(value = "OTA Package data.") @ApiParam(value = "OTA Package data.")
@RequestBody MultipartFile file) throws ThingsboardException { @RequestPart MultipartFile file) throws ThingsboardException {
checkParameter(OTA_PACKAGE_ID, strOtaPackageId); checkParameter(OTA_PACKAGE_ID, strOtaPackageId);
checkParameter(CHECKSUM_ALGORITHM, checksumAlgorithmStr); checkParameter(CHECKSUM_ALGORITHM, checksumAlgorithmStr);
try { try {
@ -221,7 +225,7 @@ public class OtaPackageController extends BaseController {
@ApiOperation(value = "Get OTA Package Infos (getOtaPackages)", @ApiOperation(value = "Get OTA Package Infos (getOtaPackages)",
notes = "Returns a page of OTA Package Info objects owned by tenant. " + notes = "Returns a page of OTA Package Info objects owned by tenant. " +
PAGE_DATA_PARAMETERS + OTA_PACKAGE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, PAGE_DATA_PARAMETERS + OTA_PACKAGE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
produces = "application/json") produces = APPLICATION_JSON_VALUE)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/otaPackages", method = RequestMethod.GET) @RequestMapping(value = "/otaPackages", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -246,7 +250,7 @@ public class OtaPackageController extends BaseController {
@ApiOperation(value = "Get OTA Package Infos (getOtaPackages)", @ApiOperation(value = "Get OTA Package Infos (getOtaPackages)",
notes = "Returns a page of OTA Package Info objects owned by tenant. " + notes = "Returns a page of OTA Package Info objects owned by tenant. " +
PAGE_DATA_PARAMETERS + OTA_PACKAGE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, PAGE_DATA_PARAMETERS + OTA_PACKAGE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
produces = "application/json") produces = APPLICATION_JSON_VALUE)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/otaPackages/{deviceProfileId}/{type}", method = RequestMethod.GET) @RequestMapping(value = "/otaPackages/{deviceProfileId}/{type}", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -278,7 +282,7 @@ public class OtaPackageController extends BaseController {
@ApiOperation(value = "Delete OTA Package (deleteOtaPackage)", @ApiOperation(value = "Delete OTA Package (deleteOtaPackage)",
notes = "Deletes the OTA Package. Referencing non-existing OTA Package Id will cause an error. " + notes = "Deletes the OTA Package. Referencing non-existing OTA Package Id will cause an error. " +
"Can't delete the OTA Package if it is referenced by existing devices or device profile." + TENANT_AUTHORITY_PARAGRAPH, "Can't delete the OTA Package if it is referenced by existing devices or device profile." + TENANT_AUTHORITY_PARAGRAPH,
produces = "application/json") produces = APPLICATION_JSON_VALUE)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/otaPackage/{otaPackageId}", method = RequestMethod.DELETE) @RequestMapping(value = "/otaPackage/{otaPackageId}", method = RequestMethod.DELETE)
@ResponseBody @ResponseBody

74
application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java

@ -16,11 +16,16 @@
package org.thingsboard.server.service.edge; package org.thingsboard.server.service.edge;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventActionType;
@ -42,7 +47,6 @@ import org.thingsboard.server.service.edge.rpc.processor.RelationEdgeProcessor;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy; import javax.annotation.PreDestroy;
import java.io.IOException;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
@ -76,29 +80,29 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService {
@Autowired @Autowired
private CustomerEdgeProcessor customerProcessor; private CustomerEdgeProcessor customerProcessor;
private ExecutorService tsCallBackExecutor; private ExecutorService dbCallBackExecutor;
@PostConstruct @PostConstruct
public void initExecutor() { public void initExecutor() {
tsCallBackExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("edge-notifications")); dbCallBackExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("edge-notifications"));
} }
@PreDestroy @PreDestroy
public void shutdownExecutor() { public void shutdownExecutor() {
if (tsCallBackExecutor != null) { if (dbCallBackExecutor != null) {
tsCallBackExecutor.shutdownNow(); dbCallBackExecutor.shutdownNow();
} }
} }
@Override @Override
public Edge setEdgeRootRuleChain(TenantId tenantId, Edge edge, RuleChainId ruleChainId) throws IOException { public Edge setEdgeRootRuleChain(TenantId tenantId, Edge edge, RuleChainId ruleChainId) throws Exception {
edge.setRootRuleChainId(ruleChainId); edge.setRootRuleChainId(ruleChainId);
Edge savedEdge = edgeService.saveEdge(edge); Edge savedEdge = edgeService.saveEdge(edge);
saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.RULE_CHAIN, EdgeEventActionType.UPDATED, ruleChainId, null); saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.RULE_CHAIN, EdgeEventActionType.UPDATED, ruleChainId, null).get();
return savedEdge; return savedEdge;
} }
private void saveEdgeEvent(TenantId tenantId, private ListenableFuture<Void> saveEdgeEvent(TenantId tenantId,
EdgeId edgeId, EdgeId edgeId,
EdgeEventType type, EdgeEventType type,
EdgeEventActionType action, EdgeEventActionType action,
@ -107,17 +111,12 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService {
log.debug("Pushing edge event to edge queue. tenantId [{}], edgeId [{}], type [{}], action[{}], entityId [{}], body [{}]", log.debug("Pushing edge event to edge queue. tenantId [{}], edgeId [{}], type [{}], action[{}], entityId [{}], body [{}]",
tenantId, edgeId, type, action, entityId, body); tenantId, edgeId, type, action, entityId, body);
EdgeEvent edgeEvent = new EdgeEvent(); EdgeEvent edgeEvent = EdgeUtils.constructEdgeEvent(tenantId, edgeId, type, action, entityId, body);
edgeEvent.setEdgeId(edgeId);
edgeEvent.setTenantId(tenantId); return Futures.transform(edgeEventService.saveAsync(edgeEvent), unused -> {
edgeEvent.setType(type); clusterService.onEdgeEventUpdate(tenantId, edgeId);
edgeEvent.setAction(action); return null;
if (entityId != null) { }, dbCallBackExecutor);
edgeEvent.setEntityId(entityId.getId());
}
edgeEvent.setBody(body);
edgeEventService.save(edgeEvent);
clusterService.onEdgeEventUpdate(tenantId, edgeId);
} }
@Override @Override
@ -126,9 +125,10 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService {
try { try {
TenantId tenantId = TenantId.fromUUID(new UUID(edgeNotificationMsg.getTenantIdMSB(), edgeNotificationMsg.getTenantIdLSB())); TenantId tenantId = TenantId.fromUUID(new UUID(edgeNotificationMsg.getTenantIdMSB(), edgeNotificationMsg.getTenantIdLSB()));
EdgeEventType type = EdgeEventType.valueOf(edgeNotificationMsg.getType()); EdgeEventType type = EdgeEventType.valueOf(edgeNotificationMsg.getType());
ListenableFuture<Void> future;
switch (type) { switch (type) {
case EDGE: case EDGE:
edgeProcessor.processEdgeNotification(tenantId, edgeNotificationMsg); future = edgeProcessor.processEdgeNotification(tenantId, edgeNotificationMsg);
break; break;
case USER: case USER:
case ASSET: case ASSET:
@ -137,33 +137,47 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService {
case ENTITY_VIEW: case ENTITY_VIEW:
case DASHBOARD: case DASHBOARD:
case RULE_CHAIN: case RULE_CHAIN:
entityProcessor.processEntityNotification(tenantId, edgeNotificationMsg); future = entityProcessor.processEntityNotification(tenantId, edgeNotificationMsg);
break; break;
case CUSTOMER: case CUSTOMER:
customerProcessor.processCustomerNotification(tenantId, edgeNotificationMsg); future = customerProcessor.processCustomerNotification(tenantId, edgeNotificationMsg);
break; break;
case WIDGETS_BUNDLE: case WIDGETS_BUNDLE:
case WIDGET_TYPE: case WIDGET_TYPE:
entityProcessor.processEntityNotificationForAllEdges(tenantId, edgeNotificationMsg); future = entityProcessor.processEntityNotificationForAllEdges(tenantId, edgeNotificationMsg);
break; break;
case ALARM: case ALARM:
alarmProcessor.processAlarmNotification(tenantId, edgeNotificationMsg); future = alarmProcessor.processAlarmNotification(tenantId, edgeNotificationMsg);
break; break;
case RELATION: case RELATION:
relationProcessor.processRelationNotification(tenantId, edgeNotificationMsg); future = relationProcessor.processRelationNotification(tenantId, edgeNotificationMsg);
break; break;
default: default:
log.warn("Edge event type [{}] is not designed to be pushed to edge", type); log.warn("Edge event type [{}] is not designed to be pushed to edge", type);
future = Futures.immediateFuture(null);
} }
Futures.addCallback(future, new FutureCallback<>() {
@Override
public void onSuccess(@Nullable Void unused) {
callback.onSuccess();
}
@Override
public void onFailure(Throwable throwable) {
callBackFailure(edgeNotificationMsg, callback, throwable);
}
}, dbCallBackExecutor);
} catch (Exception e) { } catch (Exception e) {
callback.onFailure(e); callBackFailure(edgeNotificationMsg, callback, e);
String errMsg = String.format("Can't push to edge updates, edgeNotificationMsg [%s]", edgeNotificationMsg);
log.error(errMsg, e);
} finally {
callback.onSuccess();
} }
} }
private void callBackFailure(TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg, TbCallback callback, Throwable throwable) {
String errMsg = String.format("Can't push to edge updates, edgeNotificationMsg [%s]", edgeNotificationMsg);
log.error(errMsg, throwable);
callback.onFailure(throwable);
}
} }

4
application/src/main/java/org/thingsboard/server/service/edge/EdgeNotificationService.java

@ -21,11 +21,9 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import java.io.IOException;
public interface EdgeNotificationService { public interface EdgeNotificationService {
Edge setEdgeRootRuleChain(TenantId tenantId, Edge edge, RuleChainId ruleChainId) throws IOException; Edge setEdgeRootRuleChain(TenantId tenantId, Edge edge, RuleChainId ruleChainId) throws Exception;
void pushNotificationToEdge(TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg, TbCallback callback); void pushNotificationToEdge(TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg, TbCallback callback);
} }

48
application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeEventUtils.java

@ -1,48 +0,0 @@
/**
* Copyright © 2016-2022 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.rpc;
import com.fasterxml.jackson.databind.JsonNode;
import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
public final class EdgeEventUtils {
private EdgeEventUtils() {
}
public static EdgeEvent constructEdgeEvent(TenantId tenantId,
EdgeId edgeId,
EdgeEventType type,
EdgeEventActionType action,
EntityId entityId,
JsonNode body) {
EdgeEvent edgeEvent = new EdgeEvent();
edgeEvent.setTenantId(tenantId);
edgeEvent.setEdgeId(edgeId);
edgeEvent.setType(type);
edgeEvent.setAction(action);
if (entityId != null) {
edgeEvent.setEntityId(entityId.getId());
}
edgeEvent.setBody(body);
return edgeEvent;
}
}

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

@ -15,7 +15,6 @@
*/ */
package org.thingsboard.server.service.edge.rpc; package org.thingsboard.server.service.edge.rpc;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import io.grpc.Server; import io.grpc.Server;
@ -71,7 +70,6 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i
private final ConcurrentMap<EdgeId, Lock> sessionNewEventsLocks = new ConcurrentHashMap<>(); private final ConcurrentMap<EdgeId, Lock> sessionNewEventsLocks = new ConcurrentHashMap<>();
private final Map<EdgeId, Boolean> sessionNewEvents = new HashMap<>(); private final Map<EdgeId, Boolean> sessionNewEvents = new HashMap<>();
private final ConcurrentMap<EdgeId, ScheduledFuture<?>> sessionEdgeEventChecks = new ConcurrentHashMap<>(); private final ConcurrentMap<EdgeId, ScheduledFuture<?>> sessionEdgeEventChecks = new ConcurrentHashMap<>();
private static final ObjectMapper mapper = new ObjectMapper();
@Value("${edges.rpc.port}") @Value("${edges.rpc.port}")
private int rpcPort; private int rpcPort;
@ -159,7 +157,7 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i
@Override @Override
public StreamObserver<RequestMsg> handleMsgs(StreamObserver<ResponseMsg> outputStream) { public StreamObserver<RequestMsg> handleMsgs(StreamObserver<ResponseMsg> outputStream) {
return new EdgeGrpcSession(ctx, outputStream, this::onEdgeConnect, this::onEdgeDisconnect, mapper, sendDownlinkExecutorService).getInputStream(); return new EdgeGrpcSession(ctx, outputStream, this::onEdgeConnect, this::onEdgeDisconnect, sendDownlinkExecutorService).getInputStream();
} }
@Override @Override

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

@ -16,7 +16,6 @@
package org.thingsboard.server.service.edge.rpc; package org.thingsboard.server.service.edge.rpc;
import com.datastax.oss.driver.api.core.uuid.Uuids; import com.datastax.oss.driver.api.core.uuid.Uuids;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
@ -96,7 +95,6 @@ public final class EdgeGrpcSession implements Closeable {
private final UUID sessionId; private final UUID sessionId;
private final BiConsumer<EdgeId, EdgeGrpcSession> sessionOpenListener; private final BiConsumer<EdgeId, EdgeGrpcSession> sessionOpenListener;
private final Consumer<EdgeId> sessionCloseListener; private final Consumer<EdgeId> sessionCloseListener;
private final ObjectMapper mapper;
private final EdgeSessionState sessionState = new EdgeSessionState(); private final EdgeSessionState sessionState = new EdgeSessionState();
@ -112,13 +110,12 @@ public final class EdgeGrpcSession implements Closeable {
private ScheduledExecutorService sendDownlinkExecutorService; private ScheduledExecutorService sendDownlinkExecutorService;
EdgeGrpcSession(EdgeContextComponent ctx, StreamObserver<ResponseMsg> outputStream, BiConsumer<EdgeId, EdgeGrpcSession> sessionOpenListener, EdgeGrpcSession(EdgeContextComponent ctx, StreamObserver<ResponseMsg> outputStream, BiConsumer<EdgeId, EdgeGrpcSession> sessionOpenListener,
Consumer<EdgeId> sessionCloseListener, ObjectMapper mapper, ScheduledExecutorService sendDownlinkExecutorService) { Consumer<EdgeId> sessionCloseListener, ScheduledExecutorService sendDownlinkExecutorService) {
this.sessionId = UUID.randomUUID(); this.sessionId = UUID.randomUUID();
this.ctx = ctx; this.ctx = ctx;
this.outputStream = outputStream; this.outputStream = outputStream;
this.sessionOpenListener = sessionOpenListener; this.sessionOpenListener = sessionOpenListener;
this.sessionCloseListener = sessionCloseListener; this.sessionCloseListener = sessionCloseListener;
this.mapper = mapper;
this.sendDownlinkExecutorService = sendDownlinkExecutorService; this.sendDownlinkExecutorService = sendDownlinkExecutorService;
initInputStream(); initInputStream();
} }
@ -402,11 +399,11 @@ public final class EdgeGrpcSession implements Closeable {
Runnable sendDownlinkMsgsTask = () -> { Runnable sendDownlinkMsgsTask = () -> {
try { try {
if (isConnected() && sessionState.getPendingMsgsMap().values().size() > 0) { if (isConnected() && sessionState.getPendingMsgsMap().values().size() > 0) {
List<DownlinkMsg> copy = new ArrayList<>(sessionState.getPendingMsgsMap().values());
if (!firstRun) { if (!firstRun) {
log.warn("[{}] Failed to deliver the batch: {}", this.sessionId, sessionState.getPendingMsgsMap().values()); log.warn("[{}] Failed to deliver the batch: {}", this.sessionId, copy);
} }
log.trace("[{}] [{}] downlink msg(s) are going to be send.", this.sessionId, sessionState.getPendingMsgsMap().values().size()); log.trace("[{}] [{}] downlink msg(s) are going to be send.", this.sessionId, copy.size());
List<DownlinkMsg> copy = new ArrayList<>(sessionState.getPendingMsgsMap().values());
for (DownlinkMsg downlinkMsg : copy) { for (DownlinkMsg downlinkMsg : copy) {
sendDownlinkMsg(ResponseMsg.newBuilder() sendDownlinkMsg(ResponseMsg.newBuilder()
.setDownlinkMsg(downlinkMsg) .setDownlinkMsg(downlinkMsg)

10
application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AdminSettingsEdgeEventFetcher.java

@ -26,6 +26,7 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.text.WordUtils; import org.apache.commons.lang3.text.WordUtils;
import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventActionType;
@ -35,7 +36,6 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.dao.settings.AdminSettingsService;
import org.thingsboard.server.service.edge.rpc.EdgeEventUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@ -80,19 +80,19 @@ public class AdminSettingsEdgeEventFetcher implements EdgeEventFetcher {
List<EdgeEvent> result = new ArrayList<>(); List<EdgeEvent> result = new ArrayList<>();
AdminSettings systemMailSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, "mail"); AdminSettings systemMailSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, "mail");
result.add(EdgeEventUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.ADMIN_SETTINGS, result.add(EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.ADMIN_SETTINGS,
EdgeEventActionType.UPDATED, null, mapper.valueToTree(systemMailSettings))); EdgeEventActionType.UPDATED, null, mapper.valueToTree(systemMailSettings)));
AdminSettings tenantMailSettings = convertToTenantAdminSettings(systemMailSettings.getKey(), (ObjectNode) systemMailSettings.getJsonValue()); AdminSettings tenantMailSettings = convertToTenantAdminSettings(systemMailSettings.getKey(), (ObjectNode) systemMailSettings.getJsonValue());
result.add(EdgeEventUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.ADMIN_SETTINGS, result.add(EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.ADMIN_SETTINGS,
EdgeEventActionType.UPDATED, null, mapper.valueToTree(tenantMailSettings))); EdgeEventActionType.UPDATED, null, mapper.valueToTree(tenantMailSettings)));
AdminSettings systemMailTemplates = loadMailTemplates(); AdminSettings systemMailTemplates = loadMailTemplates();
result.add(EdgeEventUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.ADMIN_SETTINGS, result.add(EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.ADMIN_SETTINGS,
EdgeEventActionType.UPDATED, null, mapper.valueToTree(systemMailTemplates))); EdgeEventActionType.UPDATED, null, mapper.valueToTree(systemMailTemplates)));
AdminSettings tenantMailTemplates = convertToTenantAdminSettings(systemMailTemplates.getKey(), (ObjectNode) systemMailTemplates.getJsonValue()); AdminSettings tenantMailTemplates = convertToTenantAdminSettings(systemMailTemplates.getKey(), (ObjectNode) systemMailTemplates.getJsonValue());
result.add(EdgeEventUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.ADMIN_SETTINGS, result.add(EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.ADMIN_SETTINGS,
EdgeEventActionType.UPDATED, null, mapper.valueToTree(tenantMailTemplates))); EdgeEventActionType.UPDATED, null, mapper.valueToTree(tenantMailTemplates)));
// @voba - returns PageData object to be in sync with other fetchers // @voba - returns PageData object to be in sync with other fetchers

4
application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AssetsEdgeEventFetcher.java

@ -17,6 +17,7 @@ package org.thingsboard.server.service.edge.rpc.fetch;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEvent;
@ -26,7 +27,6 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.service.edge.rpc.EdgeEventUtils;
@AllArgsConstructor @AllArgsConstructor
@Slf4j @Slf4j
@ -41,7 +41,7 @@ public class AssetsEdgeEventFetcher extends BasePageableEdgeEventFetcher<Asset>
@Override @Override
EdgeEvent constructEdgeEvent(TenantId tenantId, Edge edge, Asset asset) { EdgeEvent constructEdgeEvent(TenantId tenantId, Edge edge, Asset asset) {
return EdgeEventUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.ASSET, return EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.ASSET,
EdgeEventActionType.ADDED, asset.getId(), null); EdgeEventActionType.ADDED, asset.getId(), null);
} }
} }

4
application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/BaseUsersEdgeEventFetcher.java

@ -17,6 +17,7 @@ package org.thingsboard.server.service.edge.rpc.fetch;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEvent;
@ -26,7 +27,6 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.service.edge.rpc.EdgeEventUtils;
@Slf4j @Slf4j
@AllArgsConstructor @AllArgsConstructor
@ -41,7 +41,7 @@ public abstract class BaseUsersEdgeEventFetcher extends BasePageableEdgeEventFet
@Override @Override
EdgeEvent constructEdgeEvent(TenantId tenantId, Edge edge, User user) { EdgeEvent constructEdgeEvent(TenantId tenantId, Edge edge, User user) {
return EdgeEventUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.USER, return EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.USER,
EdgeEventActionType.ADDED, user.getId(), null); EdgeEventActionType.ADDED, user.getId(), null);
} }

4
application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/BaseWidgetsBundlesEdgeEventFetcher.java

@ -17,6 +17,7 @@ package org.thingsboard.server.service.edge.rpc.fetch;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventActionType;
@ -26,7 +27,6 @@ import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.widget.WidgetsBundle; import org.thingsboard.server.common.data.widget.WidgetsBundle;
import org.thingsboard.server.dao.widget.WidgetsBundleService; import org.thingsboard.server.dao.widget.WidgetsBundleService;
import org.thingsboard.server.service.edge.rpc.EdgeEventUtils;
@Slf4j @Slf4j
@AllArgsConstructor @AllArgsConstructor
@ -41,7 +41,7 @@ public abstract class BaseWidgetsBundlesEdgeEventFetcher extends BasePageableEdg
@Override @Override
EdgeEvent constructEdgeEvent(TenantId tenantId, Edge edge, WidgetsBundle widgetsBundle) { EdgeEvent constructEdgeEvent(TenantId tenantId, Edge edge, WidgetsBundle widgetsBundle) {
return EdgeEventUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.WIDGETS_BUNDLE, return EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.WIDGETS_BUNDLE,
EdgeEventActionType.ADDED, widgetsBundle.getId(), null); EdgeEventActionType.ADDED, widgetsBundle.getId(), null);
} }

4
application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/CustomerEdgeEventFetcher.java

@ -17,6 +17,7 @@ package org.thingsboard.server.service.edge.rpc.fetch;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventActionType;
@ -24,7 +25,6 @@ import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.service.edge.rpc.EdgeEventUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -41,7 +41,7 @@ public class CustomerEdgeEventFetcher implements EdgeEventFetcher {
@Override @Override
public PageData<EdgeEvent> fetchEdgeEvents(TenantId tenantId, Edge edge, PageLink pageLink) { public PageData<EdgeEvent> fetchEdgeEvents(TenantId tenantId, Edge edge, PageLink pageLink) {
List<EdgeEvent> result = new ArrayList<>(); List<EdgeEvent> result = new ArrayList<>();
result.add(EdgeEventUtils.constructEdgeEvent(edge.getTenantId(), edge.getId(), result.add(EdgeUtils.constructEdgeEvent(edge.getTenantId(), edge.getId(),
EdgeEventType.CUSTOMER, EdgeEventActionType.ADDED, edge.getCustomerId(), null)); EdgeEventType.CUSTOMER, EdgeEventActionType.ADDED, edge.getCustomerId(), null));
// @voba - returns PageData object to be in sync with other fetchers // @voba - returns PageData object to be in sync with other fetchers
return new PageData<>(result, 1, result.size(), false); return new PageData<>(result, 1, result.size(), false);

4
application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/DashboardsEdgeEventFetcher.java

@ -18,6 +18,7 @@ package org.thingsboard.server.service.edge.rpc.fetch;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.DashboardInfo; import org.thingsboard.server.common.data.DashboardInfo;
import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventActionType;
@ -26,7 +27,6 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.dashboard.DashboardService; import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.service.edge.rpc.EdgeEventUtils;
@AllArgsConstructor @AllArgsConstructor
@Slf4j @Slf4j
@ -41,7 +41,7 @@ public class DashboardsEdgeEventFetcher extends BasePageableEdgeEventFetcher<Das
@Override @Override
EdgeEvent constructEdgeEvent(TenantId tenantId, Edge edge, DashboardInfo dashboardInfo) { EdgeEvent constructEdgeEvent(TenantId tenantId, Edge edge, DashboardInfo dashboardInfo) {
return EdgeEventUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.DASHBOARD, return EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.DASHBOARD,
EdgeEventActionType.ADDED, dashboardInfo.getId(), null); EdgeEventActionType.ADDED, dashboardInfo.getId(), null);
} }
} }

4
application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/DeviceProfilesEdgeEventFetcher.java

@ -18,6 +18,7 @@ package org.thingsboard.server.service.edge.rpc.fetch;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventActionType;
@ -26,7 +27,6 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceProfileService;
import org.thingsboard.server.service.edge.rpc.EdgeEventUtils;
@AllArgsConstructor @AllArgsConstructor
@Slf4j @Slf4j
@ -41,7 +41,7 @@ public class DeviceProfilesEdgeEventFetcher extends BasePageableEdgeEventFetcher
@Override @Override
EdgeEvent constructEdgeEvent(TenantId tenantId, Edge edge, DeviceProfile deviceProfile) { EdgeEvent constructEdgeEvent(TenantId tenantId, Edge edge, DeviceProfile deviceProfile) {
return EdgeEventUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE_PROFILE, return EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE_PROFILE,
EdgeEventActionType.ADDED, deviceProfile.getId(), null); EdgeEventActionType.ADDED, deviceProfile.getId(), null);
} }
} }

4
application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/RuleChainsEdgeEventFetcher.java

@ -17,6 +17,7 @@ package org.thingsboard.server.service.edge.rpc.fetch;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventActionType;
@ -26,7 +27,6 @@ import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.service.edge.rpc.EdgeEventUtils;
@Slf4j @Slf4j
@AllArgsConstructor @AllArgsConstructor
@ -41,7 +41,7 @@ public class RuleChainsEdgeEventFetcher extends BasePageableEdgeEventFetcher<Rul
@Override @Override
EdgeEvent constructEdgeEvent(TenantId tenantId, Edge edge, RuleChain ruleChain) { EdgeEvent constructEdgeEvent(TenantId tenantId, Edge edge, RuleChain ruleChain) {
return EdgeEventUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.RULE_CHAIN, return EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.RULE_CHAIN,
EdgeEventActionType.ADDED, ruleChain.getId(), null); EdgeEventActionType.ADDED, ruleChain.getId(), null);
} }
} }

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

@ -16,11 +16,9 @@
package org.thingsboard.server.service.edge.rpc.processor; package org.thingsboard.server.service.edge.rpc.processor;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
@ -43,6 +41,8 @@ import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID; import java.util.UUID;
@Component @Component
@ -148,49 +148,44 @@ public class AlarmEdgeProcessor extends BaseEdgeProcessor {
return downlinkMsg; return downlinkMsg;
} }
public void processAlarmNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) throws JsonProcessingException { public ListenableFuture<Void> processAlarmNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) throws JsonProcessingException {
EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()); EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction());
AlarmId alarmId = new AlarmId(new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB())); AlarmId alarmId = new AlarmId(new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB()));
switch (actionType) { switch (actionType) {
case DELETED: case DELETED:
EdgeId edgeId = new EdgeId(new UUID(edgeNotificationMsg.getEdgeIdMSB(), edgeNotificationMsg.getEdgeIdLSB())); EdgeId edgeId = new EdgeId(new UUID(edgeNotificationMsg.getEdgeIdMSB(), edgeNotificationMsg.getEdgeIdLSB()));
Alarm alarm = mapper.readValue(edgeNotificationMsg.getBody(), Alarm.class); Alarm deletedAlarm = mapper.readValue(edgeNotificationMsg.getBody(), Alarm.class);
saveEdgeEvent(tenantId, edgeId, EdgeEventType.ALARM, actionType, alarmId, mapper.valueToTree(alarm)); return saveEdgeEvent(tenantId, edgeId, EdgeEventType.ALARM, actionType, alarmId, mapper.valueToTree(deletedAlarm));
break;
default: default:
ListenableFuture<Alarm> alarmFuture = alarmService.findAlarmByIdAsync(tenantId, alarmId); ListenableFuture<Alarm> alarmFuture = alarmService.findAlarmByIdAsync(tenantId, alarmId);
Futures.addCallback(alarmFuture, new FutureCallback<Alarm>() { return Futures.transformAsync(alarmFuture, alarm -> {
@Override if (alarm == null) {
public void onSuccess(@Nullable Alarm alarm) { return Futures.immediateFuture(null);
if (alarm != null) {
EdgeEventType type = EdgeUtils.getEdgeEventTypeByEntityType(alarm.getOriginator().getEntityType());
if (type != null) {
PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE);
PageData<EdgeId> pageData;
do {
pageData = edgeService.findRelatedEdgeIdsByEntityId(tenantId, alarm.getOriginator(), pageLink);
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
for (EdgeId edgeId : pageData.getData()) {
saveEdgeEvent(tenantId,
edgeId,
EdgeEventType.ALARM,
EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()),
alarmId,
null);
}
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
}
} while (pageData != null && pageData.hasNext());
}
}
} }
EdgeEventType type = EdgeUtils.getEdgeEventTypeByEntityType(alarm.getOriginator().getEntityType());
@Override if (type == null) {
public void onFailure(Throwable t) { return Futures.immediateFuture(null);
log.warn("[{}] can't find alarm by id [{}] {}", tenantId.getId(), alarmId.getId(), t);
} }
PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE);
PageData<EdgeId> pageData;
List<ListenableFuture<Void>> futures = new ArrayList<>();
do {
pageData = edgeService.findRelatedEdgeIdsByEntityId(tenantId, alarm.getOriginator(), pageLink);
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
for (EdgeId relatedEdgeId : pageData.getData()) {
futures.add(saveEdgeEvent(tenantId,
relatedEdgeId,
EdgeEventType.ALARM,
EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()),
alarmId,
null));
}
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
}
} while (pageData != null && pageData.hasNext());
return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService);
}, dbCallbackExecutorService); }, dbCallbackExecutorService);
} }
} }

41
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java

@ -17,10 +17,13 @@ package org.thingsboard.server.service.edge.rpc.processor;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.HasCustomerId; import org.thingsboard.server.common.data.HasCustomerId;
import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEvent;
@ -67,6 +70,9 @@ import org.thingsboard.server.service.executors.DbCallbackExecutorService;
import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.service.state.DeviceStateService; import org.thingsboard.server.service.state.DeviceStateService;
import java.util.ArrayList;
import java.util.List;
@Slf4j @Slf4j
public abstract class BaseEdgeProcessor { public abstract class BaseEdgeProcessor {
@ -179,27 +185,22 @@ public abstract class BaseEdgeProcessor {
@Autowired @Autowired
protected DbCallbackExecutorService dbCallbackExecutorService; protected DbCallbackExecutorService dbCallbackExecutorService;
protected void saveEdgeEvent(TenantId tenantId, protected ListenableFuture<Void> saveEdgeEvent(TenantId tenantId,
EdgeId edgeId, EdgeId edgeId,
EdgeEventType type, EdgeEventType type,
EdgeEventActionType action, EdgeEventActionType action,
EntityId entityId, EntityId entityId,
JsonNode body) { JsonNode body) {
log.debug("Pushing event to edge queue. tenantId [{}], edgeId [{}], type[{}], " + log.debug("Pushing event to edge queue. tenantId [{}], edgeId [{}], type[{}], " +
"action [{}], entityId [{}], body [{}]", "action [{}], entityId [{}], body [{}]",
tenantId, edgeId, type, action, entityId, body); tenantId, edgeId, type, action, entityId, body);
EdgeEvent edgeEvent = new EdgeEvent(); EdgeEvent edgeEvent = EdgeUtils.constructEdgeEvent(tenantId, edgeId, type, action, entityId, body);
edgeEvent.setTenantId(tenantId);
edgeEvent.setEdgeId(edgeId); return Futures.transform(edgeEventService.saveAsync(edgeEvent), unused -> {
edgeEvent.setType(type); tbClusterService.onEdgeEventUpdate(tenantId, edgeId);
edgeEvent.setAction(action); return null;
if (entityId != null) { }, dbCallbackExecutorService);
edgeEvent.setEntityId(entityId.getId());
}
edgeEvent.setBody(body);
edgeEventService.save(edgeEvent);
tbClusterService.onEdgeEventUpdate(tenantId, edgeId);
} }
protected CustomerId getCustomerIdIfEdgeAssignedToCustomer(HasCustomerId hasCustomerIdEntity, Edge edge) { protected CustomerId getCustomerIdIfEdgeAssignedToCustomer(HasCustomerId hasCustomerIdEntity, Edge edge) {
@ -210,19 +211,21 @@ public abstract class BaseEdgeProcessor {
} }
} }
protected void processActionForAllEdges(TenantId tenantId, EdgeEventType type, EdgeEventActionType actionType, EntityId entityId) { protected ListenableFuture<Void> processActionForAllEdges(TenantId tenantId, EdgeEventType type, EdgeEventActionType actionType, EntityId entityId) {
PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE);
PageData<Edge> pageData; PageData<Edge> pageData;
List<ListenableFuture<Void>> futures = new ArrayList<>();
do { do {
pageData = edgeService.findEdgesByTenantId(tenantId, pageLink); pageData = edgeService.findEdgesByTenantId(tenantId, pageLink);
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
for (Edge edge : pageData.getData()) { for (Edge edge : pageData.getData()) {
saveEdgeEvent(tenantId, edge.getId(), type, actionType, entityId, null); futures.add(saveEdgeEvent(tenantId, edge.getId(), type, actionType, entityId, null));
} }
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink(); pageLink = pageLink.nextPageLink();
} }
} }
} while (pageData != null && pageData.hasNext()); } while (pageData != null && pageData.hasNext());
return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService);
} }
} }

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

@ -15,6 +15,8 @@
*/ */
package org.thingsboard.server.service.edge.rpc.processor; package org.thingsboard.server.service.edge.rpc.processor;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
@ -35,6 +37,8 @@ import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID; import java.util.UUID;
@Component @Component
@ -70,7 +74,7 @@ public class CustomerEdgeProcessor extends BaseEdgeProcessor {
return downlinkMsg; return downlinkMsg;
} }
public void processCustomerNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { public ListenableFuture<Void> processCustomerNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) {
EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()); EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction());
EdgeEventType type = EdgeEventType.valueOf(edgeNotificationMsg.getType()); EdgeEventType type = EdgeEventType.valueOf(edgeNotificationMsg.getType());
UUID uuid = new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB()); UUID uuid = new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB());
@ -79,22 +83,24 @@ public class CustomerEdgeProcessor extends BaseEdgeProcessor {
case UPDATED: case UPDATED:
PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE);
PageData<Edge> pageData; PageData<Edge> pageData;
List<ListenableFuture<Void>> futures = new ArrayList<>();
do { do {
pageData = edgeService.findEdgesByTenantIdAndCustomerId(tenantId, customerId, pageLink); pageData = edgeService.findEdgesByTenantIdAndCustomerId(tenantId, customerId, pageLink);
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
for (Edge edge : pageData.getData()) { for (Edge edge : pageData.getData()) {
saveEdgeEvent(tenantId, edge.getId(), type, actionType, customerId, null); futures.add(saveEdgeEvent(tenantId, edge.getId(), type, actionType, customerId, null));
} }
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink(); pageLink = pageLink.nextPageLink();
} }
} }
} while (pageData != null && pageData.hasNext()); } while (pageData != null && pageData.hasNext());
break; return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService);
case DELETED: case DELETED:
EdgeId edgeId = new EdgeId(new UUID(edgeNotificationMsg.getEdgeIdMSB(), edgeNotificationMsg.getEdgeIdLSB())); EdgeId edgeId = new EdgeId(new UUID(edgeNotificationMsg.getEdgeIdMSB(), edgeNotificationMsg.getEdgeIdLSB()));
saveEdgeEvent(tenantId, edgeId, type, actionType, customerId, null); return saveEdgeEvent(tenantId, edgeId, type, actionType, customerId, null);
break; default:
return Futures.immediateFuture(null);
} }
} }

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

@ -84,7 +84,7 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor {
if (deviceAlreadyExistsForThisEdge) { if (deviceAlreadyExistsForThisEdge) {
log.info("[{}] Device with name '{}' already exists on the cloud, and related to this edge [{}]. " + log.info("[{}] Device with name '{}' already exists on the cloud, and related to this edge [{}]. " +
"deviceUpdateMsg [{}], Updating device", tenantId, deviceName, edge.getId(), deviceUpdateMsg); "deviceUpdateMsg [{}], Updating device", tenantId, deviceName, edge.getId(), deviceUpdateMsg);
updateDevice(tenantId, edge, deviceUpdateMsg); return updateDevice(tenantId, edge, deviceUpdateMsg);
} else { } else {
log.info("[{}] Device with name '{}' already exists on the cloud, but not related to this edge [{}]. deviceUpdateMsg [{}]." + log.info("[{}] Device with name '{}' already exists on the cloud, but not related to this edge [{}]. deviceUpdateMsg [{}]." +
"Creating a new device with random prefix and relate to this edge", tenantId, deviceName, edge.getId(), deviceUpdateMsg); "Creating a new device with random prefix and relate to this edge", tenantId, deviceName, edge.getId(), deviceUpdateMsg);
@ -99,8 +99,10 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor {
} }
ObjectNode body = mapper.createObjectNode(); ObjectNode body = mapper.createObjectNode();
body.put("conflictName", deviceName); body.put("conflictName", deviceName);
saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.ENTITY_MERGE_REQUEST, newDevice.getId(), body); ListenableFuture<Void> input = saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.ENTITY_MERGE_REQUEST, newDevice.getId(), body);
saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.CREDENTIALS_REQUEST, newDevice.getId(), null); return Futures.transformAsync(input, unused ->
saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.CREDENTIALS_REQUEST, newDevice.getId(), null),
dbCallbackExecutorService);
} }
} else { } else {
log.info("[{}] Creating new device and replacing device entity on the edge [{}]", tenantId, deviceUpdateMsg); log.info("[{}] Creating new device and replacing device entity on the edge [{}]", tenantId, deviceUpdateMsg);
@ -111,24 +113,22 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor {
log.error(errMsg, e); log.error(errMsg, e);
return Futures.immediateFuture(null); return Futures.immediateFuture(null);
} }
saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.CREDENTIALS_REQUEST, device.getId(), null); return saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.CREDENTIALS_REQUEST, device.getId(), null);
} }
break;
case ENTITY_UPDATED_RPC_MESSAGE: case ENTITY_UPDATED_RPC_MESSAGE:
updateDevice(tenantId, edge, deviceUpdateMsg); return updateDevice(tenantId, edge, deviceUpdateMsg);
break;
case ENTITY_DELETED_RPC_MESSAGE: case ENTITY_DELETED_RPC_MESSAGE:
DeviceId deviceId = new DeviceId(new UUID(deviceUpdateMsg.getIdMSB(), deviceUpdateMsg.getIdLSB())); DeviceId deviceId = new DeviceId(new UUID(deviceUpdateMsg.getIdMSB(), deviceUpdateMsg.getIdLSB()));
Device deviceToDelete = deviceService.findDeviceById(tenantId, deviceId); Device deviceToDelete = deviceService.findDeviceById(tenantId, deviceId);
if (deviceToDelete != null) { if (deviceToDelete != null) {
deviceService.unassignDeviceFromEdge(tenantId, deviceId, edge.getId()); deviceService.unassignDeviceFromEdge(tenantId, deviceId, edge.getId());
} }
break; return Futures.immediateFuture(null);
case UNRECOGNIZED: case UNRECOGNIZED:
default:
log.error("Unsupported msg type {}", deviceUpdateMsg.getMsgType()); log.error("Unsupported msg type {}", deviceUpdateMsg.getMsgType());
return Futures.immediateFailedFuture(new RuntimeException("Unsupported msg type " + deviceUpdateMsg.getMsgType())); return Futures.immediateFailedFuture(new RuntimeException("Unsupported msg type " + deviceUpdateMsg.getMsgType()));
} }
return Futures.immediateFuture(null);
} }
private boolean isDeviceAlreadyExistsOnCloudForThisEdge(TenantId tenantId, Edge edge, Device device) { private boolean isDeviceAlreadyExistsOnCloudForThisEdge(TenantId tenantId, Edge edge, Device device) {
@ -174,7 +174,7 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor {
} }
private void updateDevice(TenantId tenantId, Edge edge, DeviceUpdateMsg deviceUpdateMsg) { private ListenableFuture<Void> updateDevice(TenantId tenantId, Edge edge, DeviceUpdateMsg deviceUpdateMsg) {
DeviceId deviceId = new DeviceId(new UUID(deviceUpdateMsg.getIdMSB(), deviceUpdateMsg.getIdLSB())); DeviceId deviceId = new DeviceId(new UUID(deviceUpdateMsg.getIdMSB(), deviceUpdateMsg.getIdLSB()));
Device device = deviceService.findDeviceById(tenantId, deviceId); Device device = deviceService.findDeviceById(tenantId, deviceId);
if (device != null) { if (device != null) {
@ -194,9 +194,11 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor {
} }
Device savedDevice = deviceService.saveDevice(device); Device savedDevice = deviceService.saveDevice(device);
tbClusterService.onDeviceUpdated(savedDevice, device); tbClusterService.onDeviceUpdated(savedDevice, device);
saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.CREDENTIALS_REQUEST, deviceId, null); return saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.CREDENTIALS_REQUEST, deviceId, null);
} else { } else {
log.warn("[{}] can't find device [{}], edge [{}]", tenantId, deviceUpdateMsg, edge.getId()); String errMsg = String.format("[%s] can't find device [%s], edge [%s]", tenantId, deviceUpdateMsg, edge.getId());
log.warn(errMsg);
return Futures.immediateFailedFuture(new RuntimeException(errMsg));
} }
} }

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

@ -15,11 +15,9 @@
*/ */
package org.thingsboard.server.service.edge.rpc.processor; package org.thingsboard.server.service.edge.rpc.processor;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.Edge;
@ -33,6 +31,8 @@ import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID; import java.util.UUID;
@Component @Component
@ -40,7 +40,7 @@ import java.util.UUID;
@TbCoreComponent @TbCoreComponent
public class EdgeProcessor extends BaseEdgeProcessor { public class EdgeProcessor extends BaseEdgeProcessor {
public void processEdgeNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { public ListenableFuture<Void> processEdgeNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) {
try { try {
EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()); EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction());
EdgeId edgeId = new EdgeId(new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB())); EdgeId edgeId = new EdgeId(new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB()));
@ -49,54 +49,43 @@ public class EdgeProcessor extends BaseEdgeProcessor {
case ASSIGNED_TO_CUSTOMER: case ASSIGNED_TO_CUSTOMER:
CustomerId customerId = mapper.readValue(edgeNotificationMsg.getBody(), CustomerId.class); CustomerId customerId = mapper.readValue(edgeNotificationMsg.getBody(), CustomerId.class);
edgeFuture = edgeService.findEdgeByIdAsync(tenantId, edgeId); edgeFuture = edgeService.findEdgeByIdAsync(tenantId, edgeId);
Futures.addCallback(edgeFuture, new FutureCallback<Edge>() { return Futures.transformAsync(edgeFuture, edge -> {
@Override if (edge == null || customerId.isNullUid()) {
public void onSuccess(@Nullable Edge edge) { return Futures.immediateFuture(null);
if (edge != null && !customerId.isNullUid()) {
saveEdgeEvent(edge.getTenantId(), edge.getId(), EdgeEventType.CUSTOMER, EdgeEventActionType.ADDED, customerId, null);
PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE);
PageData<User> pageData;
do {
pageData = userService.findCustomerUsers(tenantId, customerId, pageLink);
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
log.trace("[{}] [{}] user(s) are going to be added to edge.", edge.getId(), pageData.getData().size());
for (User user : pageData.getData()) {
saveEdgeEvent(edge.getTenantId(), edge.getId(), EdgeEventType.USER, EdgeEventActionType.ADDED, user.getId(), null);
}
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
}
} while (pageData != null && pageData.hasNext());
}
}
@Override
public void onFailure(Throwable t) {
log.error("Can't find edge by id [{}]", edgeNotificationMsg, t);
} }
List<ListenableFuture<Void>> futures = new ArrayList<>();
futures.add(saveEdgeEvent(edge.getTenantId(), edge.getId(), EdgeEventType.CUSTOMER, EdgeEventActionType.ADDED, customerId, null));
PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE);
PageData<User> pageData;
do {
pageData = userService.findCustomerUsers(tenantId, customerId, pageLink);
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
log.trace("[{}] [{}] user(s) are going to be added to edge.", edge.getId(), pageData.getData().size());
for (User user : pageData.getData()) {
futures.add(saveEdgeEvent(edge.getTenantId(), edge.getId(), EdgeEventType.USER, EdgeEventActionType.ADDED, user.getId(), null));
}
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
}
} while (pageData != null && pageData.hasNext());
return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService);
}, dbCallbackExecutorService); }, dbCallbackExecutorService);
break;
case UNASSIGNED_FROM_CUSTOMER: case UNASSIGNED_FROM_CUSTOMER:
CustomerId customerIdToDelete = mapper.readValue(edgeNotificationMsg.getBody(), CustomerId.class); CustomerId customerIdToDelete = mapper.readValue(edgeNotificationMsg.getBody(), CustomerId.class);
edgeFuture = edgeService.findEdgeByIdAsync(tenantId, edgeId); edgeFuture = edgeService.findEdgeByIdAsync(tenantId, edgeId);
Futures.addCallback(edgeFuture, new FutureCallback<Edge>() { return Futures.transformAsync(edgeFuture, edge -> {
@Override if (edge == null || customerIdToDelete.isNullUid()) {
public void onSuccess(@Nullable Edge edge) { return Futures.immediateFuture(null);
if (edge != null && !customerIdToDelete.isNullUid()) {
saveEdgeEvent(edge.getTenantId(), edge.getId(), EdgeEventType.CUSTOMER, EdgeEventActionType.DELETED, customerIdToDelete, null);
}
}
@Override
public void onFailure(Throwable t) {
log.error("Can't find edge by id [{}]", edgeNotificationMsg, t);
} }
return saveEdgeEvent(edge.getTenantId(), edge.getId(), EdgeEventType.CUSTOMER, EdgeEventActionType.DELETED, customerIdToDelete, null);
}, dbCallbackExecutorService); }, dbCallbackExecutorService);
break; default:
return Futures.immediateFuture(null);
} }
} catch (Exception e) { } catch (Exception e) {
log.error("Exception during processing edge event", e); log.error("Exception during processing edge event", e);
return Futures.immediateFailedFuture(e);
} }
} }
} }

130
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/EntityEdgeProcessor.java

@ -15,11 +15,9 @@
*/ */
package org.thingsboard.server.service.edge.rpc.processor; package org.thingsboard.server.service.edge.rpc.processor;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.EdgeUtils;
@ -45,6 +43,7 @@ import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
@ -89,92 +88,107 @@ public class EntityEdgeProcessor extends BaseEdgeProcessor {
return downlinkMsg; return downlinkMsg;
} }
public void processEntityNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { public ListenableFuture<Void> processEntityNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) {
EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()); EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction());
EdgeEventType type = EdgeEventType.valueOf(edgeNotificationMsg.getType()); EdgeEventType type = EdgeEventType.valueOf(edgeNotificationMsg.getType());
EntityId entityId = EntityIdFactory.getByEdgeEventTypeAndUuid(type, EntityId entityId = EntityIdFactory.getByEdgeEventTypeAndUuid(type,
new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB())); new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB()));
EdgeId edgeId = null; EdgeId edgeId = safeGetEdgeId(edgeNotificationMsg);
if (edgeNotificationMsg.getEdgeIdMSB() != 0 && edgeNotificationMsg.getEdgeIdLSB() != 0) {
edgeId = new EdgeId(new UUID(edgeNotificationMsg.getEdgeIdMSB(), edgeNotificationMsg.getEdgeIdLSB()));
}
switch (actionType) { switch (actionType) {
case ADDED: // used only for USER entity case ADDED: // used only for USER entity
case UPDATED: case UPDATED:
case CREDENTIALS_UPDATED: case CREDENTIALS_UPDATED:
pushNotificationToAllRelatedEdges(tenantId, entityId, type, actionType); return pushNotificationToAllRelatedEdges(tenantId, entityId, type, actionType);
break;
case ASSIGNED_TO_CUSTOMER: case ASSIGNED_TO_CUSTOMER:
case UNASSIGNED_FROM_CUSTOMER: case UNASSIGNED_FROM_CUSTOMER:
PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); return pushNotificationToAllRelatedCustomerEdges(tenantId, edgeNotificationMsg, entityId, actionType, type);
PageData<EdgeId> pageData;
do {
pageData = edgeService.findRelatedEdgeIdsByEntityId(tenantId, entityId, pageLink);
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
for (EdgeId relatedEdgeId : pageData.getData()) {
try {
CustomerId customerId = mapper.readValue(edgeNotificationMsg.getBody(), CustomerId.class);
ListenableFuture<Edge> future = edgeService.findEdgeByIdAsync(tenantId, relatedEdgeId);
Futures.addCallback(future, new FutureCallback<>() {
@Override
public void onSuccess(@Nullable Edge edge) {
if (edge != null && edge.getCustomerId() != null &&
!edge.getCustomerId().isNullUid() && edge.getCustomerId().equals(customerId)) {
saveEdgeEvent(tenantId, relatedEdgeId, type, actionType, entityId, null);
}
}
@Override
public void onFailure(Throwable t) {
log.error("Failed to find edge by id [{}] {}", edgeNotificationMsg, t);
}
}, dbCallbackExecutorService);
} catch (Exception e) {
log.error("Can't parse customer id from entity body [{}]", edgeNotificationMsg, e);
}
}
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
}
} while (pageData != null && pageData.hasNext());
break;
case DELETED: case DELETED:
if (edgeId != null) { if (edgeId != null) {
saveEdgeEvent(tenantId, edgeId, type, actionType, entityId, null); return saveEdgeEvent(tenantId, edgeId, type, actionType, entityId, null);
} else { } else {
pushNotificationToAllRelatedEdges(tenantId, entityId, type, actionType); return pushNotificationToAllRelatedEdges(tenantId, entityId, type, actionType);
} }
break;
case ASSIGNED_TO_EDGE: case ASSIGNED_TO_EDGE:
case UNASSIGNED_FROM_EDGE: case UNASSIGNED_FROM_EDGE:
saveEdgeEvent(tenantId, edgeId, type, actionType, entityId, null); ListenableFuture<Void> future = saveEdgeEvent(tenantId, edgeId, type, actionType, entityId, null);
if (type.equals(EdgeEventType.RULE_CHAIN)) { return Futures.transformAsync(future, unused -> {
updateDependentRuleChains(tenantId, new RuleChainId(entityId.getId()), edgeId); if (type.equals(EdgeEventType.RULE_CHAIN)) {
return updateDependentRuleChains(tenantId, new RuleChainId(entityId.getId()), edgeId);
} else {
return Futures.immediateFuture(null);
}
}, dbCallbackExecutorService);
default:
return Futures.immediateFuture(null);
}
}
private ListenableFuture<Void> pushNotificationToAllRelatedCustomerEdges(TenantId tenantId,
TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg,
EntityId entityId,
EdgeEventActionType actionType,
EdgeEventType type) {
PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE);
PageData<EdgeId> pageData;
List<ListenableFuture<Void>> futures = new ArrayList<>();
do {
pageData = edgeService.findRelatedEdgeIdsByEntityId(tenantId, entityId, pageLink);
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
for (EdgeId relatedEdgeId : pageData.getData()) {
try {
CustomerId customerId = mapper.readValue(edgeNotificationMsg.getBody(), CustomerId.class);
ListenableFuture<Edge> future = edgeService.findEdgeByIdAsync(tenantId, relatedEdgeId);
futures.add(Futures.transformAsync(future, edge -> {
if (edge != null && edge.getCustomerId() != null &&
!edge.getCustomerId().isNullUid() && edge.getCustomerId().equals(customerId)) {
return saveEdgeEvent(tenantId, relatedEdgeId, type, actionType, entityId, null);
} else {
return Futures.immediateFuture(null);
}
}, dbCallbackExecutorService));
} catch (Exception e) {
log.error("Can't parse customer id from entity body [{}]", edgeNotificationMsg, e);
return Futures.immediateFailedFuture(e);
}
} }
break; if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
}
} while (pageData != null && pageData.hasNext());
return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService);
}
private EdgeId safeGetEdgeId(TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) {
if (edgeNotificationMsg.getEdgeIdMSB() != 0 && edgeNotificationMsg.getEdgeIdLSB() != 0) {
return new EdgeId(new UUID(edgeNotificationMsg.getEdgeIdMSB(), edgeNotificationMsg.getEdgeIdLSB()));
} else {
return null;
} }
} }
private void pushNotificationToAllRelatedEdges(TenantId tenantId, EntityId entityId, EdgeEventType type, EdgeEventActionType actionType) { private ListenableFuture<Void> pushNotificationToAllRelatedEdges(TenantId tenantId, EntityId entityId, EdgeEventType type, EdgeEventActionType actionType) {
PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE);
PageData<EdgeId> pageData; PageData<EdgeId> pageData;
List<ListenableFuture<Void>> futures = new ArrayList<>();
do { do {
pageData = edgeService.findRelatedEdgeIdsByEntityId(tenantId, entityId, pageLink); pageData = edgeService.findRelatedEdgeIdsByEntityId(tenantId, entityId, pageLink);
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
for (EdgeId relatedEdgeId : pageData.getData()) { for (EdgeId relatedEdgeId : pageData.getData()) {
saveEdgeEvent(tenantId, relatedEdgeId, type, actionType, entityId, null); futures.add(saveEdgeEvent(tenantId, relatedEdgeId, type, actionType, entityId, null));
} }
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink(); pageLink = pageLink.nextPageLink();
} }
} }
} while (pageData != null && pageData.hasNext()); } while (pageData != null && pageData.hasNext());
return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService);
} }
private void updateDependentRuleChains(TenantId tenantId, RuleChainId processingRuleChainId, EdgeId edgeId) { private ListenableFuture<Void> updateDependentRuleChains(TenantId tenantId, RuleChainId processingRuleChainId, EdgeId edgeId) {
PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE);
PageData<RuleChain> pageData; PageData<RuleChain> pageData;
List<ListenableFuture<Void>> futures = new ArrayList<>();
do { do {
pageData = ruleChainService.findRuleChainsByTenantIdAndEdgeId(tenantId, edgeId, pageLink); pageData = ruleChainService.findRuleChainsByTenantIdAndEdgeId(tenantId, edgeId, pageLink);
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
@ -185,12 +199,12 @@ public class EntityEdgeProcessor extends BaseEdgeProcessor {
if (connectionInfos != null && !connectionInfos.isEmpty()) { if (connectionInfos != null && !connectionInfos.isEmpty()) {
for (RuleChainConnectionInfo connectionInfo : connectionInfos) { for (RuleChainConnectionInfo connectionInfo : connectionInfos) {
if (connectionInfo.getTargetRuleChainId().equals(processingRuleChainId)) { if (connectionInfo.getTargetRuleChainId().equals(processingRuleChainId)) {
saveEdgeEvent(tenantId, futures.add(saveEdgeEvent(tenantId,
edgeId, edgeId,
EdgeEventType.RULE_CHAIN_METADATA, EdgeEventType.RULE_CHAIN_METADATA,
EdgeEventActionType.UPDATED, EdgeEventActionType.UPDATED,
ruleChain.getId(), ruleChain.getId(),
null); null));
} }
} }
} }
@ -201,9 +215,10 @@ public class EntityEdgeProcessor extends BaseEdgeProcessor {
} }
} }
} while (pageData != null && pageData.hasNext()); } while (pageData != null && pageData.hasNext());
return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService);
} }
public void processEntityNotificationForAllEdges(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { public ListenableFuture<Void> processEntityNotificationForAllEdges(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) {
EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()); EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction());
EdgeEventType type = EdgeEventType.valueOf(edgeNotificationMsg.getType()); EdgeEventType type = EdgeEventType.valueOf(edgeNotificationMsg.getType());
EntityId entityId = EntityIdFactory.getByEdgeEventTypeAndUuid(type, new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB())); EntityId entityId = EntityIdFactory.getByEdgeEventTypeAndUuid(type, new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB()));
@ -211,8 +226,9 @@ public class EntityEdgeProcessor extends BaseEdgeProcessor {
case ADDED: case ADDED:
case UPDATED: case UPDATED:
case DELETED: case DELETED:
processActionForAllEdges(tenantId, type, actionType, entityId); return processActionForAllEdges(tenantId, type, actionType, entityId);
break; default:
return Futures.immediateFuture(null);
} }
} }
} }

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

@ -126,24 +126,29 @@ public class RelationEdgeProcessor extends BaseEdgeProcessor {
.build(); .build();
} }
public void processRelationNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) throws JsonProcessingException { public ListenableFuture<Void> processRelationNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) throws JsonProcessingException {
EntityRelation relation = mapper.readValue(edgeNotificationMsg.getBody(), EntityRelation.class); EntityRelation relation = mapper.readValue(edgeNotificationMsg.getBody(), EntityRelation.class);
if (!relation.getFrom().getEntityType().equals(EntityType.EDGE) && if (relation.getFrom().getEntityType().equals(EntityType.EDGE) ||
!relation.getTo().getEntityType().equals(EntityType.EDGE)) { relation.getTo().getEntityType().equals(EntityType.EDGE)) {
Set<EdgeId> uniqueEdgeIds = new HashSet<>(); return Futures.immediateFuture(null);
uniqueEdgeIds.addAll(findRelatedEdgeIds(tenantId, relation.getTo())); }
uniqueEdgeIds.addAll(findRelatedEdgeIds(tenantId, relation.getFrom()));
if (!uniqueEdgeIds.isEmpty()) { Set<EdgeId> uniqueEdgeIds = new HashSet<>();
for (EdgeId edgeId : uniqueEdgeIds) { uniqueEdgeIds.addAll(findRelatedEdgeIds(tenantId, relation.getTo()));
saveEdgeEvent(tenantId, uniqueEdgeIds.addAll(findRelatedEdgeIds(tenantId, relation.getFrom()));
edgeId, if (uniqueEdgeIds.isEmpty()) {
EdgeEventType.RELATION, return Futures.immediateFuture(null);
EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()), }
null, List<ListenableFuture<Void>> futures = new ArrayList<>();
mapper.valueToTree(relation)); for (EdgeId edgeId : uniqueEdgeIds) {
} futures.add(saveEdgeEvent(tenantId,
} edgeId,
EdgeEventType.RELATION,
EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()),
null,
mapper.valueToTree(relation)));
} }
return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService);
} }
private List<EdgeId> findRelatedEdgeIds(TenantId tenantId, EntityId entityId) { private List<EdgeId> findRelatedEdgeIds(TenantId tenantId, EntityId entityId) {

283
application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/DefaultEdgeRequestsService.java

@ -26,6 +26,7 @@ import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.EdgeUtils;
@ -71,9 +72,7 @@ import org.thingsboard.server.gen.edge.v1.RelationRequestMsg;
import org.thingsboard.server.gen.edge.v1.RuleChainMetadataRequestMsg; import org.thingsboard.server.gen.edge.v1.RuleChainMetadataRequestMsg;
import org.thingsboard.server.gen.edge.v1.UserCredentialsRequestMsg; import org.thingsboard.server.gen.edge.v1.UserCredentialsRequestMsg;
import org.thingsboard.server.gen.edge.v1.WidgetBundleTypesRequestMsg; import org.thingsboard.server.gen.edge.v1.WidgetBundleTypesRequestMsg;
import org.thingsboard.server.service.edge.rpc.EdgeEventUtils;
import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.executors.DbCallbackExecutorService;
import org.thingsboard.server.cluster.TbClusterService;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
@ -122,13 +121,13 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService {
@Override @Override
public ListenableFuture<Void> processRuleChainMetadataRequestMsg(TenantId tenantId, Edge edge, RuleChainMetadataRequestMsg ruleChainMetadataRequestMsg) { public ListenableFuture<Void> processRuleChainMetadataRequestMsg(TenantId tenantId, Edge edge, RuleChainMetadataRequestMsg ruleChainMetadataRequestMsg) {
log.trace("[{}] processRuleChainMetadataRequestMsg [{}][{}]", tenantId, edge.getName(), ruleChainMetadataRequestMsg); log.trace("[{}] processRuleChainMetadataRequestMsg [{}][{}]", tenantId, edge.getName(), ruleChainMetadataRequestMsg);
if (ruleChainMetadataRequestMsg.getRuleChainIdMSB() != 0 && ruleChainMetadataRequestMsg.getRuleChainIdLSB() != 0) { if (ruleChainMetadataRequestMsg.getRuleChainIdMSB() == 0 || ruleChainMetadataRequestMsg.getRuleChainIdLSB() == 0) {
RuleChainId ruleChainId = return Futures.immediateFuture(null);
new RuleChainId(new UUID(ruleChainMetadataRequestMsg.getRuleChainIdMSB(), ruleChainMetadataRequestMsg.getRuleChainIdLSB()));
saveEdgeEvent(tenantId, edge.getId(),
EdgeEventType.RULE_CHAIN_METADATA, EdgeEventActionType.ADDED, ruleChainId, null);
} }
return Futures.immediateFuture(null); RuleChainId ruleChainId =
new RuleChainId(new UUID(ruleChainMetadataRequestMsg.getRuleChainIdMSB(), ruleChainMetadataRequestMsg.getRuleChainIdLSB()));
return saveEdgeEvent(tenantId, edge.getId(),
EdgeEventType.RULE_CHAIN_METADATA, EdgeEventActionType.ADDED, ruleChainId, null);
} }
@Override @Override
@ -138,63 +137,72 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService {
EntityType.valueOf(attributesRequestMsg.getEntityType()), EntityType.valueOf(attributesRequestMsg.getEntityType()),
new UUID(attributesRequestMsg.getEntityIdMSB(), attributesRequestMsg.getEntityIdLSB())); new UUID(attributesRequestMsg.getEntityIdMSB(), attributesRequestMsg.getEntityIdLSB()));
final EdgeEventType type = EdgeUtils.getEdgeEventTypeByEntityType(entityId.getEntityType()); final EdgeEventType type = EdgeUtils.getEdgeEventTypeByEntityType(entityId.getEntityType());
if (type != null) { if (type == null) {
SettableFuture<Void> futureToSet = SettableFuture.create(); log.warn("[{}] Type doesn't supported {}", tenantId, entityId.getEntityType());
String scope = attributesRequestMsg.getScope(); return Futures.immediateFuture(null);
ListenableFuture<List<AttributeKvEntry>> findAttrFuture = attributesService.findAll(tenantId, entityId, scope); }
Futures.addCallback(findAttrFuture, new FutureCallback<List<AttributeKvEntry>>() { SettableFuture<Void> futureToSet = SettableFuture.create();
@Override String scope = attributesRequestMsg.getScope();
public void onSuccess(@Nullable List<AttributeKvEntry> ssAttributes) { ListenableFuture<List<AttributeKvEntry>> findAttrFuture = attributesService.findAll(tenantId, entityId, scope);
if (ssAttributes != null && !ssAttributes.isEmpty()) { Futures.addCallback(findAttrFuture, new FutureCallback<>() {
try { @Override
Map<String, Object> entityData = new HashMap<>(); public void onSuccess(@Nullable List<AttributeKvEntry> ssAttributes) {
ObjectNode attributes = mapper.createObjectNode(); if (ssAttributes == null || ssAttributes.isEmpty()) {
for (AttributeKvEntry attr : ssAttributes) { log.trace("[{}][{}] No attributes found for entity {} [{}]", tenantId,
if (attr.getDataType() == DataType.BOOLEAN && attr.getBooleanValue().isPresent()) { edge.getName(),
attributes.put(attr.getKey(), attr.getBooleanValue().get()); entityId.getEntityType(),
} else if (attr.getDataType() == DataType.DOUBLE && attr.getDoubleValue().isPresent()) { entityId.getId());
attributes.put(attr.getKey(), attr.getDoubleValue().get());
} else if (attr.getDataType() == DataType.LONG && attr.getLongValue().isPresent()) {
attributes.put(attr.getKey(), attr.getLongValue().get());
} else {
attributes.put(attr.getKey(), attr.getValueAsString());
}
}
entityData.put("kv", attributes);
entityData.put("scope", scope);
JsonNode body = mapper.valueToTree(entityData);
log.debug("Sending attributes data msg, entityId [{}], attributes [{}]", entityId, body);
saveEdgeEvent(tenantId,
edge.getId(),
type,
EdgeEventActionType.ATTRIBUTES_UPDATED,
entityId,
body);
} catch (Exception e) {
log.error("[{}] Failed to save attribute updates to the edge", edge.getName(), e);
futureToSet.setException(new RuntimeException("[" + edge.getName() + "] Failed to send attribute updates to the edge", e));
return;
}
} else {
log.trace("[{}][{}] No attributes found for entity {} [{}]", tenantId,
edge.getName(),
entityId.getEntityType(),
entityId.getId());
}
futureToSet.set(null); futureToSet.set(null);
return;
} }
@Override try {
public void onFailure(Throwable t) { Map<String, Object> entityData = new HashMap<>();
log.error("Can't find attributes [{}]", attributesRequestMsg, t); ObjectNode attributes = mapper.createObjectNode();
futureToSet.setException(t); for (AttributeKvEntry attr : ssAttributes) {
if (attr.getDataType() == DataType.BOOLEAN && attr.getBooleanValue().isPresent()) {
attributes.put(attr.getKey(), attr.getBooleanValue().get());
} else if (attr.getDataType() == DataType.DOUBLE && attr.getDoubleValue().isPresent()) {
attributes.put(attr.getKey(), attr.getDoubleValue().get());
} else if (attr.getDataType() == DataType.LONG && attr.getLongValue().isPresent()) {
attributes.put(attr.getKey(), attr.getLongValue().get());
} else {
attributes.put(attr.getKey(), attr.getValueAsString());
}
}
entityData.put("kv", attributes);
entityData.put("scope", scope);
JsonNode body = mapper.valueToTree(entityData);
log.debug("Sending attributes data msg, entityId [{}], attributes [{}]", entityId, body);
ListenableFuture<Void> future = saveEdgeEvent(tenantId, edge.getId(), type, EdgeEventActionType.ATTRIBUTES_UPDATED, entityId, body);
Futures.addCallback(future, new FutureCallback<>() {
@Override
public void onSuccess(@Nullable Void unused) {
futureToSet.set(null);
}
@Override
public void onFailure(Throwable throwable) {
String errMsg = String.format("[%s] Failed to save edge event [%s]", edge.getId(), attributesRequestMsg);
log.error(errMsg, throwable);
futureToSet.setException(new RuntimeException(errMsg, throwable));
}
}, dbCallbackExecutorService);
} catch (Exception e) {
String errMsg = String.format("[%s] Failed to save attribute updates to the edge [%s]", edge.getId(), attributesRequestMsg);
log.error(errMsg, e);
futureToSet.setException(new RuntimeException(errMsg, e));
} }
}, dbCallbackExecutorService); }
return futureToSet;
} else { @Override
log.warn("[{}] Type doesn't supported {}", tenantId, entityId.getEntityType()); public void onFailure(Throwable t) {
return Futures.immediateFuture(null); String errMsg = String.format("[%s] Can't find attributes [%s]", edge.getId(), attributesRequestMsg);
} log.error(errMsg, t);
futureToSet.setException(new RuntimeException(errMsg, t));
}
}, dbCallbackExecutorService);
return futureToSet;
} }
@Override @Override
@ -209,33 +217,49 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService {
futures.add(findRelationByQuery(tenantId, edge, entityId, EntitySearchDirection.TO)); futures.add(findRelationByQuery(tenantId, edge, entityId, EntitySearchDirection.TO));
ListenableFuture<List<List<EntityRelation>>> relationsListFuture = Futures.allAsList(futures); ListenableFuture<List<List<EntityRelation>>> relationsListFuture = Futures.allAsList(futures);
SettableFuture<Void> futureToSet = SettableFuture.create(); SettableFuture<Void> futureToSet = SettableFuture.create();
Futures.addCallback(relationsListFuture, new FutureCallback<List<List<EntityRelation>>>() { Futures.addCallback(relationsListFuture, new FutureCallback<>() {
@Override @Override
public void onSuccess(@Nullable List<List<EntityRelation>> relationsList) { public void onSuccess(@Nullable List<List<EntityRelation>> relationsList) {
try { try {
if (relationsList != null && !relationsList.isEmpty()) { if (relationsList != null && !relationsList.isEmpty()) {
List<ListenableFuture<Void>> futures = new ArrayList<>();
for (List<EntityRelation> entityRelations : relationsList) { for (List<EntityRelation> entityRelations : relationsList) {
log.trace("[{}] [{}] [{}] relation(s) are going to be pushed to edge.", edge.getId(), entityId, entityRelations.size()); log.trace("[{}] [{}] [{}] relation(s) are going to be pushed to edge.", edge.getId(), entityId, entityRelations.size());
for (EntityRelation relation : entityRelations) { for (EntityRelation relation : entityRelations) {
try { try {
if (!relation.getFrom().getEntityType().equals(EntityType.EDGE) && if (!relation.getFrom().getEntityType().equals(EntityType.EDGE) &&
!relation.getTo().getEntityType().equals(EntityType.EDGE)) { !relation.getTo().getEntityType().equals(EntityType.EDGE)) {
saveEdgeEvent(tenantId, futures.add(saveEdgeEvent(tenantId,
edge.getId(), edge.getId(),
EdgeEventType.RELATION, EdgeEventType.RELATION,
EdgeEventActionType.ADDED, EdgeEventActionType.ADDED,
null, null,
mapper.valueToTree(relation)); mapper.valueToTree(relation)));
} }
} catch (Exception e) { } catch (Exception e) {
log.error("Exception during loading relation [{}] to edge on sync!", relation, e); String errMsg = String.format("[%s] Exception during loading relation [%s] to edge on sync!", edge.getId(), relation);
futureToSet.setException(e); log.error(errMsg, e);
futureToSet.setException(new RuntimeException(errMsg, e));
return; return;
} }
} }
} }
Futures.addCallback(Futures.allAsList(futures), new FutureCallback<>() {
@Override
public void onSuccess(@Nullable List<Void> voids) {
futureToSet.set(null);
}
@Override
public void onFailure(Throwable throwable) {
String errMsg = String.format("[%s] Exception during saving edge events [%s]!", edge.getId(), relationRequestMsg);
log.error(errMsg, throwable);
futureToSet.setException(new RuntimeException(errMsg, throwable));
}
}, dbCallbackExecutorService);
} else {
futureToSet.set(null);
} }
futureToSet.set(null);
} catch (Exception e) { } catch (Exception e) {
log.error("Exception during loading relation(s) to edge on sync!", e); log.error("Exception during loading relation(s) to edge on sync!", e);
futureToSet.setException(e); futureToSet.setException(e);
@ -244,8 +268,9 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService {
@Override @Override
public void onFailure(Throwable t) { public void onFailure(Throwable t) {
log.error("[{}] Can't find relation by query. Entity id [{}]", tenantId, entityId, t); String errMsg = String.format("[%s] Can't find relation by query. Entity id [%s]!", tenantId, entityId);
futureToSet.setException(t); log.error(errMsg, t);
futureToSet.setException(new RuntimeException(errMsg, t));
} }
}, dbCallbackExecutorService); }, dbCallbackExecutorService);
return futureToSet; return futureToSet;
@ -261,40 +286,42 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService {
@Override @Override
public ListenableFuture<Void> processDeviceCredentialsRequestMsg(TenantId tenantId, Edge edge, DeviceCredentialsRequestMsg deviceCredentialsRequestMsg) { public ListenableFuture<Void> processDeviceCredentialsRequestMsg(TenantId tenantId, Edge edge, DeviceCredentialsRequestMsg deviceCredentialsRequestMsg) {
log.trace("[{}] processDeviceCredentialsRequestMsg [{}][{}]", tenantId, edge.getName(), deviceCredentialsRequestMsg); log.trace("[{}] processDeviceCredentialsRequestMsg [{}][{}]", tenantId, edge.getName(), deviceCredentialsRequestMsg);
if (deviceCredentialsRequestMsg.getDeviceIdMSB() != 0 && deviceCredentialsRequestMsg.getDeviceIdLSB() != 0) { if (deviceCredentialsRequestMsg.getDeviceIdMSB() == 0 || deviceCredentialsRequestMsg.getDeviceIdLSB() == 0) {
DeviceId deviceId = new DeviceId(new UUID(deviceCredentialsRequestMsg.getDeviceIdMSB(), deviceCredentialsRequestMsg.getDeviceIdLSB())); return Futures.immediateFuture(null);
saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE,
EdgeEventActionType.CREDENTIALS_UPDATED, deviceId, null);
} }
return Futures.immediateFuture(null); DeviceId deviceId = new DeviceId(new UUID(deviceCredentialsRequestMsg.getDeviceIdMSB(), deviceCredentialsRequestMsg.getDeviceIdLSB()));
return saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE,
EdgeEventActionType.CREDENTIALS_UPDATED, deviceId, null);
} }
@Override @Override
public ListenableFuture<Void> processUserCredentialsRequestMsg(TenantId tenantId, Edge edge, UserCredentialsRequestMsg userCredentialsRequestMsg) { public ListenableFuture<Void> processUserCredentialsRequestMsg(TenantId tenantId, Edge edge, UserCredentialsRequestMsg userCredentialsRequestMsg) {
log.trace("[{}] processUserCredentialsRequestMsg [{}][{}]", tenantId, edge.getName(), userCredentialsRequestMsg); log.trace("[{}] processUserCredentialsRequestMsg [{}][{}]", tenantId, edge.getName(), userCredentialsRequestMsg);
if (userCredentialsRequestMsg.getUserIdMSB() != 0 && userCredentialsRequestMsg.getUserIdLSB() != 0) { if (userCredentialsRequestMsg.getUserIdMSB() == 0 || userCredentialsRequestMsg.getUserIdLSB() == 0) {
UserId userId = new UserId(new UUID(userCredentialsRequestMsg.getUserIdMSB(), userCredentialsRequestMsg.getUserIdLSB())); return Futures.immediateFuture(null);
saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.USER,
EdgeEventActionType.CREDENTIALS_UPDATED, userId, null);
} }
return Futures.immediateFuture(null); UserId userId = new UserId(new UUID(userCredentialsRequestMsg.getUserIdMSB(), userCredentialsRequestMsg.getUserIdLSB()));
return saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.USER,
EdgeEventActionType.CREDENTIALS_UPDATED, userId, null);
} }
@Override @Override
public ListenableFuture<Void> processDeviceProfileDevicesRequestMsg(TenantId tenantId, Edge edge, DeviceProfileDevicesRequestMsg deviceProfileDevicesRequestMsg) { public ListenableFuture<Void> processDeviceProfileDevicesRequestMsg(TenantId tenantId, Edge edge, DeviceProfileDevicesRequestMsg deviceProfileDevicesRequestMsg) {
log.trace("[{}] processDeviceProfileDevicesRequestMsg [{}][{}]", tenantId, edge.getName(), deviceProfileDevicesRequestMsg); log.trace("[{}] processDeviceProfileDevicesRequestMsg [{}][{}]", tenantId, edge.getName(), deviceProfileDevicesRequestMsg);
if (deviceProfileDevicesRequestMsg.getDeviceProfileIdMSB() != 0 && deviceProfileDevicesRequestMsg.getDeviceProfileIdLSB() != 0) { if (deviceProfileDevicesRequestMsg.getDeviceProfileIdMSB() == 0 || deviceProfileDevicesRequestMsg.getDeviceProfileIdLSB() == 0) {
DeviceProfileId deviceProfileId = new DeviceProfileId(new UUID(deviceProfileDevicesRequestMsg.getDeviceProfileIdMSB(), deviceProfileDevicesRequestMsg.getDeviceProfileIdLSB())); return Futures.immediateFuture(null);
DeviceProfile deviceProfileById = deviceProfileService.findDeviceProfileById(tenantId, deviceProfileId); }
if (deviceProfileById != null) { DeviceProfileId deviceProfileId = new DeviceProfileId(new UUID(deviceProfileDevicesRequestMsg.getDeviceProfileIdMSB(), deviceProfileDevicesRequestMsg.getDeviceProfileIdLSB()));
syncDevices(tenantId, edge, deviceProfileById.getName()); DeviceProfile deviceProfileById = deviceProfileService.findDeviceProfileById(tenantId, deviceProfileId);
} if (deviceProfileById == null) {
return Futures.immediateFuture(null);
} }
return Futures.immediateFuture(null); return syncDevices(tenantId, edge, deviceProfileById.getName());
} }
private void syncDevices(TenantId tenantId, Edge edge, String deviceType) { private ListenableFuture<Void> syncDevices(TenantId tenantId, Edge edge, String deviceType) {
log.trace("[{}] syncDevices [{}][{}]", tenantId, edge.getName(), deviceType); log.trace("[{}] syncDevices [{}][{}]", tenantId, edge.getName(), deviceType);
List<ListenableFuture<Void>> futures = new ArrayList<>();
try { try {
PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE);
PageData<Device> pageData; PageData<Device> pageData;
@ -303,7 +330,7 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService {
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
log.trace("[{}] [{}] device(s) are going to be pushed to edge.", edge.getId(), pageData.getData().size()); log.trace("[{}] [{}] device(s) are going to be pushed to edge.", edge.getId(), pageData.getData().size());
for (Device device : pageData.getData()) { for (Device device : pageData.getData()) {
saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.ADDED, device.getId(), null); futures.add(saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.ADDED, device.getId(), null));
} }
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink(); pageLink = pageLink.nextPageLink();
@ -313,25 +340,26 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService {
} catch (Exception e) { } catch (Exception e) {
log.error("Exception during loading edge device(s) on sync!", e); log.error("Exception during loading edge device(s) on sync!", e);
} }
return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService);
} }
@Override @Override
public ListenableFuture<Void> processWidgetBundleTypesRequestMsg(TenantId tenantId, Edge edge, public ListenableFuture<Void> processWidgetBundleTypesRequestMsg(TenantId tenantId, Edge edge,
WidgetBundleTypesRequestMsg widgetBundleTypesRequestMsg) { WidgetBundleTypesRequestMsg widgetBundleTypesRequestMsg) {
log.trace("[{}] processWidgetBundleTypesRequestMsg [{}][{}]", tenantId, edge.getName(), widgetBundleTypesRequestMsg); log.trace("[{}] processWidgetBundleTypesRequestMsg [{}][{}]", tenantId, edge.getName(), widgetBundleTypesRequestMsg);
List<ListenableFuture<Void>> futures = new ArrayList<>();
if (widgetBundleTypesRequestMsg.getWidgetBundleIdMSB() != 0 && widgetBundleTypesRequestMsg.getWidgetBundleIdLSB() != 0) { if (widgetBundleTypesRequestMsg.getWidgetBundleIdMSB() != 0 && widgetBundleTypesRequestMsg.getWidgetBundleIdLSB() != 0) {
WidgetsBundleId widgetsBundleId = new WidgetsBundleId(new UUID(widgetBundleTypesRequestMsg.getWidgetBundleIdMSB(), widgetBundleTypesRequestMsg.getWidgetBundleIdLSB())); WidgetsBundleId widgetsBundleId = new WidgetsBundleId(new UUID(widgetBundleTypesRequestMsg.getWidgetBundleIdMSB(), widgetBundleTypesRequestMsg.getWidgetBundleIdLSB()));
WidgetsBundle widgetsBundleById = widgetsBundleService.findWidgetsBundleById(tenantId, widgetsBundleId); WidgetsBundle widgetsBundleById = widgetsBundleService.findWidgetsBundleById(tenantId, widgetsBundleId);
if (widgetsBundleById != null) { if (widgetsBundleById != null) {
List<WidgetType> widgetTypesToPush = List<WidgetType> widgetTypesToPush =
widgetTypeService.findWidgetTypesByTenantIdAndBundleAlias(widgetsBundleById.getTenantId(), widgetsBundleById.getAlias()); widgetTypeService.findWidgetTypesByTenantIdAndBundleAlias(widgetsBundleById.getTenantId(), widgetsBundleById.getAlias());
for (WidgetType widgetType : widgetTypesToPush) { for (WidgetType widgetType : widgetTypesToPush) {
saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.WIDGET_TYPE, EdgeEventActionType.ADDED, widgetType.getId(), null); futures.add(saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.WIDGET_TYPE, EdgeEventActionType.ADDED, widgetType.getId(), null));
} }
} }
} }
return Futures.immediateFuture(null); return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService);
} }
@Override @Override
@ -344,46 +372,35 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService {
Futures.addCallback(entityViewService.findEntityViewsByTenantIdAndEntityIdAsync(tenantId, entityId), new FutureCallback<>() { Futures.addCallback(entityViewService.findEntityViewsByTenantIdAndEntityIdAsync(tenantId, entityId), new FutureCallback<>() {
@Override @Override
public void onSuccess(@Nullable List<EntityView> entityViews) { public void onSuccess(@Nullable List<EntityView> entityViews) {
try { if (entityViews == null || entityViews.isEmpty()) {
if (entityViews != null && !entityViews.isEmpty()) { futureToSet.set(null);
List<ListenableFuture<Boolean>> futures = new ArrayList<>(); return;
for (EntityView entityView : entityViews) { }
ListenableFuture<Boolean> future = relationService.checkRelation(tenantId, edge.getId(), entityView.getId(), List<ListenableFuture<Void>> futures = new ArrayList<>();
EntityRelation.CONTAINS_TYPE, RelationTypeGroup.EDGE); for (EntityView entityView : entityViews) {
futures.add(future); ListenableFuture<Boolean> future = relationService.checkRelation(tenantId, edge.getId(), entityView.getId(),
Futures.addCallback(future, new FutureCallback<>() { EntityRelation.CONTAINS_TYPE, RelationTypeGroup.EDGE);
@Override futures.add(Futures.transformAsync(future, result -> {
public void onSuccess(@Nullable Boolean result) { if (Boolean.TRUE.equals(result)) {
if (Boolean.TRUE.equals(result)) { return saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.ENTITY_VIEW,
saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.ENTITY_VIEW, EdgeEventActionType.ADDED, entityView.getId(), null);
EdgeEventActionType.ADDED, entityView.getId(), null); } else {
} return Futures.immediateFuture(null);
}
@Override
public void onFailure(Throwable t) {
// Do nothing - error handles in allAsList
}
}, dbCallbackExecutorService);
} }
Futures.addCallback(Futures.allAsList(futures), new FutureCallback<>() { }, dbCallbackExecutorService));
@Override }
public void onSuccess(@Nullable List<Boolean> result) { Futures.addCallback(Futures.allAsList(futures), new FutureCallback<>() {
futureToSet.set(null); @Override
} public void onSuccess(@Nullable List<Void> result) {
@Override
public void onFailure(Throwable t) {
log.error("Exception during loading relation [{}] to edge on sync!", t, t);
futureToSet.setException(t);
}
}, dbCallbackExecutorService);
} else {
futureToSet.set(null); futureToSet.set(null);
} }
} catch (Exception e) {
log.error("Exception during loading relation(s) to edge on sync!", e); @Override
futureToSet.setException(e); public void onFailure(Throwable t) {
} log.error("Exception during loading relation to edge on sync!", t);
futureToSet.setException(t);
}
}, dbCallbackExecutorService);
} }
@Override @Override
@ -395,7 +412,7 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService {
return futureToSet; return futureToSet;
} }
private void saveEdgeEvent(TenantId tenantId, private ListenableFuture<Void> saveEdgeEvent(TenantId tenantId,
EdgeId edgeId, EdgeId edgeId,
EdgeEventType type, EdgeEventType type,
EdgeEventActionType action, EdgeEventActionType action,
@ -404,10 +421,12 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService {
log.trace("Pushing edge event to edge queue. tenantId [{}], edgeId [{}], type [{}], action[{}], entityId [{}], body [{}]", log.trace("Pushing edge event to edge queue. tenantId [{}], edgeId [{}], type [{}], action[{}], entityId [{}], body [{}]",
tenantId, edgeId, type, action, entityId, body); tenantId, edgeId, type, action, entityId, body);
EdgeEvent edgeEvent = EdgeEventUtils.constructEdgeEvent(tenantId, edgeId, type, action, entityId, body); EdgeEvent edgeEvent = EdgeUtils.constructEdgeEvent(tenantId, edgeId, type, action, entityId, body);
edgeEventService.save(edgeEvent); return Futures.transform(edgeEventService.saveAsync(edgeEvent), unused -> {
tbClusterService.onEdgeEventUpdate(tenantId, edgeId); tbClusterService.onEdgeEventUpdate(tenantId, edgeId);
return null;
}, dbCallbackExecutorService);
} }
} }

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

@ -283,6 +283,11 @@ sql:
batch_max_delay: "${SQL_EVENTS_BATCH_MAX_DELAY_MS:100}" batch_max_delay: "${SQL_EVENTS_BATCH_MAX_DELAY_MS:100}"
stats_print_interval_ms: "${SQL_EVENTS_BATCH_STATS_PRINT_MS:10000}" stats_print_interval_ms: "${SQL_EVENTS_BATCH_STATS_PRINT_MS:10000}"
batch_threads: "${SQL_EVENTS_BATCH_THREADS:3}" # batch thread count have to be a prime number like 3 or 5 to gain perfect hash distribution batch_threads: "${SQL_EVENTS_BATCH_THREADS:3}" # batch thread count have to be a prime number like 3 or 5 to gain perfect hash distribution
edge_events:
batch_size: "${SQL_EDGE_EVENTS_BATCH_SIZE:1000}"
batch_max_delay: "${SQL_EDGE_EVENTS_BATCH_MAX_DELAY_MS:100}"
stats_print_interval_ms: "${SQL_EDGE_EVENTS_BATCH_STATS_PRINT_MS:10000}"
batch_threads: "${SQL_EDGE_EVENTS_BATCH_THREADS:3}" # batch thread count have to be a prime number like 3 or 5 to gain perfect hash distribution
# Specify whether to sort entities before batch update. Should be enabled for cluster mode to avoid deadlocks # Specify whether to sort entities before batch update. Should be enabled for cluster mode to avoid deadlocks
batch_sort: "${SQL_BATCH_SORT:false}" batch_sort: "${SQL_BATCH_SORT:false}"
# Specify whether to remove null characters from strValue of attributes and timeseries before insert # Specify whether to remove null characters from strValue of attributes and timeseries before insert

10
application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java

@ -382,18 +382,23 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
} }
} }
protected DeviceProfile createDeviceProfile(String name) {
return createDeviceProfile(name, null);
}
protected DeviceProfile createDeviceProfile(String name, DeviceProfileTransportConfiguration deviceProfileTransportConfiguration) { protected DeviceProfile createDeviceProfile(String name, DeviceProfileTransportConfiguration deviceProfileTransportConfiguration) {
DeviceProfile deviceProfile = new DeviceProfile(); DeviceProfile deviceProfile = new DeviceProfile();
deviceProfile.setName(name); deviceProfile.setName(name);
deviceProfile.setType(DeviceProfileType.DEFAULT); deviceProfile.setType(DeviceProfileType.DEFAULT);
deviceProfile.setTransportType(DeviceTransportType.DEFAULT);
deviceProfile.setDescription(name + " Test"); deviceProfile.setDescription(name + " Test");
DeviceProfileData deviceProfileData = new DeviceProfileData(); DeviceProfileData deviceProfileData = new DeviceProfileData();
DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration(); DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration();
deviceProfileData.setConfiguration(configuration); deviceProfileData.setConfiguration(configuration);
if (deviceProfileTransportConfiguration != null) { if (deviceProfileTransportConfiguration != null) {
deviceProfile.setTransportType(deviceProfileTransportConfiguration.getType());
deviceProfileData.setTransportConfiguration(deviceProfileTransportConfiguration); deviceProfileData.setTransportConfiguration(deviceProfileTransportConfiguration);
} else { } else {
deviceProfile.setTransportType(DeviceTransportType.DEFAULT);
deviceProfileData.setTransportConfiguration(new DefaultDeviceProfileTransportConfiguration()); deviceProfileData.setTransportConfiguration(new DefaultDeviceProfileTransportConfiguration());
} }
deviceProfile.setProfileData(deviceProfileData); deviceProfile.setProfileData(deviceProfileData);
@ -402,10 +407,11 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
return deviceProfile; return deviceProfile;
} }
protected MqttDeviceProfileTransportConfiguration createMqttDeviceProfileTransportConfiguration(TransportPayloadTypeConfiguration transportPayloadTypeConfiguration) { protected MqttDeviceProfileTransportConfiguration createMqttDeviceProfileTransportConfiguration(TransportPayloadTypeConfiguration transportPayloadTypeConfiguration, boolean sendAckOnValidationException) {
MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = new MqttDeviceProfileTransportConfiguration(); MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = new MqttDeviceProfileTransportConfiguration();
mqttDeviceProfileTransportConfiguration.setDeviceTelemetryTopic(MqttTopics.DEVICE_TELEMETRY_TOPIC); mqttDeviceProfileTransportConfiguration.setDeviceTelemetryTopic(MqttTopics.DEVICE_TELEMETRY_TOPIC);
mqttDeviceProfileTransportConfiguration.setDeviceTelemetryTopic(MqttTopics.DEVICE_ATTRIBUTES_TOPIC); mqttDeviceProfileTransportConfiguration.setDeviceTelemetryTopic(MqttTopics.DEVICE_ATTRIBUTES_TOPIC);
mqttDeviceProfileTransportConfiguration.setSendAckOnValidationException(sendAckOnValidationException);
mqttDeviceProfileTransportConfiguration.setTransportPayloadTypeConfiguration(transportPayloadTypeConfiguration); mqttDeviceProfileTransportConfiguration.setTransportPayloadTypeConfiguration(transportPayloadTypeConfiguration);
return mqttDeviceProfileTransportConfiguration; return mqttDeviceProfileTransportConfiguration;
} }

34
application/src/test/java/org/thingsboard/server/controller/BaseAssetControllerTest.java

@ -24,6 +24,7 @@ import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.asset.Asset;
@ -182,6 +183,39 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
.andExpect(status().isNotFound()); .andExpect(status().isNotFound());
} }
@Test
public void testDeleteAssetAssignedToEntityView() throws Exception {
Asset asset1 = new Asset();
asset1.setName("My asset 1");
asset1.setType("default");
Asset savedAsset1 = doPost("/api/asset", asset1, Asset.class);
Asset asset2 = new Asset();
asset2.setName("My asset 2");
asset2.setType("default");
Asset savedAsset2 = doPost("/api/asset", asset2, Asset.class);
EntityView view = new EntityView();
view.setEntityId(savedAsset1.getId());
view.setTenantId(savedTenant.getId());
view.setName("My entity view");
view.setType("default");
EntityView savedView = doPost("/api/entityView", view, EntityView.class);
doDelete("/api/asset/" + savedAsset1.getId().getId().toString())
.andExpect(status().isBadRequest());
savedView.setEntityId(savedAsset2.getId());
doPost("/api/entityView", savedView, EntityView.class);
doDelete("/api/asset/" + savedAsset1.getId().getId().toString())
.andExpect(status().isOk());
doGet("/api/asset/" + savedAsset1.getId().getId().toString())
.andExpect(status().isNotFound());
}
@Test @Test
public void testSaveAssetWithEmptyType() throws Exception { public void testSaveAssetWithEmptyType() throws Exception {
Asset asset = new Asset(); Asset asset = new Asset();

57
application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java

@ -37,6 +37,7 @@ import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.JsonTransportPayloadConfiguration;
import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration;
import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration;
@ -93,7 +94,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController
@Test @Test
public void testSaveDeviceProfile() throws Exception { public void testSaveDeviceProfile() throws Exception {
DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile");
DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class);
Assert.assertNotNull(savedDeviceProfile); Assert.assertNotNull(savedDeviceProfile);
Assert.assertNotNull(savedDeviceProfile.getId()); Assert.assertNotNull(savedDeviceProfile.getId());
@ -112,13 +113,13 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController
@Test @Test
public void saveDeviceProfileWithViolationOfValidation() throws Exception { public void saveDeviceProfileWithViolationOfValidation() throws Exception {
doPost("/api/deviceProfile", this.createDeviceProfile(RandomStringUtils.randomAlphabetic(300), null)) doPost("/api/deviceProfile", this.createDeviceProfile(RandomStringUtils.randomAlphabetic(300)))
.andExpect(statusReason(containsString("length of name must be equal or less than 255"))); .andExpect(statusReason(containsString("length of name must be equal or less than 255")));
} }
@Test @Test
public void testFindDeviceProfileById() throws Exception { public void testFindDeviceProfileById() throws Exception {
DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile");
DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class);
DeviceProfile foundDeviceProfile = doGet("/api/deviceProfile/"+savedDeviceProfile.getId().getId().toString(), DeviceProfile.class); DeviceProfile foundDeviceProfile = doGet("/api/deviceProfile/"+savedDeviceProfile.getId().getId().toString(), DeviceProfile.class);
Assert.assertNotNull(foundDeviceProfile); Assert.assertNotNull(foundDeviceProfile);
@ -127,7 +128,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController
@Test @Test
public void testFindDeviceProfileInfoById() throws Exception { public void testFindDeviceProfileInfoById() throws Exception {
DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile");
DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class);
DeviceProfileInfo foundDeviceProfileInfo = doGet("/api/deviceProfileInfo/"+savedDeviceProfile.getId().getId().toString(), DeviceProfileInfo.class); DeviceProfileInfo foundDeviceProfileInfo = doGet("/api/deviceProfileInfo/"+savedDeviceProfile.getId().getId().toString(), DeviceProfileInfo.class);
Assert.assertNotNull(foundDeviceProfileInfo); Assert.assertNotNull(foundDeviceProfileInfo);
@ -149,7 +150,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController
@Test @Test
public void testSetDefaultDeviceProfile() throws Exception { public void testSetDefaultDeviceProfile() throws Exception {
DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile 1", null); DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile 1");
DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class);
DeviceProfile defaultDeviceProfile = doPost("/api/deviceProfile/"+savedDeviceProfile.getId().getId().toString()+"/default", DeviceProfile.class); DeviceProfile defaultDeviceProfile = doPost("/api/deviceProfile/"+savedDeviceProfile.getId().getId().toString()+"/default", DeviceProfile.class);
Assert.assertNotNull(defaultDeviceProfile); Assert.assertNotNull(defaultDeviceProfile);
@ -169,19 +170,19 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController
@Test @Test
public void testSaveDeviceProfileWithSameName() throws Exception { public void testSaveDeviceProfileWithSameName() throws Exception {
DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile");
doPost("/api/deviceProfile", deviceProfile).andExpect(status().isOk()); doPost("/api/deviceProfile", deviceProfile).andExpect(status().isOk());
DeviceProfile deviceProfile2 = this.createDeviceProfile("Device Profile", null); DeviceProfile deviceProfile2 = this.createDeviceProfile("Device Profile");
doPost("/api/deviceProfile", deviceProfile2).andExpect(status().isBadRequest()) doPost("/api/deviceProfile", deviceProfile2).andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Device profile with such name already exists"))); .andExpect(statusReason(containsString("Device profile with such name already exists")));
} }
@Test @Test
public void testSaveDeviceProfileWithSameProvisionDeviceKey() throws Exception { public void testSaveDeviceProfileWithSameProvisionDeviceKey() throws Exception {
DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile");
deviceProfile.setProvisionDeviceKey("testProvisionDeviceKey"); deviceProfile.setProvisionDeviceKey("testProvisionDeviceKey");
doPost("/api/deviceProfile", deviceProfile).andExpect(status().isOk()); doPost("/api/deviceProfile", deviceProfile).andExpect(status().isOk());
DeviceProfile deviceProfile2 = this.createDeviceProfile("Device Profile 2", null); DeviceProfile deviceProfile2 = this.createDeviceProfile("Device Profile 2");
deviceProfile2.setProvisionDeviceKey("testProvisionDeviceKey"); deviceProfile2.setProvisionDeviceKey("testProvisionDeviceKey");
doPost("/api/deviceProfile", deviceProfile2).andExpect(status().isBadRequest()) doPost("/api/deviceProfile", deviceProfile2).andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Device profile with such provision device key already exists"))); .andExpect(statusReason(containsString("Device profile with such provision device key already exists")));
@ -190,7 +191,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController
@Ignore @Ignore
@Test @Test
public void testChangeDeviceProfileTypeWithExistingDevices() throws Exception { public void testChangeDeviceProfileTypeWithExistingDevices() throws Exception {
DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile");
DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class);
Device device = new Device(); Device device = new Device();
device.setName("Test device"); device.setName("Test device");
@ -205,7 +206,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController
@Test @Test
public void testChangeDeviceProfileTransportTypeWithExistingDevices() throws Exception { public void testChangeDeviceProfileTransportTypeWithExistingDevices() throws Exception {
DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile");
DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class);
Device device = new Device(); Device device = new Device();
device.setName("Test device"); device.setName("Test device");
@ -219,7 +220,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController
@Test @Test
public void testDeleteDeviceProfileWithExistingDevice() throws Exception { public void testDeleteDeviceProfileWithExistingDevice() throws Exception {
DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile");
DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class);
Device device = new Device(); Device device = new Device();
@ -236,7 +237,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController
@Test @Test
public void testDeleteDeviceProfile() throws Exception { public void testDeleteDeviceProfile() throws Exception {
DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile");
DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class);
doDelete("/api/deviceProfile/" + savedDeviceProfile.getId().getId().toString()) doDelete("/api/deviceProfile/" + savedDeviceProfile.getId().getId().toString())
@ -257,7 +258,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController
deviceProfiles.addAll(pageData.getData()); deviceProfiles.addAll(pageData.getData());
for (int i=0;i<28;i++) { for (int i=0;i<28;i++) {
DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"+i, null); DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"+i);
deviceProfiles.add(doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class)); deviceProfiles.add(doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class));
} }
@ -302,7 +303,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController
deviceProfiles.addAll(deviceProfilePageData.getData()); deviceProfiles.addAll(deviceProfilePageData.getData());
for (int i=0;i<28;i++) { for (int i=0;i<28;i++) {
DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"+i, null); DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"+i);
deviceProfiles.add(doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class)); deviceProfiles.add(doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class));
} }
@ -835,19 +836,35 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController
"}", "[Transport Configuration] invalid rpc request proto schema provided! Failed to get field descriptor for field: params!"); "}", "[Transport Configuration] invalid rpc request proto schema provided! Failed to get field descriptor for field: params!");
} }
@Test
public void testSaveDeviceProfileWithSendAckOnValidationException() throws Exception {
JsonTransportPayloadConfiguration jsonTransportPayloadConfiguration = new JsonTransportPayloadConfiguration();
MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = this.createMqttDeviceProfileTransportConfiguration(jsonTransportPayloadConfiguration, true);
DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", mqttDeviceProfileTransportConfiguration);
DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class);
Assert.assertNotNull(savedDeviceProfile);
Assert.assertEquals(savedDeviceProfile.getTransportType(), DeviceTransportType.MQTT);
Assert.assertTrue(savedDeviceProfile.getProfileData().getTransportConfiguration() instanceof MqttDeviceProfileTransportConfiguration);
MqttDeviceProfileTransportConfiguration transportConfiguration = (MqttDeviceProfileTransportConfiguration) savedDeviceProfile.getProfileData().getTransportConfiguration();
Assert.assertTrue(transportConfiguration.isSendAckOnValidationException());
DeviceProfile foundDeviceProfile = doGet("/api/deviceProfile/"+ savedDeviceProfile.getId().getId().toString(), DeviceProfile.class);
Assert.assertEquals(savedDeviceProfile, foundDeviceProfile);
}
private DeviceProfile testSaveDeviceProfileWithProtoPayloadType(String schema) throws Exception { private DeviceProfile testSaveDeviceProfileWithProtoPayloadType(String schema) throws Exception {
ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = this.createProtoTransportPayloadConfiguration(schema, schema, null, null); ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = this.createProtoTransportPayloadConfiguration(schema, schema, null, null);
MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = this.createMqttDeviceProfileTransportConfiguration(protoTransportPayloadConfiguration); MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = this.createMqttDeviceProfileTransportConfiguration(protoTransportPayloadConfiguration, false);
DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", mqttDeviceProfileTransportConfiguration); DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", mqttDeviceProfileTransportConfiguration);
DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class);
DeviceProfile foundDeviceProfile = doGet("/api/deviceProfile/"+savedDeviceProfile.getId().getId().toString(), DeviceProfile.class); Assert.assertNotNull(savedDeviceProfile);
Assert.assertEquals(savedDeviceProfile.getName(), foundDeviceProfile.getName()); DeviceProfile foundDeviceProfile = doGet("/api/deviceProfile/"+ savedDeviceProfile.getId().getId().toString(), DeviceProfile.class);
Assert.assertEquals(savedDeviceProfile, foundDeviceProfile);
return savedDeviceProfile; return savedDeviceProfile;
} }
private void testSaveDeviceProfileWithInvalidProtoSchema(String schema, String errorMsg) throws Exception { private void testSaveDeviceProfileWithInvalidProtoSchema(String schema, String errorMsg) throws Exception {
ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = this.createProtoTransportPayloadConfiguration(schema, schema, null, null); ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = this.createProtoTransportPayloadConfiguration(schema, schema, null, null);
MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = this.createMqttDeviceProfileTransportConfiguration(protoTransportPayloadConfiguration); MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = this.createMqttDeviceProfileTransportConfiguration(protoTransportPayloadConfiguration, false);
DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", mqttDeviceProfileTransportConfiguration); DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", mqttDeviceProfileTransportConfiguration);
doPost("/api/deviceProfile", deviceProfile).andExpect(status().isBadRequest()) doPost("/api/deviceProfile", deviceProfile).andExpect(status().isBadRequest())
.andExpect(statusReason(containsString(errorMsg))); .andExpect(statusReason(containsString(errorMsg)));
@ -855,7 +872,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController
private void testSaveDeviceProfileWithInvalidRpcRequestProtoSchema(String schema, String errorMsg) throws Exception { private void testSaveDeviceProfileWithInvalidRpcRequestProtoSchema(String schema, String errorMsg) throws Exception {
ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = this.createProtoTransportPayloadConfiguration(schema, schema, schema, null); ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = this.createProtoTransportPayloadConfiguration(schema, schema, schema, null);
MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = this.createMqttDeviceProfileTransportConfiguration(protoTransportPayloadConfiguration); MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = this.createMqttDeviceProfileTransportConfiguration(protoTransportPayloadConfiguration, false);
DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", mqttDeviceProfileTransportConfiguration); DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", mqttDeviceProfileTransportConfiguration);
doPost("/api/deviceProfile", deviceProfile).andExpect(status().isBadRequest()) doPost("/api/deviceProfile", deviceProfile).andExpect(status().isBadRequest())
.andExpect(statusReason(containsString(errorMsg))); .andExpect(statusReason(containsString(errorMsg)));

17
application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java

@ -124,7 +124,8 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
@Test @Test
public void testSaveEntityView() throws Exception { public void testSaveEntityView() throws Exception {
EntityView savedView = getNewSavedEntityView("Test entity view"); String name = "Test entity view";
EntityView savedView = getNewSavedEntityView(name);
Assert.assertNotNull(savedView); Assert.assertNotNull(savedView);
Assert.assertNotNull(savedView.getId()); Assert.assertNotNull(savedView.getId());
@ -132,14 +133,20 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
assertEquals(tenantId, savedView.getTenantId()); assertEquals(tenantId, savedView.getTenantId());
Assert.assertNotNull(savedView.getCustomerId()); Assert.assertNotNull(savedView.getCustomerId());
assertEquals(NULL_UUID, savedView.getCustomerId().getId()); assertEquals(NULL_UUID, savedView.getCustomerId().getId());
assertEquals(savedView.getName(), savedView.getName()); assertEquals(name, savedView.getName());
EntityView foundEntityView = doGet("/api/entityView/" + savedView.getId().getId().toString(), EntityView.class);
assertEquals(savedView, foundEntityView);
savedView.setName("New test entity view"); savedView.setName("New test entity view");
doPost("/api/entityView", savedView, EntityView.class); doPost("/api/entityView", savedView, EntityView.class);
EntityView foundEntityView = doGet("/api/entityView/" + savedView.getId().getId().toString(), EntityView.class); foundEntityView = doGet("/api/entityView/" + savedView.getId().getId().toString(), EntityView.class);
assertEquals(savedView, foundEntityView);
assertEquals(foundEntityView.getName(), savedView.getName()); doGet("/api/tenant/entityViews?entityViewName=" + name, EntityView.class, status().isNotFound());
assertEquals(foundEntityView.getKeys(), telemetry);
} }
@Test @Test

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

@ -79,7 +79,7 @@ public abstract class BaseOtaPackageControllerTest extends AbstractControllerTes
tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile");
DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class);
Assert.assertNotNull(savedDeviceProfile); Assert.assertNotNull(savedDeviceProfile);
deviceProfileId = savedDeviceProfile.getId(); deviceProfileId = savedDeviceProfile.getId();

20
application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java

@ -199,7 +199,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
private void installation() throws Exception { private void installation() throws Exception {
edge = doPost("/api/edge", constructEdge("Test Edge", "test"), Edge.class); edge = doPost("/api/edge", constructEdge("Test Edge", "test"), Edge.class);
DeviceProfile deviceProfile = this.createDeviceProfile(CUSTOM_DEVICE_PROFILE_NAME, null); DeviceProfile deviceProfile = this.createDeviceProfile(CUSTOM_DEVICE_PROFILE_NAME);
extendDeviceProfileData(deviceProfile); extendDeviceProfileData(deviceProfile);
doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class);
@ -976,7 +976,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
String timeseriesData = "{\"data\":{\"temperature\":25},\"ts\":" + System.currentTimeMillis() + "}"; String timeseriesData = "{\"data\":{\"temperature\":25},\"ts\":" + System.currentTimeMillis() + "}";
JsonNode timeseriesEntityData = mapper.readTree(timeseriesData); JsonNode timeseriesEntityData = mapper.readTree(timeseriesData);
EdgeEvent edgeEvent = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.TIMESERIES_UPDATED, device.getId().getId(), EdgeEventType.DEVICE, timeseriesEntityData); EdgeEvent edgeEvent = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.TIMESERIES_UPDATED, device.getId().getId(), EdgeEventType.DEVICE, timeseriesEntityData);
edgeEventService.save(edgeEvent); edgeEventService.saveAsync(edgeEvent).get();
clusterService.onEdgeEventUpdate(tenantId, edge.getId()); clusterService.onEdgeEventUpdate(tenantId, edge.getId());
Assert.assertTrue(edgeImitator.waitForMessages()); Assert.assertTrue(edgeImitator.waitForMessages());
@ -1007,12 +1007,12 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
testAttributesDeleteMsg(device); testAttributesDeleteMsg(device);
} }
private void testAttributesUpdatedMsg(Device device) throws JsonProcessingException, InterruptedException { private void testAttributesUpdatedMsg(Device device) throws Exception {
String attributesData = "{\"scope\":\"SERVER_SCOPE\",\"kv\":{\"key1\":\"value1\"}}"; String attributesData = "{\"scope\":\"SERVER_SCOPE\",\"kv\":{\"key1\":\"value1\"}}";
JsonNode attributesEntityData = mapper.readTree(attributesData); JsonNode attributesEntityData = mapper.readTree(attributesData);
EdgeEvent edgeEvent1 = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.ATTRIBUTES_UPDATED, device.getId().getId(), EdgeEventType.DEVICE, attributesEntityData); EdgeEvent edgeEvent1 = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.ATTRIBUTES_UPDATED, device.getId().getId(), EdgeEventType.DEVICE, attributesEntityData);
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
edgeEventService.save(edgeEvent1); edgeEventService.saveAsync(edgeEvent1).get();
clusterService.onEdgeEventUpdate(tenantId, edge.getId()); clusterService.onEdgeEventUpdate(tenantId, edge.getId());
Assert.assertTrue(edgeImitator.waitForMessages()); Assert.assertTrue(edgeImitator.waitForMessages());
@ -1032,12 +1032,12 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals("value1", keyValueProto.getStringV()); Assert.assertEquals("value1", keyValueProto.getStringV());
} }
private void testPostAttributesMsg(Device device) throws JsonProcessingException, InterruptedException { private void testPostAttributesMsg(Device device) throws Exception {
String postAttributesData = "{\"scope\":\"SERVER_SCOPE\",\"kv\":{\"key2\":\"value2\"}}"; String postAttributesData = "{\"scope\":\"SERVER_SCOPE\",\"kv\":{\"key2\":\"value2\"}}";
JsonNode postAttributesEntityData = mapper.readTree(postAttributesData); JsonNode postAttributesEntityData = mapper.readTree(postAttributesData);
EdgeEvent edgeEvent = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.POST_ATTRIBUTES, device.getId().getId(), EdgeEventType.DEVICE, postAttributesEntityData); EdgeEvent edgeEvent = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.POST_ATTRIBUTES, device.getId().getId(), EdgeEventType.DEVICE, postAttributesEntityData);
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
edgeEventService.save(edgeEvent); edgeEventService.saveAsync(edgeEvent).get();
clusterService.onEdgeEventUpdate(tenantId, edge.getId()); clusterService.onEdgeEventUpdate(tenantId, edge.getId());
Assert.assertTrue(edgeImitator.waitForMessages()); Assert.assertTrue(edgeImitator.waitForMessages());
@ -1057,12 +1057,12 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals("value2", keyValueProto.getStringV()); Assert.assertEquals("value2", keyValueProto.getStringV());
} }
private void testAttributesDeleteMsg(Device device) throws JsonProcessingException, InterruptedException { private void testAttributesDeleteMsg(Device device) throws Exception {
String deleteAttributesData = "{\"scope\":\"SERVER_SCOPE\",\"keys\":[\"key1\",\"key2\"]}"; String deleteAttributesData = "{\"scope\":\"SERVER_SCOPE\",\"keys\":[\"key1\",\"key2\"]}";
JsonNode deleteAttributesEntityData = mapper.readTree(deleteAttributesData); JsonNode deleteAttributesEntityData = mapper.readTree(deleteAttributesData);
EdgeEvent edgeEvent = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.ATTRIBUTES_DELETED, device.getId().getId(), EdgeEventType.DEVICE, deleteAttributesEntityData); EdgeEvent edgeEvent = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.ATTRIBUTES_DELETED, device.getId().getId(), EdgeEventType.DEVICE, deleteAttributesEntityData);
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
edgeEventService.save(edgeEvent); edgeEventService.saveAsync(edgeEvent).get();
clusterService.onEdgeEventUpdate(tenantId, edge.getId()); clusterService.onEdgeEventUpdate(tenantId, edge.getId());
Assert.assertTrue(edgeImitator.waitForMessages()); Assert.assertTrue(edgeImitator.waitForMessages());
@ -1097,7 +1097,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
EdgeEvent edgeEvent = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.RPC_CALL, device.getId().getId(), EdgeEventType.DEVICE, body); EdgeEvent edgeEvent = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.RPC_CALL, device.getId().getId(), EdgeEventType.DEVICE, body);
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
edgeEventService.save(edgeEvent); edgeEventService.saveAsync(edgeEvent).get();
clusterService.onEdgeEventUpdate(tenantId, edge.getId()); clusterService.onEdgeEventUpdate(tenantId, edge.getId());
Assert.assertTrue(edgeImitator.waitForMessages()); Assert.assertTrue(edgeImitator.waitForMessages());
@ -1122,7 +1122,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
JsonNode timeseriesEntityData = mapper.readTree(timeseriesData); JsonNode timeseriesEntityData = mapper.readTree(timeseriesData);
EdgeEvent edgeEvent = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.TIMESERIES_UPDATED, EdgeEvent edgeEvent = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.TIMESERIES_UPDATED,
device.getId().getId(), EdgeEventType.DEVICE, timeseriesEntityData); device.getId().getId(), EdgeEventType.DEVICE, timeseriesEntityData);
edgeEventService.save(edgeEvent); edgeEventService.saveAsync(edgeEvent).get();
clusterService.onEdgeEventUpdate(tenantId, edge.getId()); clusterService.onEdgeEventUpdate(tenantId, edge.getId());
} }

24
application/src/test/java/org/thingsboard/server/transport/mqtt/AbstractMqttIntegrationTest.java

@ -66,11 +66,19 @@ public abstract class AbstractMqttIntegrationTest extends AbstractTransportInteg
protected DeviceProfile deviceProfile; protected DeviceProfile deviceProfile;
protected void processBeforeTest(String deviceName, String gatewayName, TransportPayloadType payloadType, String telemetryTopic, String attributesTopic) throws Exception { protected void processBeforeTest(String deviceName, String gatewayName, TransportPayloadType payloadType, String telemetryTopic, String attributesTopic) throws Exception {
this.processBeforeTest(deviceName, gatewayName, payloadType, telemetryTopic, attributesTopic, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED, false, false); this.processBeforeTest(deviceName, gatewayName, payloadType, telemetryTopic, attributesTopic, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED, false, false, false);
} }
protected void processBeforeTest(String deviceName, String gatewayName, TransportPayloadType payloadType, String telemetryTopic, String attributesTopic, boolean enableCompatibilityWithJsonPayloadFormat, boolean useJsonPayloadFormatForDefaultDownlinkTopics) throws Exception { protected void processBeforeTest(String deviceName, String gatewayName, TransportPayloadType payloadType, String telemetryTopic, String attributesTopic, boolean enableCompatibilityWithJsonPayloadFormat, boolean useJsonPayloadFormatForDefaultDownlinkTopics) throws Exception {
this.processBeforeTest(deviceName, gatewayName, payloadType, telemetryTopic, attributesTopic, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED, enableCompatibilityWithJsonPayloadFormat, useJsonPayloadFormatForDefaultDownlinkTopics); this.processBeforeTest(deviceName, gatewayName, payloadType, telemetryTopic, attributesTopic, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED, enableCompatibilityWithJsonPayloadFormat, useJsonPayloadFormatForDefaultDownlinkTopics, false);
}
protected void processBeforeTest(String deviceName, String gatewayName, TransportPayloadType payloadType, String telemetryTopic, String attributesTopic, boolean enableCompatibilityWithJsonPayloadFormat, boolean useJsonPayloadFormatForDefaultDownlinkTopics, boolean sendAckOnValidationException) throws Exception {
this.processBeforeTest(deviceName, gatewayName, payloadType, telemetryTopic, attributesTopic, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED, enableCompatibilityWithJsonPayloadFormat, useJsonPayloadFormatForDefaultDownlinkTopics, sendAckOnValidationException);
}
protected void processBeforeTest(String deviceName, String gatewayName, TransportPayloadType payloadType, String telemetryTopic, String attributesTopic, boolean sendAckOnValidationException) throws Exception {
this.processBeforeTest(deviceName, gatewayName, payloadType, telemetryTopic, attributesTopic, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED, false, false, sendAckOnValidationException);
} }
protected void processBeforeTest(String deviceName, protected void processBeforeTest(String deviceName,
@ -86,7 +94,8 @@ public abstract class AbstractMqttIntegrationTest extends AbstractTransportInteg
String provisionSecret, String provisionSecret,
DeviceProfileProvisionType provisionType, DeviceProfileProvisionType provisionType,
boolean enableCompatibilityWithJsonPayloadFormat, boolean enableCompatibilityWithJsonPayloadFormat,
boolean useJsonPayloadFormatForDefaultDownlinkTopics) throws Exception { boolean useJsonPayloadFormatForDefaultDownlinkTopics,
boolean sendAckOnValidationException) throws Exception {
loginSysAdmin(); loginSysAdmin();
Tenant tenant = new Tenant(); Tenant tenant = new Tenant();
@ -115,7 +124,10 @@ public abstract class AbstractMqttIntegrationTest extends AbstractTransportInteg
gateway.setAdditionalInfo(additionalInfo); gateway.setAdditionalInfo(additionalInfo);
if (payloadType != null) { if (payloadType != null) {
DeviceProfile mqttDeviceProfile = createMqttDeviceProfile(payloadType, telemetryTopic, attributesTopic, telemetryProtoSchema, attributesProtoSchema, rpcResponseProtoSchema, rpcRequestProtoSchema, provisionKey, provisionSecret, provisionType, enableCompatibilityWithJsonPayloadFormat, useJsonPayloadFormatForDefaultDownlinkTopics); DeviceProfile mqttDeviceProfile = createMqttDeviceProfile(payloadType, telemetryTopic, attributesTopic,
telemetryProtoSchema, attributesProtoSchema, rpcResponseProtoSchema, rpcRequestProtoSchema,
provisionKey, provisionSecret, provisionType, enableCompatibilityWithJsonPayloadFormat,
useJsonPayloadFormatForDefaultDownlinkTopics, sendAckOnValidationException);
deviceProfile = doPost("/api/deviceProfile", mqttDeviceProfile, DeviceProfile.class); deviceProfile = doPost("/api/deviceProfile", mqttDeviceProfile, DeviceProfile.class);
device.setType(deviceProfile.getName()); device.setType(deviceProfile.getName());
device.setDeviceProfileId(deviceProfile.getId()); device.setDeviceProfileId(deviceProfile.getId());
@ -173,7 +185,8 @@ public abstract class AbstractMqttIntegrationTest extends AbstractTransportInteg
String provisionKey, String provisionSecret, String provisionKey, String provisionSecret,
DeviceProfileProvisionType provisionType, DeviceProfileProvisionType provisionType,
boolean enableCompatibilityWithJsonPayloadFormat, boolean enableCompatibilityWithJsonPayloadFormat,
boolean useJsonPayloadFormatForDefaultDownlinkTopics) { boolean useJsonPayloadFormatForDefaultDownlinkTopics,
boolean sendAckOnValidationException) {
DeviceProfile deviceProfile = new DeviceProfile(); DeviceProfile deviceProfile = new DeviceProfile();
deviceProfile.setName(transportPayloadType.name()); deviceProfile.setName(transportPayloadType.name());
deviceProfile.setType(DeviceProfileType.DEFAULT); deviceProfile.setType(DeviceProfileType.DEFAULT);
@ -190,6 +203,7 @@ public abstract class AbstractMqttIntegrationTest extends AbstractTransportInteg
if (!StringUtils.isEmpty(attributesTopic)) { if (!StringUtils.isEmpty(attributesTopic)) {
mqttDeviceProfileTransportConfiguration.setDeviceAttributesTopic(attributesTopic); mqttDeviceProfileTransportConfiguration.setDeviceAttributesTopic(attributesTopic);
} }
mqttDeviceProfileTransportConfiguration.setSendAckOnValidationException(sendAckOnValidationException);
TransportPayloadTypeConfiguration transportPayloadTypeConfiguration; TransportPayloadTypeConfiguration transportPayloadTypeConfiguration;
if (TransportPayloadType.JSON.equals(transportPayloadType)) { if (TransportPayloadType.JSON.equals(transportPayloadType)) {
transportPayloadTypeConfiguration = new JsonTransportPayloadConfiguration(); transportPayloadTypeConfiguration = new JsonTransportPayloadConfiguration();

8
application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/AbstractMqttAttributesRequestBackwardCompatibilityIntegrationTest.java

@ -38,28 +38,28 @@ public abstract class AbstractMqttAttributesRequestBackwardCompatibilityIntegrat
@Test @Test
public void testRequestAttributesValuesFromTheServerWithEnabledJsonCompatibility() throws Exception { public void testRequestAttributesValuesFromTheServerWithEnabledJsonCompatibility() throws Exception {
super.processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto", super.processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto",
TransportPayloadType.PROTOBUF, null, null, null, ATTRIBUTES_SCHEMA_STR, null, null, null, null, DeviceProfileProvisionType.DISABLED, true, false); TransportPayloadType.PROTOBUF, null, null, null, ATTRIBUTES_SCHEMA_STR, null, null, null, null, DeviceProfileProvisionType.DISABLED, true, false, false);
processProtoTestRequestAttributesValuesFromTheServer(MqttTopics.DEVICE_ATTRIBUTES_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX); processProtoTestRequestAttributesValuesFromTheServer(MqttTopics.DEVICE_ATTRIBUTES_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX);
} }
@Test @Test
public void testRequestAttributesValuesFromTheServerWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception { public void testRequestAttributesValuesFromTheServerWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception {
super.processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto", super.processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto",
TransportPayloadType.PROTOBUF, null, null, null, ATTRIBUTES_SCHEMA_STR, null, null, null, null, DeviceProfileProvisionType.DISABLED, true, true); TransportPayloadType.PROTOBUF, null, null, null, ATTRIBUTES_SCHEMA_STR, null, null, null, null, DeviceProfileProvisionType.DISABLED, true, true, false);
processJsonTestRequestAttributesValuesFromTheServer(MqttTopics.DEVICE_ATTRIBUTES_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX); processJsonTestRequestAttributesValuesFromTheServer(MqttTopics.DEVICE_ATTRIBUTES_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX);
} }
@Test @Test
public void testRequestAttributesValuesFromTheServerOnShortTopicWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception { public void testRequestAttributesValuesFromTheServerOnShortTopicWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception {
super.processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto", super.processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto",
TransportPayloadType.PROTOBUF, null, null, null, ATTRIBUTES_SCHEMA_STR, null, null, null, null, DeviceProfileProvisionType.DISABLED, true, true); TransportPayloadType.PROTOBUF, null, null, null, ATTRIBUTES_SCHEMA_STR, null, null, null, null, DeviceProfileProvisionType.DISABLED, true, true, false);
processProtoTestRequestAttributesValuesFromTheServer(MqttTopics.DEVICE_ATTRIBUTES_SHORT_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_SHORT_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_SHORT_TOPIC_PREFIX); processProtoTestRequestAttributesValuesFromTheServer(MqttTopics.DEVICE_ATTRIBUTES_SHORT_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_SHORT_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_SHORT_TOPIC_PREFIX);
} }
@Test @Test
public void testRequestAttributesValuesFromTheServerOnShortProtoTopicWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception { public void testRequestAttributesValuesFromTheServerOnShortProtoTopicWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception {
super.processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto", super.processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto",
TransportPayloadType.PROTOBUF, null, null, null, ATTRIBUTES_SCHEMA_STR, null, null, null, null, DeviceProfileProvisionType.DISABLED, true, true); TransportPayloadType.PROTOBUF, null, null, null, ATTRIBUTES_SCHEMA_STR, null, null, null, null, DeviceProfileProvisionType.DISABLED, true, true, false);
processProtoTestRequestAttributesValuesFromTheServer(MqttTopics.DEVICE_ATTRIBUTES_SHORT_PROTO_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_SHORT_PROTO_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_SHORT_PROTO_TOPIC_PREFIX); processProtoTestRequestAttributesValuesFromTheServer(MqttTopics.DEVICE_ATTRIBUTES_SHORT_PROTO_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_SHORT_PROTO_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_SHORT_PROTO_TOPIC_PREFIX);
} }

6
application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java

@ -38,21 +38,21 @@ public abstract class AbstractMqttAttributesRequestProtoIntegrationTest extends
@Test @Test
public void testRequestAttributesValuesFromTheServer() throws Exception { public void testRequestAttributesValuesFromTheServer() throws Exception {
super.processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto", super.processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto",
TransportPayloadType.PROTOBUF, null, null, null, ATTRIBUTES_SCHEMA_STR, null, null, null, null, DeviceProfileProvisionType.DISABLED, false, false); TransportPayloadType.PROTOBUF, null, null, null, ATTRIBUTES_SCHEMA_STR, null, null, null, null, DeviceProfileProvisionType.DISABLED, false, false, false);
processProtoTestRequestAttributesValuesFromTheServer(MqttTopics.DEVICE_ATTRIBUTES_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX); processProtoTestRequestAttributesValuesFromTheServer(MqttTopics.DEVICE_ATTRIBUTES_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX);
} }
@Test @Test
public void testRequestAttributesValuesFromTheServerOnShortTopic() throws Exception { public void testRequestAttributesValuesFromTheServerOnShortTopic() throws Exception {
super.processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto", super.processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto",
TransportPayloadType.PROTOBUF, null, null, null, ATTRIBUTES_SCHEMA_STR, null, null, null, null, DeviceProfileProvisionType.DISABLED, false, false); TransportPayloadType.PROTOBUF, null, null, null, ATTRIBUTES_SCHEMA_STR, null, null, null, null, DeviceProfileProvisionType.DISABLED, false, false, false);
processProtoTestRequestAttributesValuesFromTheServer(MqttTopics.DEVICE_ATTRIBUTES_SHORT_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_SHORT_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_SHORT_TOPIC_PREFIX); processProtoTestRequestAttributesValuesFromTheServer(MqttTopics.DEVICE_ATTRIBUTES_SHORT_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_SHORT_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_SHORT_TOPIC_PREFIX);
} }
@Test @Test
public void testRequestAttributesValuesFromTheServerOnShortProtoTopic() throws Exception { public void testRequestAttributesValuesFromTheServerOnShortProtoTopic() throws Exception {
super.processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto", super.processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto",
TransportPayloadType.PROTOBUF, null, null, null, ATTRIBUTES_SCHEMA_STR, null, null, null, null, DeviceProfileProvisionType.DISABLED, false, false); TransportPayloadType.PROTOBUF, null, null, null, ATTRIBUTES_SCHEMA_STR, null, null, null, null, DeviceProfileProvisionType.DISABLED, false, false, false);
processProtoTestRequestAttributesValuesFromTheServer(MqttTopics.DEVICE_ATTRIBUTES_SHORT_PROTO_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_SHORT_PROTO_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_SHORT_PROTO_TOPIC_PREFIX); processProtoTestRequestAttributesValuesFromTheServer(MqttTopics.DEVICE_ATTRIBUTES_SHORT_PROTO_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_SHORT_PROTO_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_SHORT_PROTO_TOPIC_PREFIX);
} }

14
application/src/test/java/org/thingsboard/server/transport/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java

@ -94,7 +94,7 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn
protected void processTestProvisioningDisabledDevice() throws Exception { protected void processTestProvisioningDisabledDevice() throws Exception {
super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED, false, false); super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED, false, false, false);
byte[] result = createMqttClientAndPublish().getPayloadBytes(); byte[] result = createMqttClientAndPublish().getPayloadBytes();
JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject();
Assert.assertEquals("Provision data was not found!", response.get("errorMsg").getAsString()); Assert.assertEquals("Provision data was not found!", response.get("errorMsg").getAsString());
@ -103,7 +103,7 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn
protected void processTestProvisioningCreateNewDeviceWithoutCredentials() throws Exception { protected void processTestProvisioningCreateNewDeviceWithoutCredentials() throws Exception {
super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, false, false); super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, false, false, false);
byte[] result = createMqttClientAndPublish().getPayloadBytes(); byte[] result = createMqttClientAndPublish().getPayloadBytes();
JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject();
@ -119,7 +119,7 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn
protected void processTestProvisioningCreateNewDeviceWithAccessToken() throws Exception { protected void processTestProvisioningCreateNewDeviceWithAccessToken() throws Exception {
super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, false, false); super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, false, false, false);
String requestCredentials = ",\"credentialsType\": \"ACCESS_TOKEN\",\"token\": \"test_token\""; String requestCredentials = ",\"credentialsType\": \"ACCESS_TOKEN\",\"token\": \"test_token\"";
byte[] result = createMqttClientAndPublish(requestCredentials).getPayloadBytes(); byte[] result = createMqttClientAndPublish(requestCredentials).getPayloadBytes();
JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject();
@ -138,7 +138,7 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn
protected void processTestProvisioningCreateNewDeviceWithCert() throws Exception { protected void processTestProvisioningCreateNewDeviceWithCert() throws Exception {
super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, false, false); super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, false, false, false);
String requestCredentials = ",\"credentialsType\": \"X509_CERTIFICATE\",\"hash\": \"testHash\""; String requestCredentials = ",\"credentialsType\": \"X509_CERTIFICATE\",\"hash\": \"testHash\"";
byte[] result = createMqttClientAndPublish(requestCredentials).getPayloadBytes(); byte[] result = createMqttClientAndPublish(requestCredentials).getPayloadBytes();
JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject();
@ -163,7 +163,7 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn
protected void processTestProvisioningCreateNewDeviceWithMqttBasic() throws Exception { protected void processTestProvisioningCreateNewDeviceWithMqttBasic() throws Exception {
super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, false, false); super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, false, false, false);
String requestCredentials = ",\"credentialsType\": \"MQTT_BASIC\",\"clientId\": \"test_clientId\",\"username\": \"test_username\",\"password\": \"test_password\""; String requestCredentials = ",\"credentialsType\": \"MQTT_BASIC\",\"clientId\": \"test_clientId\",\"username\": \"test_username\",\"password\": \"test_password\"";
byte[] result = createMqttClientAndPublish(requestCredentials).getPayloadBytes(); byte[] result = createMqttClientAndPublish(requestCredentials).getPayloadBytes();
JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject();
@ -188,7 +188,7 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn
} }
protected void processTestProvisioningCheckPreProvisionedDevice() throws Exception { protected void processTestProvisioningCheckPreProvisionedDevice() throws Exception {
super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, false, false); super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, false, false, false);
byte[] result = createMqttClientAndPublish().getPayloadBytes(); byte[] result = createMqttClientAndPublish().getPayloadBytes();
JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject();
@ -199,7 +199,7 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn
} }
protected void processTestProvisioningWithBadKeyDevice() throws Exception { protected void processTestProvisioningWithBadKeyDevice() throws Exception {
super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, null, null, "testProvisionKeyOrig", "testProvisionSecret", DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, false, false); super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, null, null, "testProvisionKeyOrig", "testProvisionSecret", DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, false, false, false);
byte[] result = createMqttClientAndPublish().getPayloadBytes(); byte[] result = createMqttClientAndPublish().getPayloadBytes();
JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject();
Assert.assertEquals("Provision data was not found!", response.get("errorMsg").getAsString()); Assert.assertEquals("Provision data was not found!", response.get("errorMsg").getAsString());

14
application/src/test/java/org/thingsboard/server/transport/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java

@ -101,14 +101,14 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI
protected void processTestProvisioningDisabledDevice() throws Exception { protected void processTestProvisioningDisabledDevice() throws Exception {
super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED, false, false); super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED, false, false, false);
ProvisionDeviceResponseMsg result = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes()); ProvisionDeviceResponseMsg result = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes());
Assert.assertNotNull(result); Assert.assertNotNull(result);
Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), result.getStatus().toString()); Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), result.getStatus().toString());
} }
protected void processTestProvisioningCreateNewDeviceWithoutCredentials() throws Exception { protected void processTestProvisioningCreateNewDeviceWithoutCredentials() throws Exception {
super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, false, false); super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, false, false, false);
ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes()); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes());
Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device");
@ -122,7 +122,7 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI
} }
protected void processTestProvisioningCreateNewDeviceWithAccessToken() throws Exception { protected void processTestProvisioningCreateNewDeviceWithAccessToken() throws Exception {
super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null,null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, false, false); super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null,null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, false, false, false);
CredentialsDataProto requestCredentials = CredentialsDataProto.newBuilder().setValidateDeviceTokenRequestMsg(ValidateDeviceTokenRequestMsg.newBuilder().setToken("test_token").build()).build(); CredentialsDataProto requestCredentials = CredentialsDataProto.newBuilder().setValidateDeviceTokenRequestMsg(ValidateDeviceTokenRequestMsg.newBuilder().setToken("test_token").build()).build();
ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish(createTestsProvisionMessage(CredentialsType.ACCESS_TOKEN, requestCredentials)).getPayloadBytes()); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish(createTestsProvisionMessage(CredentialsType.ACCESS_TOKEN, requestCredentials)).getPayloadBytes());
@ -140,7 +140,7 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI
} }
protected void processTestProvisioningCreateNewDeviceWithCert() throws Exception { protected void processTestProvisioningCreateNewDeviceWithCert() throws Exception {
super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, false, false); super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, false, false, false);
CredentialsDataProto requestCredentials = CredentialsDataProto.newBuilder().setValidateDeviceX509CertRequestMsg(ValidateDeviceX509CertRequestMsg.newBuilder().setHash("testHash").build()).build(); CredentialsDataProto requestCredentials = CredentialsDataProto.newBuilder().setValidateDeviceX509CertRequestMsg(ValidateDeviceX509CertRequestMsg.newBuilder().setHash("testHash").build()).build();
ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish(createTestsProvisionMessage(CredentialsType.X509_CERTIFICATE, requestCredentials)).getPayloadBytes()); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish(createTestsProvisionMessage(CredentialsType.X509_CERTIFICATE, requestCredentials)).getPayloadBytes());
@ -164,7 +164,7 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI
} }
protected void processTestProvisioningCreateNewDeviceWithMqttBasic() throws Exception { protected void processTestProvisioningCreateNewDeviceWithMqttBasic() throws Exception {
super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, false, false); super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, false, false, false);
CredentialsDataProto requestCredentials = CredentialsDataProto.newBuilder().setValidateBasicMqttCredRequestMsg( CredentialsDataProto requestCredentials = CredentialsDataProto.newBuilder().setValidateBasicMqttCredRequestMsg(
ValidateBasicMqttCredRequestMsg.newBuilder() ValidateBasicMqttCredRequestMsg.newBuilder()
.setClientId("test_clientId") .setClientId("test_clientId")
@ -195,7 +195,7 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI
} }
protected void processTestProvisioningCheckPreProvisionedDevice() throws Exception { protected void processTestProvisioningCheckPreProvisionedDevice() throws Exception {
super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, false, false); super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, false, false, false);
ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes()); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes());
DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), savedDevice.getId()); DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), savedDevice.getId());
@ -205,7 +205,7 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI
} }
protected void processTestProvisioningWithBadKeyDevice() throws Exception { protected void processTestProvisioningWithBadKeyDevice() throws Exception {
super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, null, "testProvisionKeyOrig", "testProvisionSecret", DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, false, false); super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, null, "testProvisionKeyOrig", "testProvisionSecret", DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, false, false, false);
ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes()); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes());
Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.getStatus().toString()); Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.getStatus().toString());
} }

20
application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcBackwardCompatibilityIntegrationTest.java

@ -33,61 +33,61 @@ public abstract class AbstractMqttServerSideRpcBackwardCompatibilityIntegrationT
@Test @Test
public void testServerMqttOneWayRpcWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception { public void testServerMqttOneWayRpcWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception {
super.processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, RPC_REQUEST_PROTO_SCHEMA, null, null, DeviceProfileProvisionType.DISABLED, true, true); super.processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, RPC_REQUEST_PROTO_SCHEMA, null, null, DeviceProfileProvisionType.DISABLED, true, true, false);
processOneWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_TOPIC); processOneWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_TOPIC);
} }
@Test @Test
public void testServerMqttOneWayRpcOnShortTopicWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception { public void testServerMqttOneWayRpcOnShortTopicWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception {
super.processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, RPC_REQUEST_PROTO_SCHEMA, null, null, DeviceProfileProvisionType.DISABLED, true, true); super.processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, RPC_REQUEST_PROTO_SCHEMA, null, null, DeviceProfileProvisionType.DISABLED, true, true, false);
processOneWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_TOPIC); processOneWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_TOPIC);
} }
@Test @Test
public void testServerMqttOneWayRpcOnShortProtoTopicWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception { public void testServerMqttOneWayRpcOnShortProtoTopicWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception {
super.processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, RPC_REQUEST_PROTO_SCHEMA, null, null, DeviceProfileProvisionType.DISABLED, true, true); super.processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, RPC_REQUEST_PROTO_SCHEMA, null, null, DeviceProfileProvisionType.DISABLED, true, true, false);
processOneWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_PROTO_TOPIC); processOneWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_PROTO_TOPIC);
} }
@Test @Test
public void testServerMqttTwoWayRpcWithEnabledJsonCompatibility() throws Exception { public void testServerMqttTwoWayRpcWithEnabledJsonCompatibility() throws Exception {
super.processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, RPC_REQUEST_PROTO_SCHEMA, null, null, DeviceProfileProvisionType.DISABLED, true, false); super.processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, RPC_REQUEST_PROTO_SCHEMA, null, null, DeviceProfileProvisionType.DISABLED, true, false, false);
processProtoTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_TOPIC); processProtoTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_TOPIC);
} }
@Test @Test
public void testServerMqttTwoWayRpcWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception { public void testServerMqttTwoWayRpcWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception {
super.processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, RPC_REQUEST_PROTO_SCHEMA, null, null, DeviceProfileProvisionType.DISABLED, true, true); super.processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, RPC_REQUEST_PROTO_SCHEMA, null, null, DeviceProfileProvisionType.DISABLED, true, true, false);
processJsonTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_TOPIC); processJsonTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_TOPIC);
} }
@Test @Test
public void testServerMqttTwoWayRpcOnShortTopic() throws Exception { public void testServerMqttTwoWayRpcOnShortTopic() throws Exception {
super.processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, RPC_REQUEST_PROTO_SCHEMA, null, null, DeviceProfileProvisionType.DISABLED, true, true); super.processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, RPC_REQUEST_PROTO_SCHEMA, null, null, DeviceProfileProvisionType.DISABLED, true, true, false);
processProtoTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_TOPIC); processProtoTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_TOPIC);
} }
@Test @Test
public void testServerMqttTwoWayRpcOnShortProtoTopicWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception { public void testServerMqttTwoWayRpcOnShortProtoTopicWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception {
super.processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, RPC_REQUEST_PROTO_SCHEMA, null, null, DeviceProfileProvisionType.DISABLED, true, true); super.processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, RPC_REQUEST_PROTO_SCHEMA, null, null, DeviceProfileProvisionType.DISABLED, true, true, false);
processProtoTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_PROTO_TOPIC); processProtoTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_PROTO_TOPIC);
} }
@Test @Test
public void testServerMqttTwoWayRpcOnShortJsonTopicWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception { public void testServerMqttTwoWayRpcOnShortJsonTopicWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception {
super.processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, RPC_REQUEST_PROTO_SCHEMA, null, null, DeviceProfileProvisionType.DISABLED, true, true); super.processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, RPC_REQUEST_PROTO_SCHEMA, null, null, DeviceProfileProvisionType.DISABLED, true, true, false);
processJsonTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_JSON_TOPIC); processJsonTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_JSON_TOPIC);
} }
@Test @Test
public void testGatewayServerMqttOneWayRpcWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception { public void testGatewayServerMqttOneWayRpcWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception {
super.processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, RPC_REQUEST_PROTO_SCHEMA, null, null, DeviceProfileProvisionType.DISABLED, true, true); super.processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, RPC_REQUEST_PROTO_SCHEMA, null, null, DeviceProfileProvisionType.DISABLED, true, true, false);
processProtoOneWayRpcTestGateway("Gateway Device OneWay RPC Proto"); processProtoOneWayRpcTestGateway("Gateway Device OneWay RPC Proto");
} }
@Test @Test
public void testGatewayServerMqttTwoWayRpcWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception { public void testGatewayServerMqttTwoWayRpcWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception {
super.processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, RPC_REQUEST_PROTO_SCHEMA, null, null, DeviceProfileProvisionType.DISABLED, true, true); super.processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, RPC_REQUEST_PROTO_SCHEMA, null, null, DeviceProfileProvisionType.DISABLED, true, true, false);
processProtoTwoWayRpcTestGateway("Gateway Device TwoWay RPC Proto"); processProtoTwoWayRpcTestGateway("Gateway Device TwoWay RPC Proto");
} }

2
application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcProtoIntegrationTest.java

@ -28,7 +28,7 @@ public abstract class AbstractMqttServerSideRpcProtoIntegrationTest extends Abst
@Before @Before
public void beforeTest() throws Exception { public void beforeTest() throws Exception {
processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, RPC_REQUEST_PROTO_SCHEMA, null, null, DeviceProfileProvisionType.DISABLED, false, false); processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, RPC_REQUEST_PROTO_SCHEMA, null, null, DeviceProfileProvisionType.DISABLED, false, false, false);
} }
@After @After

33
application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/AbstractMqttTimeseriesIntegrationTest.java

@ -23,6 +23,7 @@ import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.eclipse.paho.client.mqttv3.MqttCallback; import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions; import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttMessage; import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.internal.wire.MqttWireMessage;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
@ -50,6 +51,9 @@ public abstract class AbstractMqttTimeseriesIntegrationTest extends AbstractMqtt
protected static final String PAYLOAD_VALUES_STR = "{\"key1\":\"value1\", \"key2\":true, \"key3\": 3.0, \"key4\": 4," + protected static final String PAYLOAD_VALUES_STR = "{\"key1\":\"value1\", \"key2\":true, \"key3\": 3.0, \"key4\": 4," +
" \"key5\": {\"someNumber\": 42, \"someArray\": [1,2,3], \"someNestedObject\": {\"key\": \"value\"}}}"; " \"key5\": {\"someNumber\": 42, \"someArray\": [1,2,3], \"someNestedObject\": {\"key\": \"value\"}}}";
protected static final String MALFORMED_JSON_PAYLOAD = "{\"key1\":, \"key2\":true, \"key3\": 3.0, \"key4\": 4," +
" \"key5\": {\"someNumber\": 42, \"someArray\": [1,2,3], \"someNestedObject\": {\"key\": \"value\"}}}";
@Before @Before
public void beforeTest() throws Exception { public void beforeTest() throws Exception {
processBeforeTest("Test Post Telemetry device", "Test Post Telemetry gateway", null, null, null); processBeforeTest("Test Post Telemetry device", "Test Post Telemetry gateway", null, null, null);
@ -368,5 +372,34 @@ public abstract class AbstractMqttTimeseriesIntegrationTest extends AbstractMqtt
} }
} }
public static class TestMqttPublishCallback implements MqttCallback {
private final CountDownLatch latch;
private boolean pubAckReceived;
public boolean isPubAckReceived() {
return pubAckReceived;
}
public TestMqttPublishCallback(CountDownLatch latch) {
this.latch = latch;
}
@Override
public void connectionLost(Throwable throwable) {
latch.countDown();
}
@Override
public void messageArrived(String s, MqttMessage mqttMessage) throws Exception {
}
@Override
public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
pubAckReceived = iMqttDeliveryToken.getResponse().getType() == MqttWireMessage.MESSAGE_TYPE_PUBACK;
latch.countDown();
}
}
} }

66
application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/AbstractMqttTimeseriesJsonIntegrationTest.java

@ -20,14 +20,15 @@ import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.TransportPayloadType;
import org.thingsboard.server.common.data.device.profile.MqttTopics;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@Slf4j @Slf4j
public abstract class AbstractMqttTimeseriesJsonIntegrationTest extends AbstractMqttTimeseriesIntegrationTest { public abstract class AbstractMqttTimeseriesJsonIntegrationTest extends AbstractMqttTimeseriesIntegrationTest {
@ -36,7 +37,7 @@ public abstract class AbstractMqttTimeseriesJsonIntegrationTest extends Abstract
@Before @Before
public void beforeTest() throws Exception { public void beforeTest() throws Exception {
processBeforeTest("Test Post Telemetry device json payload", "Test Post Telemetry gateway json payload", TransportPayloadType.JSON, POST_DATA_TELEMETRY_TOPIC, null); //do nothing, processBeforeTest will be invoked in particular test methods with different parameters
} }
@After @After
@ -46,12 +47,14 @@ public abstract class AbstractMqttTimeseriesJsonIntegrationTest extends Abstract
@Test @Test
public void testPushTelemetry() throws Exception { public void testPushTelemetry() throws Exception {
processBeforeTest("Test Post Telemetry device json payload", "Test Post Telemetry gateway json payload", TransportPayloadType.JSON, POST_DATA_TELEMETRY_TOPIC, null);
List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5"); List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5");
processJsonPayloadTelemetryTest(POST_DATA_TELEMETRY_TOPIC, expectedKeys, PAYLOAD_VALUES_STR.getBytes(), false); processJsonPayloadTelemetryTest(POST_DATA_TELEMETRY_TOPIC, expectedKeys, PAYLOAD_VALUES_STR.getBytes(), false);
} }
@Test @Test
public void testPushTelemetryWithTs() throws Exception { public void testPushTelemetryWithTs() throws Exception {
processBeforeTest("Test Post Telemetry device json payload", "Test Post Telemetry gateway json payload", TransportPayloadType.JSON, POST_DATA_TELEMETRY_TOPIC, null);
String payloadStr = "{\"ts\": 10000, \"values\": " + PAYLOAD_VALUES_STR + "}"; String payloadStr = "{\"ts\": 10000, \"values\": " + PAYLOAD_VALUES_STR + "}";
List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5"); List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5");
processJsonPayloadTelemetryTest(POST_DATA_TELEMETRY_TOPIC, expectedKeys, payloadStr.getBytes(), true); processJsonPayloadTelemetryTest(POST_DATA_TELEMETRY_TOPIC, expectedKeys, payloadStr.getBytes(), true);
@ -59,21 +62,74 @@ public abstract class AbstractMqttTimeseriesJsonIntegrationTest extends Abstract
@Test @Test
public void testPushTelemetryOnShortTopic() throws Exception { public void testPushTelemetryOnShortTopic() throws Exception {
processBeforeTest("Test Post Telemetry device json payload", "Test Post Telemetry gateway json payload", TransportPayloadType.JSON, POST_DATA_TELEMETRY_TOPIC, null);
super.testPushTelemetryOnShortTopic(); super.testPushTelemetryOnShortTopic();
} }
@Test @Test
public void testPushTelemetryWithTsOnShortJsonTopic() throws Exception { public void testPushTelemetryOnShortJsonTopic() throws Exception {
processBeforeTest("Test Post Telemetry device json payload", "Test Post Telemetry gateway json payload", TransportPayloadType.JSON, POST_DATA_TELEMETRY_TOPIC, null);
super.testPushTelemetryOnShortJsonTopic(); super.testPushTelemetryOnShortJsonTopic();
} }
@Test @Test
public void testPushTelemetryGateway() throws Exception { public void testPushTelemetryGateway() throws Exception {
processBeforeTest("Test Post Telemetry device json payload", "Test Post Telemetry gateway json payload", TransportPayloadType.JSON, POST_DATA_TELEMETRY_TOPIC, null);
super.testPushTelemetryGateway(); super.testPushTelemetryGateway();
} }
@Test @Test
public void testGatewayConnect() throws Exception { public void testGatewayConnect() throws Exception {
processBeforeTest("Test Post Telemetry device json payload", "Test Post Telemetry gateway json payload", TransportPayloadType.JSON, POST_DATA_TELEMETRY_TOPIC, null);
super.testGatewayConnect(); super.testGatewayConnect();
} }
@Test
public void testPushTelemetryWithMalformedPayloadAndSendAckOnErrorEnabled() throws Exception {
processBeforeTest("Test Post Telemetry device json payload", "Test Post Telemetry gateway json payload", TransportPayloadType.JSON, POST_DATA_TELEMETRY_TOPIC, null, true);
CountDownLatch latch = new CountDownLatch(1);
MqttAsyncClient client = getMqttAsyncClient(accessToken);
TestMqttPublishCallback callback = new TestMqttPublishCallback(latch);
client.setCallback(callback);
publishMqttMsg(client, MALFORMED_JSON_PAYLOAD.getBytes(), POST_DATA_TELEMETRY_TOPIC);
latch.await(3, TimeUnit.SECONDS);
assertTrue(callback.isPubAckReceived());
}
@Test
public void testPushTelemetryWithMalformedPayloadAndSendAckOnErrorDisabled() throws Exception {
processBeforeTest("Test Post Telemetry device json payload", "Test Post Telemetry gateway json payload", TransportPayloadType.JSON, POST_DATA_TELEMETRY_TOPIC, null, false);
CountDownLatch latch = new CountDownLatch(1);
MqttAsyncClient client = getMqttAsyncClient(accessToken);
TestMqttPublishCallback callback = new TestMqttPublishCallback(latch);
client.setCallback(callback);
publishMqttMsg(client, MALFORMED_JSON_PAYLOAD.getBytes(), POST_DATA_TELEMETRY_TOPIC);
latch.await(3, TimeUnit.SECONDS);
assertFalse(callback.isPubAckReceived());
}
@Test
public void testPushTelemetryGatewayWithMalformedPayloadAndSendAckOnErrorEnabled() throws Exception {
processBeforeTest("Test Post Telemetry device json payload", "Test Post Telemetry gateway json payload", TransportPayloadType.JSON, POST_DATA_TELEMETRY_TOPIC, null, true);
CountDownLatch latch = new CountDownLatch(1);
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
TestMqttPublishCallback callback = new TestMqttPublishCallback(latch);
client.setCallback(callback);
publishMqttMsg(client, MALFORMED_JSON_PAYLOAD.getBytes(), POST_DATA_TELEMETRY_TOPIC);
latch.await(3, TimeUnit.SECONDS);
assertTrue(callback.isPubAckReceived());
}
@Test
public void testPushTelemetryGatewayWithMalformedPayloadAndSendAckOnErrorDisabled() throws Exception {
processBeforeTest("Test Post Telemetry device json payload", "Test Post Telemetry gateway json payload", TransportPayloadType.JSON, POST_DATA_TELEMETRY_TOPIC, null, false);
CountDownLatch latch = new CountDownLatch(1);
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
TestMqttPublishCallback callback = new TestMqttPublishCallback(latch);
client.setCallback(callback);
publishMqttMsg(client, MALFORMED_JSON_PAYLOAD.getBytes(), POST_DATA_TELEMETRY_TOPIC);
latch.await(3, TimeUnit.SECONDS);
assertFalse(callback.isPubAckReceived());
}
} }

85
application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java

@ -36,7 +36,10 @@ import org.thingsboard.server.gen.transport.TransportProtos;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
@ -44,6 +47,7 @@ import static org.junit.Assert.assertTrue;
public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends AbstractMqttTimeseriesIntegrationTest { public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends AbstractMqttTimeseriesIntegrationTest {
private static final String POST_DATA_TELEMETRY_TOPIC = "proto/telemetry"; private static final String POST_DATA_TELEMETRY_TOPIC = "proto/telemetry";
private static final String MALFORMED_PROTO_PAYLOAD = "invalid proto payload str";
@Before @Before
@Override @Override
@ -91,7 +95,7 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac
" }\n" + " }\n" +
" }\n" + " }\n" +
"}"; "}";
processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null, schemaStr, null, null, null, null, null, DeviceProfileProvisionType.DISABLED, false, false); processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null, schemaStr, null, null, null, null, null, DeviceProfileProvisionType.DISABLED, false, false, false);
DynamicSchema telemetrySchema = getDynamicSchema(schemaStr); DynamicSchema telemetrySchema = getDynamicSchema(schemaStr);
DynamicMessage.Builder nestedJsonObjectBuilder = telemetrySchema.newMessageBuilder("PostTelemetry.JsonObject.NestedJsonObject"); DynamicMessage.Builder nestedJsonObjectBuilder = telemetrySchema.newMessageBuilder("PostTelemetry.JsonObject.NestedJsonObject");
@ -193,7 +197,7 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac
" }\n" + " }\n" +
" }\n" + " }\n" +
"}"; "}";
processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null, schemaStr, null, null, null, null, null, DeviceProfileProvisionType.DISABLED, false, false); processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null, schemaStr, null, null, null, null, null, DeviceProfileProvisionType.DISABLED, false, false, false);
DynamicSchema telemetrySchema = getDynamicSchema(schemaStr); DynamicSchema telemetrySchema = getDynamicSchema(schemaStr);
DynamicMessage.Builder nestedJsonObjectBuilder = telemetrySchema.newMessageBuilder("PostTelemetry.JsonObject.NestedJsonObject"); DynamicMessage.Builder nestedJsonObjectBuilder = telemetrySchema.newMessageBuilder("PostTelemetry.JsonObject.NestedJsonObject");
@ -253,7 +257,7 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac
@Test @Test
public void testPushTelemetryGateway() throws Exception { public void testPushTelemetryGateway() throws Exception {
processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, null, null, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED, false, false); processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, null, null, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED, false, false, false);
TransportApiProtos.GatewayTelemetryMsg.Builder gatewayTelemetryMsgProtoBuilder = TransportApiProtos.GatewayTelemetryMsg.newBuilder(); TransportApiProtos.GatewayTelemetryMsg.Builder gatewayTelemetryMsgProtoBuilder = TransportApiProtos.GatewayTelemetryMsg.newBuilder();
List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5"); List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5");
String deviceName1 = "Device A"; String deviceName1 = "Device A";
@ -267,7 +271,7 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac
@Test @Test
public void testGatewayConnect() throws Exception { public void testGatewayConnect() throws Exception {
processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED, false, false); processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED, false, false, false);
String deviceName = "Device A"; String deviceName = "Device A";
TransportApiProtos.ConnectMsg connectMsgProto = getConnectProto(deviceName); TransportApiProtos.ConnectMsg connectMsgProto = getConnectProto(deviceName);
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken); MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
@ -280,6 +284,79 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac
assertNotNull(device); assertNotNull(device);
} }
@Test
public void testPushTelemetryWithMalformedPayloadAndSendAckOnErrorEnabled() throws Exception {
processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null, true);
CountDownLatch latch = new CountDownLatch(1);
MqttAsyncClient client = getMqttAsyncClient(accessToken);
TestMqttPublishCallback callback = new TestMqttPublishCallback(latch);
client.setCallback(callback);
publishMqttMsg(client, MALFORMED_PROTO_PAYLOAD.getBytes(), POST_DATA_TELEMETRY_TOPIC);
latch.await(3, TimeUnit.SECONDS);
assertTrue(callback.isPubAckReceived());
}
@Test
public void testPushTelemetryWithMalformedPayloadAndSendAckOnErrorDisabled() throws Exception {
processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null, false);
CountDownLatch latch = new CountDownLatch(1);
MqttAsyncClient client = getMqttAsyncClient(accessToken);
TestMqttPublishCallback callback = new TestMqttPublishCallback(latch);
client.setCallback(callback);
publishMqttMsg(client, MALFORMED_PROTO_PAYLOAD.getBytes(), POST_DATA_TELEMETRY_TOPIC);
latch.await(3, TimeUnit.SECONDS);
assertFalse(callback.isPubAckReceived());
}
@Test
public void testPushTelemetryWithMalformedPayloadAndSendAckOnErrorEnabledAndBackwardCompatibilityEnabled() throws Exception {
processBeforeTest("Test Post Telemetry device", "Test Post Telemetry gateway", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null, true, false, true);
CountDownLatch latch = new CountDownLatch(1);
MqttAsyncClient client = getMqttAsyncClient(accessToken);
TestMqttPublishCallback callback = new TestMqttPublishCallback(latch);
client.setCallback(callback);
publishMqttMsg(client, MALFORMED_JSON_PAYLOAD.getBytes(), POST_DATA_TELEMETRY_TOPIC);
latch.await(3, TimeUnit.SECONDS);
assertTrue(callback.isPubAckReceived());
}
@Test
public void testPushTelemetryWithMalformedPayloadAndSendAckOnErrorDisabledAndBackwardCompatibilityEnabled() throws Exception {
processBeforeTest("Test Post Telemetry device", "Test Post Telemetry gateway", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null, true, false, false);
CountDownLatch latch = new CountDownLatch(1);
MqttAsyncClient client = getMqttAsyncClient(accessToken);
TestMqttPublishCallback callback = new TestMqttPublishCallback(latch);
client.setCallback(callback);
publishMqttMsg(client, MALFORMED_JSON_PAYLOAD.getBytes(), POST_DATA_TELEMETRY_TOPIC);
latch.await(3, TimeUnit.SECONDS);
assertFalse(callback.isPubAckReceived());
}
@Test
public void testPushTelemetryGatewayWithMalformedPayloadAndSendAckOnErrorEnabled() throws Exception {
processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null, true);
CountDownLatch latch = new CountDownLatch(1);
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
TestMqttPublishCallback callback = new TestMqttPublishCallback(latch);
client.setCallback(callback);
publishMqttMsg(client, MALFORMED_PROTO_PAYLOAD.getBytes(), POST_DATA_TELEMETRY_TOPIC);
latch.await(3, TimeUnit.SECONDS);
assertTrue(callback.isPubAckReceived());
}
@Test
public void testPushTelemetryGatewayWithMalformedPayloadAndSendAckOnErrorDisabled() throws Exception {
processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null, false);
CountDownLatch latch = new CountDownLatch(1);
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
TestMqttPublishCallback callback = new TestMqttPublishCallback(latch);
client.setCallback(callback);
publishMqttMsg(client, MALFORMED_PROTO_PAYLOAD.getBytes(), POST_DATA_TELEMETRY_TOPIC);
latch.await(3, TimeUnit.SECONDS);
assertFalse(callback.isPubAckReceived());
}
private DynamicSchema getDynamicSchema(String deviceTelemetryProtoSchema) { private DynamicSchema getDynamicSchema(String deviceTelemetryProtoSchema) {
DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration();
assertTrue(transportConfiguration instanceof MqttDeviceProfileTransportConfiguration); assertTrue(transportConfiguration instanceof MqttDeviceProfileTransportConfiguration);

3
common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeEventService.java

@ -15,6 +15,7 @@
*/ */
package org.thingsboard.server.dao.edge; package org.thingsboard.server.dao.edge;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
@ -23,7 +24,7 @@ import org.thingsboard.server.common.data.page.TimePageLink;
public interface EdgeEventService { public interface EdgeEventService {
EdgeEvent save(EdgeEvent edgeEvent); ListenableFuture<Void> saveAsync(EdgeEvent edgeEvent);
PageData<EdgeEvent> findEdgeEvents(TenantId tenantId, EdgeId edgeId, TimePageLink pageLink, boolean withTsUpdate); PageData<EdgeEvent> findEdgeEvents(TenantId tenantId, EdgeId edgeId, TimePageLink pageLink, boolean withTsUpdate);

24
common/data/src/main/java/org/thingsboard/server/common/data/EdgeUtils.java

@ -15,8 +15,14 @@
*/ */
package org.thingsboard.server.common.data; package org.thingsboard.server.common.data;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.ThreadLocalRandom;
@ -63,4 +69,22 @@ public final class EdgeUtils {
public static int nextPositiveInt() { public static int nextPositiveInt() {
return ThreadLocalRandom.current().nextInt(0, Integer.MAX_VALUE); return ThreadLocalRandom.current().nextInt(0, Integer.MAX_VALUE);
} }
public static EdgeEvent constructEdgeEvent(TenantId tenantId,
EdgeId edgeId,
EdgeEventType type,
EdgeEventActionType action,
EntityId entityId,
JsonNode body) {
EdgeEvent edgeEvent = new EdgeEvent();
edgeEvent.setTenantId(tenantId);
edgeEvent.setEdgeId(edgeId);
edgeEvent.setType(type);
edgeEvent.setAction(action);
if (entityId != null) {
edgeEvent.setEntityId(entityId.getId());
}
edgeEvent.setBody(body);
return edgeEvent;
}
} }

1
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java

@ -27,6 +27,7 @@ public class MqttDeviceProfileTransportConfiguration implements DeviceProfileTra
@NoXss @NoXss
private String deviceAttributesTopic = MqttTopics.DEVICE_ATTRIBUTES_TOPIC; private String deviceAttributesTopic = MqttTopics.DEVICE_ATTRIBUTES_TOPIC;
private TransportPayloadTypeConfiguration transportPayloadTypeConfiguration; private TransportPayloadTypeConfiguration transportPayloadTypeConfiguration;
private boolean sendAckOnValidationException;
@Override @Override
public DeviceTransportType getType() { public DeviceTransportType getType() {

11
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java

@ -362,7 +362,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
ctx.close(); ctx.close();
} catch (AdaptorException e) { } catch (AdaptorException e) {
log.debug("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e); log.debug("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e);
ctx.close(); sendAckOrCloseSession(ctx, topicName, msgId);
} }
} }
@ -451,6 +451,15 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
} }
} catch (AdaptorException e) { } catch (AdaptorException e) {
log.debug("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e); log.debug("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e);
sendAckOrCloseSession(ctx, topicName, msgId);
}
}
private void sendAckOrCloseSession(ChannelHandlerContext ctx, String topicName, int msgId) {
if (deviceSessionCtx.isSendAckOnValidationException() && msgId > 0) {
log.debug("[{}] Send pub ack on invalid publish msg [{}][{}]", sessionId, topicName, msgId);
ctx.writeAndFlush(createMqttPubAckMsg(msgId));
} else {
log.info("[{}] Closing current session due to invalid publish msg [{}][{}]", sessionId, topicName, msgId); log.info("[{}] Closing current session due to invalid publish msg [{}][{}]", sessionId, topicName, msgId);
ctx.close(); ctx.close();
} }

7
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java

@ -84,6 +84,7 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext {
private volatile MqttTransportAdaptor adaptor; private volatile MqttTransportAdaptor adaptor;
private volatile boolean jsonPayloadFormatCompatibilityEnabled; private volatile boolean jsonPayloadFormatCompatibilityEnabled;
private volatile boolean useJsonPayloadFormatForDefaultDownlinkTopics; private volatile boolean useJsonPayloadFormatForDefaultDownlinkTopics;
private volatile boolean sendAckOnValidationException;
@Getter @Getter
@Setter @Setter
@ -115,6 +116,10 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext {
return payloadType.equals(TransportPayloadType.JSON); return payloadType.equals(TransportPayloadType.JSON);
} }
public boolean isSendAckOnValidationException() {
return sendAckOnValidationException;
}
public Descriptors.Descriptor getTelemetryDynamicMsgDescriptor() { public Descriptors.Descriptor getTelemetryDynamicMsgDescriptor() {
return telemetryDynamicMessageDescriptor; return telemetryDynamicMessageDescriptor;
} }
@ -152,6 +157,7 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext {
payloadType = transportPayloadTypeConfiguration.getTransportPayloadType(); payloadType = transportPayloadTypeConfiguration.getTransportPayloadType();
telemetryTopicFilter = MqttTopicFilterFactory.toFilter(mqttConfig.getDeviceTelemetryTopic()); telemetryTopicFilter = MqttTopicFilterFactory.toFilter(mqttConfig.getDeviceTelemetryTopic());
attributesTopicFilter = MqttTopicFilterFactory.toFilter(mqttConfig.getDeviceAttributesTopic()); attributesTopicFilter = MqttTopicFilterFactory.toFilter(mqttConfig.getDeviceAttributesTopic());
sendAckOnValidationException = mqttConfig.isSendAckOnValidationException();
if (TransportPayloadType.PROTOBUF.equals(payloadType)) { if (TransportPayloadType.PROTOBUF.equals(payloadType)) {
ProtoTransportPayloadConfiguration protoTransportPayloadConfig = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; ProtoTransportPayloadConfiguration protoTransportPayloadConfig = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration;
updateDynamicMessageDescriptors(protoTransportPayloadConfig); updateDynamicMessageDescriptors(protoTransportPayloadConfig);
@ -162,6 +168,7 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext {
telemetryTopicFilter = MqttTopicFilterFactory.getDefaultTelemetryFilter(); telemetryTopicFilter = MqttTopicFilterFactory.getDefaultTelemetryFilter();
attributesTopicFilter = MqttTopicFilterFactory.getDefaultAttributesFilter(); attributesTopicFilter = MqttTopicFilterFactory.getDefaultAttributesFilter();
payloadType = TransportPayloadType.JSON; payloadType = TransportPayloadType.JSON;
sendAckOnValidationException = false;
} }
updateAdaptor(); updateAdaptor();
} }

5
dao/src/main/java/org/thingsboard/server/dao/edge/BaseEdgeEventService.java

@ -15,6 +15,7 @@
*/ */
package org.thingsboard.server.dao.edge; package org.thingsboard.server.dao.edge;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -36,9 +37,9 @@ public class BaseEdgeEventService implements EdgeEventService {
private DataValidator<EdgeEvent> edgeEventValidator; private DataValidator<EdgeEvent> edgeEventValidator;
@Override @Override
public EdgeEvent save(EdgeEvent edgeEvent) { public ListenableFuture<Void> saveAsync(EdgeEvent edgeEvent) {
edgeEventValidator.validate(edgeEvent, EdgeEvent::getTenantId); edgeEventValidator.validate(edgeEvent, EdgeEvent::getTenantId);
return edgeEventDao.save(edgeEvent); return edgeEventDao.saveAsync(edgeEvent);
} }
@Override @Override

2
dao/src/main/java/org/thingsboard/server/dao/edge/EdgeEventDao.java

@ -35,7 +35,7 @@ public interface EdgeEventDao extends Dao<EdgeEvent> {
* @param edgeEvent the event object * @param edgeEvent the event object
* @return saved edge event object future * @return saved edge event object future
*/ */
EdgeEvent save(EdgeEvent edgeEvent); ListenableFuture<Void> saveAsync(EdgeEvent edgeEvent);
/** /**

28
dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java

@ -24,9 +24,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache; import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager; import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
@ -86,29 +84,31 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti
@Autowired @Autowired
private DataValidator<EntityView> entityViewValidator; private DataValidator<EntityView> entityViewValidator;
@Caching(evict = {
@CacheEvict(cacheNames = ENTITY_VIEW_CACHE, key = "{#entityView.tenantId, #entityView.entityId}"),
@CacheEvict(cacheNames = ENTITY_VIEW_CACHE, key = "{#entityView.tenantId, #entityView.name}"),
@CacheEvict(cacheNames = ENTITY_VIEW_CACHE, key = "{#entityView.id}")})
@Override @Override
public EntityView saveEntityView(EntityView entityView) { public EntityView saveEntityView(EntityView entityView) {
log.trace("Executing save entity view [{}]", entityView); log.trace("Executing save entity view [{}]", entityView);
entityViewValidator.validate(entityView, EntityView::getTenantId); entityViewValidator.validate(entityView, EntityView::getTenantId);
if (entityView.getId() != null) {
EntityView oldEntityView = entityViewDao.findById(entityView.getTenantId(), entityView.getUuidId());
evictCache(oldEntityView);
}
return entityViewDao.save(entityView.getTenantId(), entityView); return entityViewDao.save(entityView.getTenantId(), entityView);
} }
@CacheEvict(cacheNames = ENTITY_VIEW_CACHE, key = "{#entityViewId}")
@Override @Override
public EntityView assignEntityViewToCustomer(TenantId tenantId, EntityViewId entityViewId, CustomerId customerId) { public EntityView assignEntityViewToCustomer(TenantId tenantId, EntityViewId entityViewId, CustomerId customerId) {
EntityView entityView = findEntityViewById(tenantId, entityViewId); EntityView entityView = findEntityViewById(tenantId, entityViewId);
evictCache(entityView);
entityView.setCustomerId(customerId); entityView.setCustomerId(customerId);
return saveEntityView(entityView); return saveEntityView(entityView);
} }
@CacheEvict(cacheNames = ENTITY_VIEW_CACHE, key = "{#entityViewId}")
@Override @Override
public EntityView unassignEntityViewFromCustomer(TenantId tenantId, EntityViewId entityViewId) { public EntityView unassignEntityViewFromCustomer(TenantId tenantId, EntityViewId entityViewId) {
EntityView entityView = findEntityViewById(tenantId, entityViewId); EntityView entityView = findEntityViewById(tenantId, entityViewId);
evictCache(entityView);
entityView.setCustomerId(null); entityView.setCustomerId(null);
return saveEntityView(entityView); return saveEntityView(entityView);
} }
@ -292,15 +292,13 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti
} }
} }
@CacheEvict(cacheNames = ENTITY_VIEW_CACHE, key = "{#entityViewId}")
@Override @Override
public void deleteEntityView(TenantId tenantId, EntityViewId entityViewId) { public void deleteEntityView(TenantId tenantId, EntityViewId entityViewId) {
log.trace("Executing deleteEntityView [{}]", entityViewId); log.trace("Executing deleteEntityView [{}]", entityViewId);
validateId(entityViewId, INCORRECT_ENTITY_VIEW_ID + entityViewId); validateId(entityViewId, INCORRECT_ENTITY_VIEW_ID + entityViewId);
deleteEntityRelations(tenantId, entityViewId); deleteEntityRelations(tenantId, entityViewId);
EntityView entityView = entityViewDao.findById(tenantId, entityViewId.getId()); EntityView entityView = entityViewDao.findById(tenantId, entityViewId.getId());
cacheManager.getCache(ENTITY_VIEW_CACHE).evict(Arrays.asList(entityView.getTenantId(), entityView.getEntityId())); evictCache(entityView);
cacheManager.getCache(ENTITY_VIEW_CACHE).evict(Arrays.asList(entityView.getTenantId(), entityView.getName()));
entityViewDao.removeById(tenantId, entityViewId.getId()); entityViewDao.removeById(tenantId, entityViewId.getId());
} }
@ -323,7 +321,6 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti
}, MoreExecutors.directExecutor()); }, MoreExecutors.directExecutor());
} }
@CacheEvict(cacheNames = ENTITY_VIEW_CACHE, key = "{#entityViewId}")
@Override @Override
public EntityView assignEntityViewToEdge(TenantId tenantId, EntityViewId entityViewId, EdgeId edgeId) { public EntityView assignEntityViewToEdge(TenantId tenantId, EntityViewId entityViewId, EdgeId edgeId) {
EntityView entityView = findEntityViewById(tenantId, entityViewId); EntityView entityView = findEntityViewById(tenantId, entityViewId);
@ -413,4 +410,11 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti
unassignEntityViewFromCustomer(tenantId, new EntityViewId(entity.getUuidId())); unassignEntityViewFromCustomer(tenantId, new EntityViewId(entity.getUuidId()));
} }
}; };
private void evictCache(EntityView entityView) {
Cache cache = cacheManager.getCache(ENTITY_VIEW_CACHE);
cache.evict(Arrays.asList(entityView.getTenantId(), entityView.getEntityId()));
cache.evict(Arrays.asList(entityView.getTenantId(), entityView.getName()));
cache.evict(Arrays.asList(entityView.getId()));
}
} }

79
dao/src/main/java/org/thingsboard/server/dao/sql/edge/EdgeEventInsertRepository.java

@ -0,0 +1,79 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.sql.edge;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import org.thingsboard.server.dao.model.sql.EdgeEventEntity;
import org.thingsboard.server.dao.util.PsqlDao;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;
@PsqlDao
@Repository
@Transactional
public class EdgeEventInsertRepository {
private static final String INSERT =
"INSERT INTO edge_event (id, created_time, edge_id, edge_event_type, edge_event_uid, entity_id, edge_event_action, body, tenant_id, ts) " +
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) " +
"ON CONFLICT DO NOTHING;";
@Autowired
protected JdbcTemplate jdbcTemplate;
@Autowired
private TransactionTemplate transactionTemplate;
protected void save(List<EdgeEventEntity> entities) {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
jdbcTemplate.batchUpdate(INSERT, new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
EdgeEventEntity edgeEvent = entities.get(i);
ps.setObject(1, edgeEvent.getId());
ps.setLong(2, edgeEvent.getCreatedTime());
ps.setObject(3, edgeEvent.getEdgeId());
ps.setString(4, edgeEvent.getEdgeEventType().name());
ps.setString(5, edgeEvent.getEdgeEventUid());
ps.setObject(6, edgeEvent.getEntityId());
ps.setString(7, edgeEvent.getEdgeEventAction().name());
ps.setString(8, edgeEvent.getEntityBody() != null
? edgeEvent.getEntityBody().toString()
: null);
ps.setObject(9, edgeEvent.getTenantId());
ps.setLong(10, edgeEvent.getTs());
}
@Override
public int getBatchSize() {
return entities.size();
}
});
}
});
}
}

164
dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaBaseEdgeEventDao.java

@ -16,9 +16,11 @@
package org.thingsboard.server.dao.sql.edge; package org.thingsboard.server.dao.sql.edge;
import com.datastax.oss.driver.api.core.uuid.Uuids; import com.datastax.oss.driver.api.core.uuid.Uuids;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEvent;
@ -26,23 +28,26 @@ import org.thingsboard.server.common.data.id.EdgeEventId;
import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.stats.StatsFactory;
import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.edge.EdgeEventDao; import org.thingsboard.server.dao.edge.EdgeEventDao;
import org.thingsboard.server.dao.model.sql.EdgeEventEntity; import org.thingsboard.server.dao.model.sql.EdgeEventEntity;
import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao; import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao;
import org.thingsboard.server.dao.sql.ScheduledLogExecutorComponent;
import org.thingsboard.server.dao.sql.TbSqlBlockingQueueParams;
import org.thingsboard.server.dao.sql.TbSqlBlockingQueueWrapper;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.sql.Connection; import java.sql.Connection;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.Comparator;
import java.util.Objects; import java.util.Objects;
import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock; import java.util.function.Function;
import java.util.concurrent.locks.ReentrantLock;
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
@ -52,11 +57,32 @@ public class JpaBaseEdgeEventDao extends JpaAbstractSearchTextDao<EdgeEventEntit
private final UUID systemTenantId = NULL_UUID; private final UUID systemTenantId = NULL_UUID;
private final ConcurrentMap<EdgeId, Lock> readWriteLocks = new ConcurrentHashMap<>(); @Autowired
ScheduledLogExecutorComponent logExecutor;
@Autowired
private StatsFactory statsFactory;
@Value("${sql.edge_events.batch_size:1000}")
private int batchSize;
@Value("${sql.edge_events.batch_max_delay:100}")
private long maxDelay;
@Value("${sql.edge_events.stats_print_interval_ms:10000}")
private long statsPrintIntervalMs;
@Value("${sql.edge_events.batch_threads:3}")
private int batchThreads;
private TbSqlBlockingQueueWrapper<EdgeEventEntity> queue;
@Autowired @Autowired
private EdgeEventRepository edgeEventRepository; private EdgeEventRepository edgeEventRepository;
@Autowired
private EdgeEventInsertRepository edgeEventInsertRepository;
@Override @Override
protected Class<EdgeEventEntity> getEntityClass() { protected Class<EdgeEventEntity> getEntityClass() {
return EdgeEventEntity.class; return EdgeEventEntity.class;
@ -67,66 +93,58 @@ public class JpaBaseEdgeEventDao extends JpaAbstractSearchTextDao<EdgeEventEntit
return edgeEventRepository; return edgeEventRepository;
} }
@Override @PostConstruct
public EdgeEvent save(EdgeEvent edgeEvent) { private void init() {
final Lock readWriteLock = readWriteLocks.computeIfAbsent(edgeEvent.getEdgeId(), id -> new ReentrantLock()); TbSqlBlockingQueueParams params = TbSqlBlockingQueueParams.builder()
readWriteLock.lock(); .logName("Edge Events")
try { .batchSize(batchSize)
log.debug("Save edge event [{}] ", edgeEvent); .maxDelay(maxDelay)
if (edgeEvent.getId() == null) { .statsPrintIntervalMs(statsPrintIntervalMs)
UUID timeBased = Uuids.timeBased(); .statsNamePrefix("edge.events")
edgeEvent.setId(new EdgeEventId(timeBased)); .batchSortEnabled(true)
edgeEvent.setCreatedTime(Uuids.unixTimestamp(timeBased)); .build();
} else if (edgeEvent.getCreatedTime() == 0L) { Function<EdgeEventEntity, Integer> hashcodeFunction = entity -> {
UUID eventId = edgeEvent.getId().getId(); if (entity.getEntityId() != null) {
if (eventId.version() == 1) { return entity.getEntityId().hashCode();
edgeEvent.setCreatedTime(Uuids.unixTimestamp(eventId)); } else {
} else { return NULL_UUID.hashCode();
edgeEvent.setCreatedTime(System.currentTimeMillis());
}
}
if (StringUtils.isEmpty(edgeEvent.getUid())) {
edgeEvent.setUid(edgeEvent.getId().toString());
} }
return save(new EdgeEventEntity(edgeEvent)).orElse(null); };
} finally { queue = new TbSqlBlockingQueueWrapper<>(params, hashcodeFunction, batchThreads, statsFactory);
readWriteLock.unlock(); queue.init(logExecutor, v -> edgeEventInsertRepository.save(v),
Comparator.comparing(EdgeEventEntity::getTs)
);
}
@PreDestroy
private void destroy() {
if (queue != null) {
queue.destroy();
} }
} }
@Override @Override
public PageData<EdgeEvent> findEdgeEvents(UUID tenantId, EdgeId edgeId, TimePageLink pageLink, boolean withTsUpdate) { public ListenableFuture<Void> saveAsync(EdgeEvent edgeEvent) {
final Lock readWriteLock = readWriteLocks.computeIfAbsent(edgeId, id -> new ReentrantLock()); log.debug("Save edge event [{}] ", edgeEvent);
readWriteLock.lock(); if (edgeEvent.getId() == null) {
try { UUID timeBased = Uuids.timeBased();
if (withTsUpdate) { edgeEvent.setId(new EdgeEventId(timeBased));
return DaoUtil.toPageData( edgeEvent.setCreatedTime(Uuids.unixTimestamp(timeBased));
edgeEventRepository } else if (edgeEvent.getCreatedTime() == 0L) {
.findEdgeEventsByTenantIdAndEdgeId( UUID eventId = edgeEvent.getId().getId();
tenantId, if (eventId.version() == 1) {
edgeId.getId(), edgeEvent.setCreatedTime(Uuids.unixTimestamp(eventId));
Objects.toString(pageLink.getTextSearch(), ""),
pageLink.getStartTime(),
pageLink.getEndTime(),
DaoUtil.toPageable(pageLink)));
} else { } else {
return DaoUtil.toPageData( edgeEvent.setCreatedTime(System.currentTimeMillis());
edgeEventRepository
.findEdgeEventsByTenantIdAndEdgeIdWithoutTimeseriesUpdated(
tenantId,
edgeId.getId(),
Objects.toString(pageLink.getTextSearch(), ""),
pageLink.getStartTime(),
pageLink.getEndTime(),
DaoUtil.toPageable(pageLink)));
} }
} finally {
readWriteLock.unlock();
} }
if (StringUtils.isEmpty(edgeEvent.getUid())) {
edgeEvent.setUid(edgeEvent.getId().toString());
}
return save(new EdgeEventEntity(edgeEvent));
} }
public Optional<EdgeEvent> save(EdgeEventEntity entity) { private ListenableFuture<Void> save(EdgeEventEntity entity) {
log.debug("Save edge event [{}] ", entity); log.debug("Save edge event [{}] ", entity);
if (entity.getTenantId() == null) { if (entity.getTenantId() == null) {
log.trace("Save system edge event with predefined id {}", systemTenantId); log.trace("Save system edge event with predefined id {}", systemTenantId);
@ -135,7 +153,39 @@ public class JpaBaseEdgeEventDao extends JpaAbstractSearchTextDao<EdgeEventEntit
if (entity.getUuid() == null) { if (entity.getUuid() == null) {
entity.setUuid(Uuids.timeBased()); entity.setUuid(Uuids.timeBased());
} }
return Optional.of(DaoUtil.getData(edgeEventRepository.save(entity)));
return addToQueue(entity);
}
private ListenableFuture<Void> addToQueue(EdgeEventEntity entity) {
return queue.add(entity);
}
@Override
public PageData<EdgeEvent> findEdgeEvents(UUID tenantId, EdgeId edgeId, TimePageLink pageLink, boolean withTsUpdate) {
if (withTsUpdate) {
return DaoUtil.toPageData(
edgeEventRepository
.findEdgeEventsByTenantIdAndEdgeId(
tenantId,
edgeId.getId(),
Objects.toString(pageLink.getTextSearch(), ""),
pageLink.getStartTime(),
pageLink.getEndTime(),
DaoUtil.toPageable(pageLink)));
} else {
return DaoUtil.toPageData(
edgeEventRepository
.findEdgeEventsByTenantIdAndEdgeIdWithoutTimeseriesUpdated(
tenantId,
edgeId.getId(),
Objects.toString(pageLink.getTextSearch(), ""),
pageLink.getStartTime(),
pageLink.getEndTime(),
DaoUtil.toPageable(pageLink)));
}
} }
@Override @Override

76
dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultQueryLogComponent.java

@ -17,9 +17,15 @@ package org.thingsboard.server.dao.sql.query;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.jdbc.core.SqlParameter;
import org.springframework.jdbc.core.SqlParameterValue;
import org.springframework.jdbc.core.namedparam.NamedParameterUtils;
import org.springframework.jdbc.core.namedparam.ParsedSql;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.Arrays; import java.util.List;
import java.util.UUID;
@Component @Component
@Slf4j @Slf4j
@ -33,8 +39,72 @@ public class DefaultQueryLogComponent implements QueryLogComponent {
@Override @Override
public void logQuery(QueryContext ctx, String query, long duration) { public void logQuery(QueryContext ctx, String query, long duration) {
if (logSqlQueries && duration > logQueriesThreshold) { if (logSqlQueries && duration > logQueriesThreshold) {
log.info("QUERY: {} took {} ms", query, duration);
Arrays.asList(ctx.getParameterNames()).forEach(param -> log.info("QUERY PARAM: {} -> {}", param, ctx.getValue(param))); String sqlToUse = substituteParametersInSqlString(query, ctx);
log.warn("SLOW QUERY took {} ms: {}", duration, sqlToUse);
}
}
String substituteParametersInSqlString(String sql, SqlParameterSource paramSource) {
ParsedSql parsedSql = NamedParameterUtils.parseSqlStatement(sql);
List<SqlParameter> declaredParams = NamedParameterUtils.buildSqlParameterList(parsedSql, paramSource);
if (declaredParams.isEmpty()) {
return sql;
}
for (SqlParameter parSQL: declaredParams) {
String paramName = parSQL.getName();
if (!paramSource.hasValue(paramName)) {
continue;
}
Object value = paramSource.getValue(paramName);
if (value instanceof SqlParameterValue) {
value = ((SqlParameterValue)value).getValue();
}
if (!(value instanceof Iterable)) {
String ValueForSQLQuery = getValueForSQLQuery(value);
sql = sql.replace(":" + paramName, ValueForSQLQuery);
continue;
}
//Iterable
int count = 0;
String valueArrayStr = "";
for (Object valueTemp: (Iterable)value) {
if (count > 0) {
valueArrayStr+=", ";
}
String valueForSQLQuery = getValueForSQLQuery(valueTemp);
valueArrayStr += valueForSQLQuery;
++count;
}
sql = sql.replace(":" + paramName, valueArrayStr);
} }
return sql;
}
String getValueForSQLQuery(Object valueParameter) {
if (valueParameter instanceof String) {
return "'" + ((String) valueParameter).replaceAll("'", "''") + "'";
}
if (valueParameter instanceof UUID) {
return "'" + valueParameter + "'";
}
return String.valueOf(valueParameter);
} }
} }

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

@ -242,10 +242,17 @@ CREATE TABLE IF NOT EXISTS device_profile (
CONSTRAINT fk_software_device_profile FOREIGN KEY (software_id) REFERENCES ota_package(id) CONSTRAINT fk_software_device_profile FOREIGN KEY (software_id) REFERENCES ota_package(id)
); );
ALTER TABLE ota_package DO
ADD CONSTRAINT fk_device_profile_ota_package $$
FOREIGN KEY (device_profile_id) REFERENCES device_profile (id) BEGIN
ON DELETE CASCADE; IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'fk_device_profile_ota_package') THEN
ALTER TABLE ota_package
ADD CONSTRAINT fk_device_profile_ota_package
FOREIGN KEY (device_profile_id) REFERENCES device_profile (id)
ON DELETE CASCADE;
END IF;
END;
$$;
-- We will use one-to-many relation in the first release and extend this feature in case of user requests -- We will use one-to-many relation in the first release and extend this feature in case of user requests
-- CREATE TABLE IF NOT EXISTS device_profile_firmware ( -- CREATE TABLE IF NOT EXISTS device_profile_firmware (

1
dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java

@ -25,6 +25,7 @@ import org.junit.runner.RunWith;
"org.thingsboard.server.dao.service.event.sql.*SqlTest", "org.thingsboard.server.dao.service.event.sql.*SqlTest",
"org.thingsboard.server.dao.service.sql.*SqlTest", "org.thingsboard.server.dao.service.sql.*SqlTest",
"org.thingsboard.server.dao.service.timeseries.sql.*SqlTest", "org.thingsboard.server.dao.service.timeseries.sql.*SqlTest",
"org.thingsboard.server.dao.service.install.sql.*SqlTest"
}) })
public class SqlDaoServiceTestSuite { public class SqlDaoServiceTestSuite {
} }

44
dao/src/test/java/org/thingsboard/server/dao/service/BaseEdgeEventServiceTest.java

@ -16,6 +16,8 @@
package org.thingsboard.server.dao.service; package org.thingsboard.server.dao.service;
import com.datastax.oss.driver.api.core.uuid.Uuids; import com.datastax.oss.driver.api.core.uuid.Uuids;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEvent;
@ -34,6 +36,8 @@ import java.io.IOException;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.Month; import java.time.Month;
import java.time.ZoneOffset; import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.List;
public abstract class BaseEdgeEventServiceTest extends AbstractServiceTest { public abstract class BaseEdgeEventServiceTest extends AbstractServiceTest {
@ -41,8 +45,14 @@ public abstract class BaseEdgeEventServiceTest extends AbstractServiceTest {
public void saveEdgeEvent() throws Exception { public void saveEdgeEvent() throws Exception {
EdgeId edgeId = new EdgeId(Uuids.timeBased()); EdgeId edgeId = new EdgeId(Uuids.timeBased());
DeviceId deviceId = new DeviceId(Uuids.timeBased()); DeviceId deviceId = new DeviceId(Uuids.timeBased());
EdgeEvent edgeEvent = generateEdgeEvent(null, edgeId, deviceId, EdgeEventActionType.ADDED); TenantId tenantId = new TenantId(Uuids.timeBased());
EdgeEvent saved = edgeEventService.save(edgeEvent); EdgeEvent edgeEvent = generateEdgeEvent(tenantId, edgeId, deviceId, EdgeEventActionType.ADDED);
edgeEventService.saveAsync(edgeEvent).get();
PageData<EdgeEvent> edgeEvents = edgeEventService.findEdgeEvents(tenantId, edgeId, new TimePageLink(1), false);
Assert.assertFalse(edgeEvents.getData().isEmpty());
EdgeEvent saved = edgeEvents.getData().get(0);
Assert.assertEquals(saved.getTenantId(), edgeEvent.getTenantId()); Assert.assertEquals(saved.getTenantId(), edgeEvent.getTenantId());
Assert.assertEquals(saved.getEdgeId(), edgeEvent.getEdgeId()); Assert.assertEquals(saved.getEdgeId(), edgeEvent.getEdgeId());
Assert.assertEquals(saved.getEntityId(), edgeEvent.getEntityId()); Assert.assertEquals(saved.getEntityId(), edgeEvent.getEntityId());
@ -77,27 +87,31 @@ public abstract class BaseEdgeEventServiceTest extends AbstractServiceTest {
EdgeId edgeId = new EdgeId(Uuids.timeBased()); EdgeId edgeId = new EdgeId(Uuids.timeBased());
DeviceId deviceId = new DeviceId(Uuids.timeBased()); DeviceId deviceId = new DeviceId(Uuids.timeBased());
TenantId tenantId = TenantId.fromUUID(Uuids.timeBased()); TenantId tenantId = TenantId.fromUUID(Uuids.timeBased());
saveEdgeEventWithProvidedTime(timeBeforeStartTime, edgeId, deviceId, tenantId);
EdgeEvent savedEdgeEvent = saveEdgeEventWithProvidedTime(eventTime, edgeId, deviceId, tenantId); List<ListenableFuture<Void>> futures = new ArrayList<>();
EdgeEvent savedEdgeEvent2 = saveEdgeEventWithProvidedTime(eventTime + 1, edgeId, deviceId, tenantId); futures.add(saveEdgeEventWithProvidedTime(timeBeforeStartTime, edgeId, deviceId, tenantId));
EdgeEvent savedEdgeEvent3 = saveEdgeEventWithProvidedTime(eventTime + 2, edgeId, deviceId, tenantId); futures.add(saveEdgeEventWithProvidedTime(eventTime, edgeId, deviceId, tenantId));
saveEdgeEventWithProvidedTime(timeAfterEndTime, edgeId, deviceId, tenantId); futures.add(saveEdgeEventWithProvidedTime(eventTime + 1, edgeId, deviceId, tenantId));
futures.add(saveEdgeEventWithProvidedTime(eventTime + 2, edgeId, deviceId, tenantId));
futures.add(saveEdgeEventWithProvidedTime(timeAfterEndTime, edgeId, deviceId, tenantId));
Futures.allAsList(futures).get();
TimePageLink pageLink = new TimePageLink(2, 0, "", new SortOrder("createdTime", SortOrder.Direction.DESC), startTime, endTime); TimePageLink pageLink = new TimePageLink(2, 0, "", new SortOrder("createdTime", SortOrder.Direction.DESC), startTime, endTime);
PageData<EdgeEvent> edgeEvents = edgeEventService.findEdgeEvents(tenantId, edgeId, pageLink, true); PageData<EdgeEvent> edgeEvents = edgeEventService.findEdgeEvents(tenantId, edgeId, pageLink, true);
Assert.assertNotNull(edgeEvents.getData()); Assert.assertNotNull(edgeEvents.getData());
Assert.assertTrue(edgeEvents.getData().size() == 2); Assert.assertEquals(2, edgeEvents.getData().size());
Assert.assertTrue(edgeEvents.getData().get(0).getUuidId().equals(savedEdgeEvent3.getUuidId())); Assert.assertEquals(Uuids.startOf(eventTime + 2), edgeEvents.getData().get(0).getUuidId());
Assert.assertTrue(edgeEvents.getData().get(1).getUuidId().equals(savedEdgeEvent2.getUuidId())); Assert.assertEquals(Uuids.startOf(eventTime + 1), edgeEvents.getData().get(1).getUuidId());
Assert.assertTrue(edgeEvents.hasNext()); Assert.assertTrue(edgeEvents.hasNext());
Assert.assertNotNull(pageLink.nextPageLink()); Assert.assertNotNull(pageLink.nextPageLink());
edgeEvents = edgeEventService.findEdgeEvents(tenantId, edgeId, pageLink.nextPageLink(), true); edgeEvents = edgeEventService.findEdgeEvents(tenantId, edgeId, pageLink.nextPageLink(), true);
Assert.assertNotNull(edgeEvents.getData()); Assert.assertNotNull(edgeEvents.getData());
Assert.assertTrue(edgeEvents.getData().size() == 1); Assert.assertEquals(1, edgeEvents.getData().size());
Assert.assertTrue(edgeEvents.getData().get(0).getUuidId().equals(savedEdgeEvent.getUuidId())); Assert.assertEquals(Uuids.startOf(eventTime), edgeEvents.getData().get(0).getUuidId());
Assert.assertFalse(edgeEvents.hasNext()); Assert.assertFalse(edgeEvents.hasNext());
} }
@ -109,7 +123,7 @@ public abstract class BaseEdgeEventServiceTest extends AbstractServiceTest {
TimePageLink pageLink = new TimePageLink(1, 0, null, new SortOrder("createdTime", SortOrder.Direction.ASC)); TimePageLink pageLink = new TimePageLink(1, 0, null, new SortOrder("createdTime", SortOrder.Direction.ASC));
EdgeEvent edgeEventWithTsUpdate = generateEdgeEvent(tenantId, edgeId, deviceId, EdgeEventActionType.TIMESERIES_UPDATED); EdgeEvent edgeEventWithTsUpdate = generateEdgeEvent(tenantId, edgeId, deviceId, EdgeEventActionType.TIMESERIES_UPDATED);
edgeEventService.save(edgeEventWithTsUpdate); edgeEventService.saveAsync(edgeEventWithTsUpdate).get();
PageData<EdgeEvent> allEdgeEvents = edgeEventService.findEdgeEvents(tenantId, edgeId, pageLink, true); PageData<EdgeEvent> allEdgeEvents = edgeEventService.findEdgeEvents(tenantId, edgeId, pageLink, true);
PageData<EdgeEvent> edgeEventsWithoutTsUpdate = edgeEventService.findEdgeEvents(tenantId, edgeId, pageLink, false); PageData<EdgeEvent> edgeEventsWithoutTsUpdate = edgeEventService.findEdgeEvents(tenantId, edgeId, pageLink, false);
@ -121,9 +135,9 @@ public abstract class BaseEdgeEventServiceTest extends AbstractServiceTest {
Assert.assertTrue(edgeEventsWithoutTsUpdate.getData().isEmpty()); Assert.assertTrue(edgeEventsWithoutTsUpdate.getData().isEmpty());
} }
private EdgeEvent saveEdgeEventWithProvidedTime(long time, EdgeId edgeId, EntityId entityId, TenantId tenantId) throws Exception { private ListenableFuture<Void> saveEdgeEventWithProvidedTime(long time, EdgeId edgeId, EntityId entityId, TenantId tenantId) throws Exception {
EdgeEvent edgeEvent = generateEdgeEvent(tenantId, edgeId, entityId, EdgeEventActionType.ADDED); EdgeEvent edgeEvent = generateEdgeEvent(tenantId, edgeId, entityId, EdgeEventActionType.ADDED);
edgeEvent.setId(new EdgeEventId(Uuids.startOf(time))); edgeEvent.setId(new EdgeEventId(Uuids.startOf(time)));
return edgeEventService.save(edgeEvent); return edgeEventService.saveAsync(edgeEvent);
} }
} }

72
dao/src/test/java/org/thingsboard/server/dao/service/install/sql/EntitiesSchemaSqlTest.java

@ -0,0 +1,72 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.service.install.sql;
import org.assertj.core.api.Assertions;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.jdbc.core.JdbcTemplate;
import org.thingsboard.server.dao.service.AbstractServiceTest;
import org.thingsboard.server.dao.service.DaoSqlTest;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
@DaoSqlTest
public class EntitiesSchemaSqlTest extends AbstractServiceTest {
@Value("${classpath:sql/schema-entities.sql}")
private Path installScriptPath;
@Autowired
private JdbcTemplate jdbcTemplate;
@Test
public void testRepeatedInstall() throws IOException {
String installScript = Files.readString(installScriptPath);
try {
for (int i = 1; i <= 2; i++) {
jdbcTemplate.execute(installScript);
}
} catch (Exception e) {
Assertions.fail("Failed to execute reinstall", e);
}
}
@Test
public void testRepeatedInstall_badScript() {
String illegalInstallScript = "CREATE TABLE IF NOT EXISTS qwerty ();\n" +
"ALTER TABLE qwerty ADD COLUMN first VARCHAR(10);";
assertDoesNotThrow(() -> {
jdbcTemplate.execute(illegalInstallScript);
});
try {
assertThatThrownBy(() -> {
jdbcTemplate.execute(illegalInstallScript);
}).getCause().hasMessageContaining("column").hasMessageContaining("already exists");
} finally {
jdbcTemplate.execute("DROP TABLE qwerty;");
}
}
}

154
dao/src/test/java/org/thingsboard/server/dao/sql/query/DefaultQueryLogComponentTest.java

@ -0,0 +1,154 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.sql.query;
import org.junit.Before;
import org.junit.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.runner.RunWith;
import org.mockito.BDDMockito;
import org.mockito.Mockito;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.junit4.SpringRunner;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.TenantId;
import java.util.List;
import java.util.UUID;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.times;
@RunWith(SpringRunner.class)
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = DefaultQueryLogComponent.class)
@EnableConfigurationProperties
@TestPropertySource(properties = {
"sql.log_queries=true",
"sql.log_queries_threshold:2999"
})
public class DefaultQueryLogComponentTest {
private TenantId tenantId;
private QueryContext ctx;
@SpyBean
private DefaultQueryLogComponent queryLog;
@Before
public void setUp() {
tenantId = new TenantId(UUID.fromString("97275c1c-9cf2-4d25-a68d-933031158f84"));
ctx = new QueryContext(new QuerySecurityContext(tenantId, null, EntityType.ALARM));
}
@Test
public void logQuery() {
BDDMockito.willReturn("").given(queryLog).substituteParametersInSqlString("", ctx);
queryLog.logQuery(ctx, "", 3000);
Mockito.verify(queryLog, times(1)).substituteParametersInSqlString("", ctx);
}
@Test
public void substituteParametersInSqlString_StringType() {
String sql = "Select * from Table Where name = :name AND id = :id";
String sqlToUse = "Select * from Table Where name = 'Mery''s' AND id = 'ID_1'";
ctx.addStringParameter("name", "Mery's");
ctx.addStringParameter("id", "ID_1");
String sqlToUseResult = queryLog.substituteParametersInSqlString(sql, ctx);
assertEquals(sqlToUse, sqlToUseResult);
}
@Test
public void substituteParametersInSqlString_DoubleLongType() {
double sum = 0.00000021d;
long price = 100000;
String sql = "Select * from Table Where sum = :sum AND price = :price";
String sqlToUse = "Select * from Table Where sum = 2.1E-7 AND price = 100000";
ctx.addDoubleParameter("sum", sum);
ctx.addLongParameter("price", price);
String sqlToUseResult = queryLog.substituteParametersInSqlString(sql, ctx);
assertEquals(sqlToUse, sqlToUseResult);
}
@Test
public void substituteParametersInSqlString_BooleanType() {
String sql = "Select * from Table Where check = :check AND mark = :mark";
String sqlToUse = "Select * from Table Where check = true AND mark = false";
ctx.addBooleanParameter("check", true);
ctx.addBooleanParameter("mark", false);
String sqlToUseResult = queryLog.substituteParametersInSqlString(sql, ctx);
assertEquals(sqlToUse, sqlToUseResult);
}
@Test
public void substituteParametersInSqlString_UuidType() {
UUID guid = UUID.randomUUID();
String sql = "Select * from Table Where guid = :guid";
String sqlToUse = "Select * from Table Where guid = '" + guid + "'";
ctx.addUuidParameter("guid", guid);
String sqlToUseResult = queryLog.substituteParametersInSqlString(sql, ctx);
assertEquals(sqlToUse, sqlToUseResult);
}
@Test
public void substituteParametersInSqlString_StringListType() {
List<String> ids = List.of("ID_1'", "ID_2", "ID_3", "ID_4");
String sql = "Select * from Table Where id IN (:ids)";
String sqlToUse = "Select * from Table Where id IN ('ID_1''', 'ID_2', 'ID_3', 'ID_4')";
ctx.addStringListParameter("ids", ids);
String sqlToUseResult = queryLog.substituteParametersInSqlString(sql, ctx);
assertEquals(sqlToUse, sqlToUseResult);
}
@Test
public void substituteParametersInSqlString_UuidListType() {
List<UUID> guids = List.of(UUID.fromString("634a8d03-6871-4e01-94d0-876bf3e67dff"), UUID.fromString("3adbb5b8-4dc6-4faf-80dc-681a7b518b5e"), UUID.fromString("63a50f0c-2058-4d1d-8f15-812eb7f84412"));
String sql = "Select * from Table Where guid IN (:guids)";
String sqlToUse = "Select * from Table Where guid IN ('634a8d03-6871-4e01-94d0-876bf3e67dff', '3adbb5b8-4dc6-4faf-80dc-681a7b518b5e', '63a50f0c-2058-4d1d-8f15-812eb7f84412')";
ctx.addUuidListParameter("guids", guids);
String sqlToUseResult = queryLog.substituteParametersInSqlString(sql, ctx);
assertEquals(sqlToUse, sqlToUseResult);
}
}

178
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/AbstractTbMsgPushNode.java

@ -0,0 +1,178 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.rule.engine.edge;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNode;
import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.session.SessionMsgType;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@Slf4j
public abstract class AbstractTbMsgPushNode<T extends BaseTbMsgPushNodeConfiguration, S, U> implements TbNode {
protected T config;
private static final String SCOPE = "scope";
@Override
public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
this.config = TbNodeUtils.convert(configuration, getConfigClazz());
}
@Override
public void onMsg(TbContext ctx, TbMsg msg) {
if (getIgnoredMessageSource().equalsIgnoreCase(msg.getMetaData().getValue(DataConstants.MSG_SOURCE_KEY))) {
log.debug("Ignoring msg from the {}, msg [{}]", getIgnoredMessageSource(), msg);
ctx.ack(msg);
return;
}
if (isSupportedOriginator(msg.getOriginator().getEntityType())) {
if (isSupportedMsgType(msg.getType())) {
processMsg(ctx, msg);
} else {
String errMsg = String.format("Unsupported msg type %s", msg.getType());
log.debug(errMsg);
ctx.tellFailure(msg, new RuntimeException(errMsg));
}
} else {
String errMsg = String.format("Unsupported originator type %s", msg.getOriginator().getEntityType());
log.debug(errMsg);
ctx.tellFailure(msg, new RuntimeException(errMsg));
}
}
@Override
public void destroy() {
}
protected S buildEvent(TbMsg msg, TbContext ctx) {
String msgType = msg.getType();
if (DataConstants.ALARM.equals(msgType)) {
return buildEvent(ctx.getTenantId(), EdgeEventActionType.ADDED, getUUIDFromMsgData(msg), getAlarmEventType(), null);
} else {
EdgeEventActionType actionType = getEdgeEventActionTypeByMsgType(msgType);
Map<String, Object> entityBody = new HashMap<>();
Map<String, String> metadata = msg.getMetaData().getData();
JsonNode dataJson = JacksonUtil.toJsonNode(msg.getData());
switch (actionType) {
case ATTRIBUTES_UPDATED:
case POST_ATTRIBUTES:
entityBody.put("kv", dataJson);
entityBody.put(SCOPE, getScope(metadata));
if (EdgeEventActionType.POST_ATTRIBUTES.equals(actionType)) {
entityBody.put("isPostAttributes", true);
}
break;
case ATTRIBUTES_DELETED:
List<String> keys = JacksonUtil.convertValue(dataJson.get("attributes"), new TypeReference<>() {});
entityBody.put("keys", keys);
entityBody.put(SCOPE, getScope(metadata));
break;
case TIMESERIES_UPDATED:
entityBody.put("data", dataJson);
entityBody.put("ts", msg.getMetaDataTs());
break;
}
return buildEvent(ctx.getTenantId(),
actionType,
msg.getOriginator().getId(),
getEventTypeByEntityType(msg.getOriginator().getEntityType()),
JacksonUtil.valueToTree(entityBody));
}
}
abstract S buildEvent(TenantId tenantId, EdgeEventActionType eventAction, UUID entityId, U eventType, JsonNode entityBody);
abstract U getEventTypeByEntityType(EntityType entityType);
abstract U getAlarmEventType();
abstract String getIgnoredMessageSource();
abstract protected Class<T> getConfigClazz();
abstract void processMsg(TbContext ctx, TbMsg msg);
protected UUID getUUIDFromMsgData(TbMsg msg) {
JsonNode data = JacksonUtil.toJsonNode(msg.getData()).get("id");
String id = JacksonUtil.convertValue(data.get("id"), String.class);
return UUID.fromString(id);
}
protected String getScope(Map<String, String> metadata) {
String scope = metadata.get(SCOPE);
if (StringUtils.isEmpty(scope)) {
scope = config.getScope();
}
return scope;
}
protected EdgeEventActionType getEdgeEventActionTypeByMsgType(String msgType) {
EdgeEventActionType actionType;
if (SessionMsgType.POST_TELEMETRY_REQUEST.name().equals(msgType)
|| DataConstants.TIMESERIES_UPDATED.equals(msgType)) {
actionType = EdgeEventActionType.TIMESERIES_UPDATED;
} else if (DataConstants.ATTRIBUTES_UPDATED.equals(msgType)) {
actionType = EdgeEventActionType.ATTRIBUTES_UPDATED;
} else if (SessionMsgType.POST_ATTRIBUTES_REQUEST.name().equals(msgType)) {
actionType = EdgeEventActionType.POST_ATTRIBUTES;
} else {
actionType = EdgeEventActionType.ATTRIBUTES_DELETED;
}
return actionType;
}
protected boolean isSupportedMsgType(String msgType) {
return SessionMsgType.POST_TELEMETRY_REQUEST.name().equals(msgType)
|| SessionMsgType.POST_ATTRIBUTES_REQUEST.name().equals(msgType)
|| DataConstants.ATTRIBUTES_UPDATED.equals(msgType)
|| DataConstants.ATTRIBUTES_DELETED.equals(msgType)
|| DataConstants.TIMESERIES_UPDATED.equals(msgType)
|| DataConstants.ALARM.equals(msgType);
}
protected boolean isSupportedOriginator(EntityType entityType) {
switch (entityType) {
case DEVICE:
case ASSET:
case ENTITY_VIEW:
case DASHBOARD:
case TENANT:
case CUSTOMER:
case EDGE:
return true;
default:
return false;
}
}
}

33
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/BaseTbMsgPushNodeConfiguration.java

@ -0,0 +1,33 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.rule.engine.edge;
import lombok.Data;
import org.thingsboard.rule.engine.api.NodeConfiguration;
import org.thingsboard.server.common.data.DataConstants;
@Data
public class BaseTbMsgPushNodeConfiguration implements NodeConfiguration<BaseTbMsgPushNodeConfiguration> {
private String scope;
@Override
public BaseTbMsgPushNodeConfiguration defaultConfiguration() {
BaseTbMsgPushNodeConfiguration configuration = new BaseTbMsgPushNodeConfiguration();
configuration.setScope(DataConstants.SERVER_SCOPE);
return configuration;
}
}

39
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/TbMsgPushToCloudNode.java

@ -15,17 +15,19 @@
*/ */
package org.thingsboard.rule.engine.edge; package org.thingsboard.rule.engine.edge;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.RuleNode;
import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import java.util.UUID;
@Slf4j @Slf4j
@RuleNode( @RuleNode(
type = ComponentType.ACTION, type = ComponentType.ACTION,
@ -57,22 +59,37 @@ import org.thingsboard.server.common.msg.TbMsg;
icon = "cloud_upload", icon = "cloud_upload",
ruleChainTypes = RuleChainType.EDGE ruleChainTypes = RuleChainType.EDGE
) )
public class TbMsgPushToCloudNode implements TbNode { public class TbMsgPushToCloudNode extends AbstractTbMsgPushNode<TbMsgPushToCloudNodeConfiguration, Object, Object> {
// Implementation of this node is done on the Edge
@Override
Object buildEvent(TenantId tenantId, EdgeEventActionType eventAction, UUID entityId, Object eventType, JsonNode entityBody) {
return null;
}
private TbMsgPushToCloudNodeConfiguration config; @Override
Object getEventTypeByEntityType(EntityType entityType) {
return null;
}
@Override
Object getAlarmEventType() {
return null;
}
@Override @Override
public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { String getIgnoredMessageSource() {
this.config = TbNodeUtils.convert(configuration, TbMsgPushToCloudNodeConfiguration.class); return null;
} }
@Override @Override
public void onMsg(TbContext ctx, TbMsg msg) { protected Class<TbMsgPushToCloudNodeConfiguration> getConfigClazz() {
// Implementation of this node is done on the Edge return TbMsgPushToCloudNodeConfiguration.class;
} }
@Override @Override
public void destroy() { void processMsg(TbContext ctx, TbMsg msg) {
} }
} }

7
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/TbMsgPushToCloudNodeConfiguration.java

@ -16,13 +16,12 @@
package org.thingsboard.rule.engine.edge; package org.thingsboard.rule.engine.edge;
import lombok.Data; import lombok.Data;
import org.thingsboard.rule.engine.api.NodeConfiguration; import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
@EqualsAndHashCode(callSuper = true)
@Data @Data
public class TbMsgPushToCloudNodeConfiguration implements NodeConfiguration<TbMsgPushToCloudNodeConfiguration> { public class TbMsgPushToCloudNodeConfiguration extends BaseTbMsgPushNodeConfiguration {
private String scope;
@Override @Override
public TbMsgPushToCloudNodeConfiguration defaultConfiguration() { public TbMsgPushToCloudNodeConfiguration defaultConfiguration() {

246
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNode.java

@ -15,23 +15,17 @@
*/ */
package org.thingsboard.rule.engine.edge; package org.thingsboard.rule.engine.edge;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.common.util.JacksonUtil; import org.checkerframework.checker.nullness.qual.Nullable;
import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.RuleNode;
import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNode;
import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.edge.EdgeEventType;
@ -42,16 +36,11 @@ import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.session.SessionMsgType;
import javax.annotation.Nullable; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.UUID; import java.util.UUID;
import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS;
@Slf4j @Slf4j
@RuleNode( @RuleNode(
type = ComponentType.ACTION, type = ComponentType.ACTION,
@ -84,173 +73,108 @@ import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS;
icon = "cloud_download", icon = "cloud_download",
ruleChainTypes = RuleChainType.CORE ruleChainTypes = RuleChainType.CORE
) )
public class TbMsgPushToEdgeNode implements TbNode { public class TbMsgPushToEdgeNode extends AbstractTbMsgPushNode<TbMsgPushToEdgeNodeConfiguration, EdgeEvent, EdgeEventType> {
private TbMsgPushToEdgeNodeConfiguration config;
private static final String SCOPE = "scope"; static final int DEFAULT_PAGE_SIZE = 100;
private static final int DEFAULT_PAGE_SIZE = 1000;
@Override @Override
public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { EdgeEvent buildEvent(TenantId tenantId, EdgeEventActionType eventAction, UUID entityId,
this.config = TbNodeUtils.convert(configuration, TbMsgPushToEdgeNodeConfiguration.class); EdgeEventType eventType, JsonNode entityBody) {
}
@Override
public void onMsg(TbContext ctx, TbMsg msg) {
if (DataConstants.EDGE_MSG_SOURCE.equalsIgnoreCase(msg.getMetaData().getValue(DataConstants.MSG_SOURCE_KEY))) {
log.debug("Ignoring msg from the cloud, msg [{}]", msg);
ctx.ack(msg);
return;
}
if (isSupportedOriginator(msg.getOriginator().getEntityType())) {
if (isSupportedMsgType(msg.getType())) {
processMsg(ctx, msg);
} else {
log.debug("Unsupported msg type {}", msg.getType());
ctx.tellFailure(msg, new RuntimeException("Unsupported msg type '" + msg.getType() + "'"));
}
} else {
log.debug("Unsupported originator type {}", msg.getOriginator().getEntityType());
ctx.tellFailure(msg, new RuntimeException("Unsupported originator type '" + msg.getOriginator().getEntityType() + "'"));
}
}
private void processMsg(TbContext ctx, TbMsg msg) {
if (EntityType.EDGE.equals(msg.getOriginator().getEntityType())) {
EdgeEvent edgeEvent = buildEdgeEvent(msg, ctx);
if (edgeEvent != null) {
EdgeId edgeId = new EdgeId(msg.getOriginator().getId());
notifyEdge(ctx, msg, edgeEvent, edgeId);
}
} else {
PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE);
PageData<EdgeId> pageData;
do {
pageData = ctx.getEdgeService().findRelatedEdgeIdsByEntityId(ctx.getTenantId(), msg.getOriginator(), pageLink);
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
for (EdgeId edgeId : pageData.getData()) {
EdgeEvent edgeEvent = buildEdgeEvent(msg, ctx);
if (edgeEvent == null) {
log.debug("Edge event type is null. Entity Type {}", msg.getOriginator().getEntityType());
ctx.tellFailure(msg, new RuntimeException("Edge event type is null. Entity Type '" + msg.getOriginator().getEntityType() + "'"));
} else {
notifyEdge(ctx, msg, edgeEvent, edgeId);
}
}
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
}
} while (pageData != null && pageData.hasNext());
}
}
private void notifyEdge(TbContext ctx, TbMsg msg, EdgeEvent edgeEvent, EdgeId edgeId) {
edgeEvent.setEdgeId(edgeId);
ctx.getEdgeEventService().save(edgeEvent);
ctx.tellNext(msg, SUCCESS);
ctx.onEdgeEventUpdate(ctx.getTenantId(), edgeId);
}
private EdgeEvent buildEdgeEvent(TbMsg msg, TbContext ctx) {
String msgType = msg.getType();
if (DataConstants.ALARM.equals(msgType)) {
return buildEdgeEvent(ctx.getTenantId(), EdgeEventActionType.ADDED, getUUIDFromMsgData(msg), EdgeEventType.ALARM, null);
} else {
EdgeEventType edgeEventTypeByEntityType = EdgeUtils.getEdgeEventTypeByEntityType(msg.getOriginator().getEntityType());
if (edgeEventTypeByEntityType == null) {
return null;
}
EdgeEventActionType actionType = getEdgeEventActionTypeByMsgType(msgType);
Map<String, Object> entityBody = new HashMap<>();
Map<String, String> metadata = msg.getMetaData().getData();
JsonNode dataJson = JacksonUtil.toJsonNode(msg.getData());
switch (actionType) {
case ATTRIBUTES_UPDATED:
case POST_ATTRIBUTES:
entityBody.put("kv", dataJson);
entityBody.put(SCOPE, getScope(metadata));
break;
case ATTRIBUTES_DELETED:
List<String> keys = JacksonUtil.convertValue(dataJson.get("attributes"), new TypeReference<>() {
});
entityBody.put("keys", keys);
entityBody.put(SCOPE, getScope(metadata));
break;
case TIMESERIES_UPDATED:
entityBody.put("data", dataJson);
entityBody.put("ts", metadata.get("ts"));
break;
}
return buildEdgeEvent(ctx.getTenantId(), actionType, msg.getOriginator().getId(), edgeEventTypeByEntityType, JacksonUtil.valueToTree(entityBody));
}
}
private String getScope(Map<String, String> metadata) {
String scope = metadata.get(SCOPE);
if (StringUtils.isEmpty(scope)) {
scope = config.getScope();
}
return scope;
}
private EdgeEvent buildEdgeEvent(TenantId tenantId, EdgeEventActionType edgeEventAction, UUID entityId, EdgeEventType edgeEventType, JsonNode entityBody) {
EdgeEvent edgeEvent = new EdgeEvent(); EdgeEvent edgeEvent = new EdgeEvent();
edgeEvent.setTenantId(tenantId); edgeEvent.setTenantId(tenantId);
edgeEvent.setAction(edgeEventAction); edgeEvent.setAction(eventAction);
edgeEvent.setEntityId(entityId); edgeEvent.setEntityId(entityId);
edgeEvent.setType(edgeEventType); edgeEvent.setType(eventType);
edgeEvent.setBody(entityBody); edgeEvent.setBody(entityBody);
return edgeEvent; return edgeEvent;
} }
private UUID getUUIDFromMsgData(TbMsg msg) { @Override
JsonNode data = JacksonUtil.toJsonNode(msg.getData()).get("id"); EdgeEventType getEventTypeByEntityType(EntityType entityType) {
String id = JacksonUtil.convertValue(data.get("id"), String.class); return EdgeUtils.getEdgeEventTypeByEntityType(entityType);
return UUID.fromString(id);
} }
private EdgeEventActionType getEdgeEventActionTypeByMsgType(String msgType) { @Override
EdgeEventActionType actionType; EdgeEventType getAlarmEventType() {
if (SessionMsgType.POST_TELEMETRY_REQUEST.name().equals(msgType)) { return EdgeEventType.ALARM;
actionType = EdgeEventActionType.TIMESERIES_UPDATED;
} else if (DataConstants.ATTRIBUTES_UPDATED.equals(msgType)) {
actionType = EdgeEventActionType.ATTRIBUTES_UPDATED;
} else if (SessionMsgType.POST_ATTRIBUTES_REQUEST.name().equals(msgType)) {
actionType = EdgeEventActionType.POST_ATTRIBUTES;
} else {
actionType = EdgeEventActionType.ATTRIBUTES_DELETED;
}
return actionType;
} }
private boolean isSupportedOriginator(EntityType entityType) { @Override
switch (entityType) { String getIgnoredMessageSource() {
case DEVICE: return DataConstants.EDGE_MSG_SOURCE;
case ASSET:
case ENTITY_VIEW:
case DASHBOARD:
case TENANT:
case CUSTOMER:
case EDGE:
return true;
default:
return false;
}
} }
private boolean isSupportedMsgType(String msgType) { @Override
return SessionMsgType.POST_TELEMETRY_REQUEST.name().equals(msgType) protected Class<TbMsgPushToEdgeNodeConfiguration> getConfigClazz() {
|| SessionMsgType.POST_ATTRIBUTES_REQUEST.name().equals(msgType) return TbMsgPushToEdgeNodeConfiguration.class;
|| DataConstants.ATTRIBUTES_UPDATED.equals(msgType)
|| DataConstants.ATTRIBUTES_DELETED.equals(msgType)
|| DataConstants.ALARM.equals(msgType);
} }
@Override @Override
public void destroy() { protected void processMsg(TbContext ctx, TbMsg msg) {
try {
if (EntityType.EDGE.equals(msg.getOriginator().getEntityType())) {
EdgeEvent edgeEvent = buildEvent(msg, ctx);
EdgeId edgeId = new EdgeId(msg.getOriginator().getId());
ListenableFuture<Void> future = notifyEdge(ctx, edgeEvent, edgeId);
FutureCallback<Void> futureCallback = new FutureCallback<>() {
@Override
public void onSuccess(@Nullable Void result) {
ctx.tellSuccess(msg);
}
@Override
public void onFailure(Throwable t) {
ctx.tellFailure(msg, t);
}
};
Futures.addCallback(future, futureCallback, ctx.getDbCallbackExecutor());
} else {
PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE);
PageData<EdgeId> pageData;
List<ListenableFuture<Void>> futures = new ArrayList<>();
do {
pageData = ctx.getEdgeService().findRelatedEdgeIdsByEntityId(ctx.getTenantId(), msg.getOriginator(), pageLink);
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
for (EdgeId edgeId : pageData.getData()) {
EdgeEvent edgeEvent = buildEvent(msg, ctx);
futures.add(notifyEdge(ctx, edgeEvent, edgeId));
}
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
}
} while (pageData != null && pageData.hasNext());
if (futures.isEmpty()) {
// ack in case no edges are related to provided entity
ctx.ack(msg);
} else {
Futures.addCallback(Futures.allAsList(futures), new FutureCallback<>() {
@Override
public void onSuccess(@Nullable List<Void> voids) {
ctx.tellSuccess(msg);
}
@Override
public void onFailure(Throwable t) {
ctx.tellFailure(msg, t);
}
}, ctx.getDbCallbackExecutor());
}
}
} catch (Exception e) {
log.error("Failed to build edge event", e);
ctx.tellFailure(msg, e);
}
}
private ListenableFuture<Void> notifyEdge(TbContext ctx, EdgeEvent edgeEvent, EdgeId edgeId) {
edgeEvent.setEdgeId(edgeId);
ListenableFuture<Void> future = ctx.getEdgeEventService().saveAsync(edgeEvent);
return Futures.transform(future, result -> {
ctx.onEdgeEventUpdate(ctx.getTenantId(), edgeId);
return null;
}, ctx.getDbCallbackExecutor());
} }
} }

7
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNodeConfiguration.java

@ -16,13 +16,12 @@
package org.thingsboard.rule.engine.edge; package org.thingsboard.rule.engine.edge;
import lombok.Data; import lombok.Data;
import org.thingsboard.rule.engine.api.NodeConfiguration; import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
@EqualsAndHashCode(callSuper = true)
@Data @Data
public class TbMsgPushToEdgeNodeConfiguration implements NodeConfiguration<TbMsgPushToEdgeNodeConfiguration> { public class TbMsgPushToEdgeNodeConfiguration extends BaseTbMsgPushNodeConfiguration {
private String scope;
@Override @Override
public TbMsgPushToEdgeNodeConfiguration defaultConfiguration() { public TbMsgPushToEdgeNodeConfiguration defaultConfiguration() {

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java

@ -158,7 +158,7 @@ public class TbKafkaNode implements TbNode {
} }
private void processRecord(TbContext ctx, TbMsg msg, RecordMetadata metadata, Exception e) { private void processRecord(TbContext ctx, TbMsg msg, RecordMetadata metadata, Exception e) {
if (metadata != null) { if (e == null) {
TbMsg next = processResponse(ctx, msg, metadata); TbMsg next = processResponse(ctx, msg, metadata);
ctx.tellNext(next, TbRelationTypes.SUCCESS); ctx.tellNext(next, TbRelationTypes.SUCCESS);
} else { } else {

76
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNodeTest.java

@ -0,0 +1,76 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.rule.engine.edge;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.server.common.data.id.DeviceId;
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.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgDataType;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.msg.session.SessionMsgType;
import org.thingsboard.server.dao.edge.EdgeService;
import java.util.UUID;
import static org.mockito.Mockito.verify;
@RunWith(MockitoJUnitRunner.class)
public class TbMsgPushToEdgeNodeTest {
TbMsgPushToEdgeNode node;
private final TenantId tenantId = TenantId.fromUUID(UUID.randomUUID());
private final DeviceId deviceId = new DeviceId(UUID.randomUUID());
@Mock
private TbContext ctx;
@Mock
private EdgeService edgeService;
@Before
public void setUp() throws TbNodeException {
node = new TbMsgPushToEdgeNode();
TbMsgPushToEdgeNodeConfiguration config = new TbMsgPushToEdgeNodeConfiguration().defaultConfiguration();
node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config)));
}
@Test
public void ackMsgInCaseNoEdgeRelated() {
Mockito.when(ctx.getTenantId()).thenReturn(tenantId);
Mockito.when(ctx.getEdgeService()).thenReturn(edgeService);
Mockito.when(edgeService.findRelatedEdgeIdsByEntityId(tenantId, deviceId, new PageLink(TbMsgPushToEdgeNode.DEFAULT_PAGE_SIZE))).thenReturn(new PageData<>());
TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(),
TbMsgDataType.JSON, "{}", null, null);
node.onMsg(ctx, msg);
verify(ctx).ack(msg);
}
}

1
ui-ngx/.yarnrc

@ -0,0 +1 @@
network-timeout 1000000

11
ui-ngx/generate-types.js

@ -17,9 +17,12 @@
const child_process = require("child_process"); const child_process = require("child_process");
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
const typeDir = './target/types';
const srcDir = typeDir + '/src'; const typeDir = path.join('.', 'target', 'types');
const moduleMapPath = "src/app/modules/common/modules-map.ts"; const srcDir = path.join('.', 'target', 'types', 'src');
const moduleMapPath = path.join('src', 'app', 'modules', 'common', 'modules-map.ts');
const ngcPath = path.join('.', 'node_modules', '.bin', 'ngc');
const tsconfigPath = path.join('src', 'tsconfig.app.json');
console.log(`Remove directory: ${typeDir}`); console.log(`Remove directory: ${typeDir}`);
try { try {
@ -28,7 +31,7 @@ try {
console.error(`Remove directory error: ${err}`); console.error(`Remove directory error: ${err}`);
} }
const cliCommand = `./node_modules/.bin/ngc --p src/tsconfig.app.json --declaration --outDir ${srcDir}`; const cliCommand = `${ngcPath} --p ${tsconfigPath} --declaration --outDir ${srcDir}`;
console.log(cliCommand); console.log(cliCommand);
try { try {
child_process.execSync(cliCommand); child_process.execSync(cliCommand);

6
ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html

@ -134,4 +134,10 @@
</div> </div>
</fieldset> </fieldset>
</section> </section>
<div style="padding-bottom: 20px">
<mat-checkbox formControlName="sendAckOnValidationException">
{{ 'device-profile.mqtt-send-ack-on-validation-exception' | translate }}
</mat-checkbox>
<div class="tb-hint" innerHTML="{{ 'device-profile.mqtt-send-ack-on-validation-exception-hint' | translate }}"></div>
</div>
</form> </form>

1
ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts

@ -92,6 +92,7 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control
this.mqttDeviceProfileTransportConfigurationFormGroup = this.fb.group({ this.mqttDeviceProfileTransportConfigurationFormGroup = this.fb.group({
deviceAttributesTopic: [null, [Validators.required, this.validationMQTTTopic()]], deviceAttributesTopic: [null, [Validators.required, this.validationMQTTTopic()]],
deviceTelemetryTopic: [null, [Validators.required, this.validationMQTTTopic()]], deviceTelemetryTopic: [null, [Validators.required, this.validationMQTTTopic()]],
sendAckOnValidationException: [false, Validators.required],
transportPayloadTypeConfiguration: this.fb.group({ transportPayloadTypeConfiguration: this.fb.group({
transportPayloadType: [TransportPayloadType.JSON, Validators.required], transportPayloadType: [TransportPayloadType.JSON, Validators.required],
deviceTelemetryProtoSchema: [defaultTelemetrySchema, Validators.required], deviceTelemetryProtoSchema: [defaultTelemetrySchema, Validators.required],

2
ui-ngx/src/app/shared/models/device.models.ts

@ -242,6 +242,7 @@ export interface DefaultDeviceProfileTransportConfiguration {
export interface MqttDeviceProfileTransportConfiguration { export interface MqttDeviceProfileTransportConfiguration {
deviceTelemetryTopic?: string; deviceTelemetryTopic?: string;
deviceAttributesTopic?: string; deviceAttributesTopic?: string;
sendAckOnValidationException?: boolean;
transportPayloadTypeConfiguration?: { transportPayloadTypeConfiguration?: {
transportPayloadType?: TransportPayloadType; transportPayloadType?: TransportPayloadType;
enableCompatibilityWithJsonPayloadFormat?: boolean; enableCompatibilityWithJsonPayloadFormat?: boolean;
@ -358,6 +359,7 @@ export function createDeviceProfileTransportConfiguration(type: DeviceTransportT
const mqttTransportConfiguration: MqttDeviceProfileTransportConfiguration = { const mqttTransportConfiguration: MqttDeviceProfileTransportConfiguration = {
deviceTelemetryTopic: 'v1/devices/me/telemetry', deviceTelemetryTopic: 'v1/devices/me/telemetry',
deviceAttributesTopic: 'v1/devices/me/attributes', deviceAttributesTopic: 'v1/devices/me/attributes',
sendAckOnValidationException: false,
transportPayloadTypeConfiguration: { transportPayloadTypeConfiguration: {
transportPayloadType: TransportPayloadType.JSON, transportPayloadType: TransportPayloadType.JSON,
enableCompatibilityWithJsonPayloadFormat: false, enableCompatibilityWithJsonPayloadFormat: false,

2
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -1148,6 +1148,8 @@
"mqtt-enable-compatibility-with-json-payload-format-hint": "When enabled, the platform will use a Protobuf payload format by default. If parsing fails, the platform will attempt to use JSON payload format. Useful for backward compatibility during firmware updates. For example, the initial release of the firmware uses Json, while the new release uses Protobuf. During the process of firmware update for the fleet of devices, it is required to support both Protobuf and JSON simultaneously. The compatibility mode introduces slight performance degradation, so it is recommended to disable this mode once all devices are updated.", "mqtt-enable-compatibility-with-json-payload-format-hint": "When enabled, the platform will use a Protobuf payload format by default. If parsing fails, the platform will attempt to use JSON payload format. Useful for backward compatibility during firmware updates. For example, the initial release of the firmware uses Json, while the new release uses Protobuf. During the process of firmware update for the fleet of devices, it is required to support both Protobuf and JSON simultaneously. The compatibility mode introduces slight performance degradation, so it is recommended to disable this mode once all devices are updated.",
"mqtt-use-json-format-for-default-downlink-topics": "Use Json format for default downlink topics", "mqtt-use-json-format-for-default-downlink-topics": "Use Json format for default downlink topics",
"mqtt-use-json-format-for-default-downlink-topics-hint": "When enabled, the platform will use Json payload format to push attributes and RPC via the following topics: <b>v1/devices/me/attributes/response/$request_id</b>, <b>v1/devices/me/attributes</b>, <b>v1/devices/me/rpc/request/$request_id</b>, <b>v1/devices/me/rpc/response/$request_id</b>. This setting does not impact attribute and rpc subscriptions sent using new (v2) topics: <b>v2/a/res/$request_id</b>, <b>v2/a</b>, <b>v2/r/req/$request_id</b>, <b>v2/r/res/$request_id</b>. Where <b>$request_id</b> is an integer request identifier.", "mqtt-use-json-format-for-default-downlink-topics-hint": "When enabled, the platform will use Json payload format to push attributes and RPC via the following topics: <b>v1/devices/me/attributes/response/$request_id</b>, <b>v1/devices/me/attributes</b>, <b>v1/devices/me/rpc/request/$request_id</b>, <b>v1/devices/me/rpc/response/$request_id</b>. This setting does not impact attribute and rpc subscriptions sent using new (v2) topics: <b>v2/a/res/$request_id</b>, <b>v2/a</b>, <b>v2/r/req/$request_id</b>, <b>v2/r/res/$request_id</b>. Where <b>$request_id</b> is an integer request identifier.",
"mqtt-send-ack-on-validation-exception": "Send PUBACK on PUBLISH message validation failure",
"mqtt-send-ack-on-validation-exception-hint": "By default, the platform will close the MQTT session on message validation failure. When enabled, the platform will send publish acknowledgment instead of closing the session.",
"snmp-add-mapping": "Add SNMP mapping", "snmp-add-mapping": "Add SNMP mapping",
"snmp-mapping-not-configured": "No mapping for OID to timeseries/telemetry configured", "snmp-mapping-not-configured": "No mapping for OID to timeseries/telemetry configured",
"snmp-timseries-or-attribute-name": "Timeseries/attribute name for mapping", "snmp-timseries-or-attribute-name": "Timeseries/attribute name for mapping",

Loading…
Cancel
Save