Browse Source

merge with develop/3.4

pull/6534/head
YevhenBondarenko 4 years ago
parent
commit
6c1031536d
  1. 6
      application/src/main/java/org/thingsboard/server/controller/CustomerController.java
  2. 378
      application/src/main/java/org/thingsboard/server/controller/DashboardController.java
  3. 2
      application/src/main/java/org/thingsboard/server/controller/DeviceController.java
  4. 4
      application/src/main/java/org/thingsboard/server/controller/EdgeController.java
  5. 2
      application/src/main/java/org/thingsboard/server/service/edge/EdgeBulkImportService.java
  6. 3
      application/src/main/java/org/thingsboard/server/service/entitiy/AbstractTbEntityService.java
  7. 48
      application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java
  8. 4
      application/src/main/java/org/thingsboard/server/service/entitiy/SimpleTbEntityService.java
  9. 17
      application/src/main/java/org/thingsboard/server/service/entitiy/TbNotificationEntityService.java
  10. 14
      application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java
  11. 6
      application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java
  12. 6
      application/src/main/java/org/thingsboard/server/service/entitiy/asset/DefaultTbAssetService.java
  13. 5
      application/src/main/java/org/thingsboard/server/service/entitiy/asset/TbAssetService.java
  14. 18
      application/src/main/java/org/thingsboard/server/service/entitiy/customer/DefaultTbCustomerService.java
  15. 4
      application/src/main/java/org/thingsboard/server/service/entitiy/customer/TbCustomerService.java
  16. 275
      application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/DefaultTbDashboardService.java
  17. 49
      application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/TbDashboardService.java
  18. 2
      application/src/main/java/org/thingsboard/server/service/entitiy/device/DefaultTbDeviceService.java
  19. 2
      application/src/main/java/org/thingsboard/server/service/entitiy/device/TbDeviceService.java
  20. 8
      application/src/main/java/org/thingsboard/server/service/entitiy/edge/DefaultTbEdgeService.java
  21. 4
      application/src/main/java/org/thingsboard/server/service/entitiy/edge/TbEdgeService.java
  22. 73
      application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java
  23. 16
      application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractSubCtx.java
  24. 6
      application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java
  25. 7
      application/src/main/java/org/thingsboard/server/service/subscription/TbEntityCountSubCtx.java
  26. 8
      application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubCtx.java
  27. 6
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java
  28. 2
      application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java
  29. 53
      application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java
  30. 14
      application/src/test/java/org/thingsboard/server/controller/BaseWebsocketApiTest.java
  31. 16
      application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java
  32. 17
      application/src/test/java/org/thingsboard/server/transport/mqtt/AbstractMqttIntegrationTest.java
  33. 88
      application/src/test/java/org/thingsboard/server/transport/mqtt/MqttTestCallback.java
  34. 126
      application/src/test/java/org/thingsboard/server/transport/mqtt/MqttTestClient.java
  35. 653
      application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/AbstractMqttAttributesIntegrationTest.java
  36. 2
      application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/MqttAttributesRequestBackwardCompatibilityIntegrationTest.java
  37. 1
      application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/MqttAttributesRequestProtoIntegrationTest.java
  38. 18
      application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/MqttAttributesUpdatesBackwardCompatibilityIntegrationTest.java
  39. 11
      application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/MqttAttributesUpdatesIntegrationTest.java
  40. 11
      application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/MqttAttributesUpdatesJsonIntegrationTest.java
  41. 14
      application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/MqttAttributesUpdatesProtoIntegrationTest.java
  42. 31
      application/src/test/java/org/thingsboard/server/transport/mqtt/claim/MqttClaimDeviceTest.java
  43. 5
      application/src/test/java/org/thingsboard/server/transport/mqtt/claim/MqttClaimProtoDeviceTest.java
  44. 65
      application/src/test/java/org/thingsboard/server/transport/mqtt/credentials/BasicMqttCredentialsTest.java
  45. 149
      application/src/test/java/org/thingsboard/server/transport/mqtt/provision/MqttProvisionJsonDeviceTest.java
  46. 114
      application/src/test/java/org/thingsboard/server/transport/mqtt/provision/MqttProvisionProtoDeviceTest.java
  47. 396
      application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java
  48. 23
      application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/MqttServerSideRpcBackwardCompatibilityIntegrationTest.java
  49. 16
      application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/MqttServerSideRpcDefaultIntegrationTest.java
  50. 23
      application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/MqttServerSideRpcJsonIntegrationTest.java
  51. 17
      application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/MqttServerSideRpcProtoIntegrationTest.java
  52. 48
      application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/MqttAttributesIntegrationTest.java
  53. 10
      application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/MqttAttributesProtoIntegrationTest.java
  54. 172
      application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/AbstractMqttTimeseriesIntegrationTest.java
  55. 46
      application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/AbstractMqttTimeseriesJsonIntegrationTest.java
  56. 87
      application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java
  57. 5
      dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java
  58. 3
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttGatewayClientTest.java
  59. 10
      ui-ngx/src/app/core/api/alias-controller.ts
  60. 4
      ui-ngx/src/app/core/api/widget-api.models.ts
  61. 8
      ui-ngx/src/app/core/api/widget-subscription.ts
  62. 6
      ui-ngx/src/app/core/http/entity.service.ts
  63. 87
      ui-ngx/src/app/core/schema-utils.ts
  64. 19
      ui-ngx/src/app/modules/home/components/widget/lib/maps/common-maps-utils.ts
  65. 5
      ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget.interface.ts
  66. 60
      ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts
  67. 1
      ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts
  68. 1603
      ui-ngx/src/app/modules/home/components/widget/lib/maps/schemes.ts
  69. 6
      ui-ngx/src/app/modules/home/components/widget/lib/markdown-widget.component.ts
  70. 45
      ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.ts
  71. 8
      ui-ngx/src/app/modules/home/components/widget/widget-config.component.html
  72. 2
      ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts
  73. 3
      ui-ngx/src/app/modules/home/components/widget/widget.component.ts
  74. 1
      ui-ngx/src/app/shared/models/widget.models.ts
  75. 1
      ui-ngx/src/assets/locale/locale.constant-en_US.json

6
application/src/main/java/org/thingsboard/server/controller/CustomerController.java

@ -32,22 +32,16 @@ import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.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.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.customer.TbCustomerService; import org.thingsboard.server.service.entitiy.customer.TbCustomerService;
import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource; import org.thingsboard.server.service.security.permission.Resource;
import java.util.List;
import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_ID; import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_ID;
import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_ID_PARAM_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_ID_PARAM_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_SORT_PROPERTY_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_SORT_PROPERTY_ALLOWABLE_VALUES;

378
application/src/main/java/org/thingsboard/server/controller/DashboardController.java

@ -22,6 +22,7 @@ import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse; import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.Example; import io.swagger.annotations.Example;
import io.swagger.annotations.ExampleProperty; import io.swagger.annotations.ExampleProperty;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
@ -38,15 +39,12 @@ import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.DashboardInfo; import org.thingsboard.server.common.data.DashboardInfo;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.HomeDashboard; import org.thingsboard.server.common.data.HomeDashboard;
import org.thingsboard.server.common.data.HomeDashboardInfo; import org.thingsboard.server.common.data.HomeDashboardInfo;
import org.thingsboard.server.common.data.ShortCustomerInfo;
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.audit.ActionType;
import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.DashboardId;
@ -55,6 +53,7 @@ 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.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.dashboard.TbDashboardService;
import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource; import org.thingsboard.server.service.security.permission.Resource;
@ -62,6 +61,7 @@ import org.thingsboard.server.service.security.permission.Resource;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_ID; import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_ID;
@ -90,9 +90,11 @@ import static org.thingsboard.server.controller.ControllerConstants.UUID_WIKI_LI
@RestController @RestController
@TbCoreComponent @TbCoreComponent
@RequiredArgsConstructor
@RequestMapping("/api") @RequestMapping("/api")
public class DashboardController extends BaseController { public class DashboardController extends BaseController {
private final TbDashboardService tbDashboardService;
public static final String DASHBOARD_ID = "dashboardId"; public static final String DASHBOARD_ID = "dashboardId";
private static final String HOME_DASHBOARD_ID = "homeDashboardId"; private static final String HOME_DASHBOARD_ID = "homeDashboardId";
@ -180,28 +182,9 @@ public class DashboardController extends BaseController {
public Dashboard saveDashboard( public Dashboard saveDashboard(
@ApiParam(value = "A JSON value representing the dashboard.") @ApiParam(value = "A JSON value representing the dashboard.")
@RequestBody Dashboard dashboard) throws ThingsboardException { @RequestBody Dashboard dashboard) throws ThingsboardException {
try { dashboard.setTenantId(getCurrentUser().getTenantId());
dashboard.setTenantId(getCurrentUser().getTenantId()); checkEntity(dashboard.getId(), dashboard, Resource.DASHBOARD);
return tbDashboardService.save(dashboard, getCurrentUser());
checkEntity(dashboard.getId(), dashboard, Resource.DASHBOARD);
Dashboard savedDashboard = checkNotNull(dashboardService.saveDashboard(dashboard));
logEntityAction(savedDashboard.getId(), savedDashboard,
null,
dashboard.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);
if (dashboard.getId() != null) {
sendEntityNotificationMsg(savedDashboard.getTenantId(), savedDashboard.getId(), EdgeEventActionType.UPDATED);
}
return savedDashboard;
} catch (Exception e) {
logEntityAction(emptyId(EntityType.DASHBOARD), dashboard,
null, dashboard.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e);
throw handleException(e);
}
} }
@ApiOperation(value = "Delete the Dashboard (deleteDashboard)", @ApiOperation(value = "Delete the Dashboard (deleteDashboard)",
@ -213,28 +196,9 @@ public class DashboardController extends BaseController {
@ApiParam(value = DASHBOARD_ID_PARAM_DESCRIPTION) @ApiParam(value = DASHBOARD_ID_PARAM_DESCRIPTION)
@PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException { @PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException {
checkParameter(DASHBOARD_ID, strDashboardId); checkParameter(DASHBOARD_ID, strDashboardId);
try { DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); Dashboard dashboard = checkDashboardId(dashboardId, Operation.DELETE);
Dashboard dashboard = checkDashboardId(dashboardId, Operation.DELETE); tbDashboardService.delete(dashboard, getCurrentUser());
List<EdgeId> relatedEdgeIds = findRelatedEdgeIds(getTenantId(), dashboardId);
dashboardService.deleteDashboard(getCurrentUser().getTenantId(), dashboardId);
logEntityAction(dashboardId, dashboard,
null,
ActionType.DELETED, null, strDashboardId);
sendDeleteNotificationMsg(getTenantId(), dashboardId, relatedEdgeIds);
} catch (Exception e) {
logEntityAction(emptyId(EntityType.DASHBOARD),
null,
null,
ActionType.DELETED, e, strDashboardId);
throw handleException(e);
}
} }
@ApiOperation(value = "Assign the Dashboard (assignDashboardToCustomer)", @ApiOperation(value = "Assign the Dashboard (assignDashboardToCustomer)",
@ -251,30 +215,13 @@ public class DashboardController extends BaseController {
@PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException { @PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException {
checkParameter(CUSTOMER_ID, strCustomerId); checkParameter(CUSTOMER_ID, strCustomerId);
checkParameter(DASHBOARD_ID, strDashboardId); checkParameter(DASHBOARD_ID, strDashboardId);
try {
CustomerId customerId = new CustomerId(toUUID(strCustomerId));
Customer customer = checkCustomerId(customerId, Operation.READ);
DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
checkDashboardId(dashboardId, Operation.ASSIGN_TO_CUSTOMER);
Dashboard savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(getCurrentUser().getTenantId(), dashboardId, customerId));
logEntityAction(dashboardId, savedDashboard, CustomerId customerId = new CustomerId(toUUID(strCustomerId));
customerId, Customer customer = checkCustomerId(customerId, Operation.READ);
ActionType.ASSIGNED_TO_CUSTOMER, null, strDashboardId, strCustomerId, customer.getName());
sendEntityAssignToCustomerNotificationMsg(savedDashboard.getTenantId(), savedDashboard.getId(), customerId, EdgeEventActionType.ASSIGNED_TO_CUSTOMER); DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
checkDashboardId(dashboardId, Operation.ASSIGN_TO_CUSTOMER);
return savedDashboard; return tbDashboardService.assignDashboardToCustomer(dashboardId, customer, getCurrentUser());
} catch (Exception e) {
logEntityAction(emptyId(EntityType.DASHBOARD), null,
null,
ActionType.ASSIGNED_TO_CUSTOMER, e, strDashboardId, strCustomerId);
throw handleException(e);
}
} }
@ApiOperation(value = "Unassign the Dashboard (unassignDashboardFromCustomer)", @ApiOperation(value = "Unassign the Dashboard (unassignDashboardFromCustomer)",
@ -291,29 +238,11 @@ public class DashboardController extends BaseController {
@PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException { @PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException {
checkParameter("customerId", strCustomerId); checkParameter("customerId", strCustomerId);
checkParameter(DASHBOARD_ID, strDashboardId); checkParameter(DASHBOARD_ID, strDashboardId);
try { CustomerId customerId = new CustomerId(toUUID(strCustomerId));
CustomerId customerId = new CustomerId(toUUID(strCustomerId)); Customer customer = checkCustomerId(customerId, Operation.READ);
Customer customer = checkCustomerId(customerId, Operation.READ); DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); Dashboard dashboard = checkDashboardId(dashboardId, Operation.UNASSIGN_FROM_CUSTOMER);
Dashboard dashboard = checkDashboardId(dashboardId, Operation.UNASSIGN_FROM_CUSTOMER); return tbDashboardService.unassignDashboardFromCustomer(dashboard, customer, getCurrentUser());
Dashboard savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(getCurrentUser().getTenantId(), dashboardId, customerId));
logEntityAction(dashboardId, dashboard,
customerId,
ActionType.UNASSIGNED_FROM_CUSTOMER, null, strDashboardId, customer.getId().toString(), customer.getName());
sendEntityAssignToCustomerNotificationMsg(savedDashboard.getTenantId(), savedDashboard.getId(), customerId, EdgeEventActionType.UNASSIGNED_FROM_CUSTOMER);
return savedDashboard;
} catch (Exception e) {
logEntityAction(emptyId(EntityType.DASHBOARD), null,
null,
ActionType.UNASSIGNED_FROM_CUSTOMER, e, strDashboardId);
throw handleException(e);
}
} }
@ApiOperation(value = "Update the Dashboard Customers (updateDashboardCustomers)", @ApiOperation(value = "Update the Dashboard Customers (updateDashboardCustomers)",
@ -331,69 +260,15 @@ public class DashboardController extends BaseController {
@ApiParam(value = "JSON array with the list of customer ids, or empty to remove all customers") @ApiParam(value = "JSON array with the list of customer ids, or empty to remove all customers")
@RequestBody(required = false) String[] strCustomerIds) throws ThingsboardException { @RequestBody(required = false) String[] strCustomerIds) throws ThingsboardException {
checkParameter(DASHBOARD_ID, strDashboardId); checkParameter(DASHBOARD_ID, strDashboardId);
try { DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); Dashboard dashboard = checkDashboardId(dashboardId, Operation.ASSIGN_TO_CUSTOMER);
Dashboard dashboard = checkDashboardId(dashboardId, Operation.ASSIGN_TO_CUSTOMER); Set<CustomerId> customerIds = customerIdFromStr(strCustomerIds, dashboard);
return tbDashboardService.updateDashboardCustomers(dashboard, customerIds, getCurrentUser());
Set<CustomerId> customerIds = new HashSet<>();
if (strCustomerIds != null) {
for (String strCustomerId : strCustomerIds) {
customerIds.add(new CustomerId(toUUID(strCustomerId)));
}
}
Set<CustomerId> addedCustomerIds = new HashSet<>();
Set<CustomerId> removedCustomerIds = new HashSet<>();
for (CustomerId customerId : customerIds) {
if (!dashboard.isAssignedToCustomer(customerId)) {
addedCustomerIds.add(customerId);
}
}
Set<ShortCustomerInfo> assignedCustomers = dashboard.getAssignedCustomers();
if (assignedCustomers != null) {
for (ShortCustomerInfo customerInfo : assignedCustomers) {
if (!customerIds.contains(customerInfo.getCustomerId())) {
removedCustomerIds.add(customerInfo.getCustomerId());
}
}
}
if (addedCustomerIds.isEmpty() && removedCustomerIds.isEmpty()) {
return dashboard;
} else {
Dashboard savedDashboard = null;
for (CustomerId customerId : addedCustomerIds) {
savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(getCurrentUser().getTenantId(), dashboardId, customerId));
ShortCustomerInfo customerInfo = savedDashboard.getAssignedCustomerInfo(customerId);
logEntityAction(dashboardId, savedDashboard,
customerId,
ActionType.ASSIGNED_TO_CUSTOMER, null, strDashboardId, customerId.toString(), customerInfo.getTitle());
sendEntityAssignToCustomerNotificationMsg(savedDashboard.getTenantId(), savedDashboard.getId(), customerId, EdgeEventActionType.ASSIGNED_TO_CUSTOMER);
}
for (CustomerId customerId : removedCustomerIds) {
ShortCustomerInfo customerInfo = dashboard.getAssignedCustomerInfo(customerId);
savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(getCurrentUser().getTenantId(), dashboardId, customerId));
logEntityAction(dashboardId, dashboard,
customerId,
ActionType.UNASSIGNED_FROM_CUSTOMER, null, strDashboardId, customerId.toString(), customerInfo.getTitle());
sendEntityAssignToCustomerNotificationMsg(savedDashboard.getTenantId(), savedDashboard.getId(), customerId, EdgeEventActionType.UNASSIGNED_FROM_CUSTOMER);
}
return savedDashboard;
}
} catch (Exception e) {
logEntityAction(emptyId(EntityType.DASHBOARD), null,
null,
ActionType.ASSIGNED_TO_CUSTOMER, e, strDashboardId);
throw handleException(e);
}
} }
@ApiOperation(value = "Adds the Dashboard Customers (addDashboardCustomers)", @ApiOperation(value = "Adds the Dashboard Customers (addDashboardCustomers)",
notes = "Adds the list of Customers to the existing list of assignments for the Dashboard. Keeps previous assignments to customers that are not in the provided list. " + notes = "Adds the list of Customers to the existing list of assignments for the Dashboard. Keeps previous assignments to customers that are not in the provided list. " +
"Returns the Dashboard object." + TENANT_AUTHORITY_PARAGRAPH, "Returns the Dashboard object." + TENANT_AUTHORITY_PARAGRAPH,
produces = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE,
consumes = MediaType.APPLICATION_JSON_VALUE) consumes = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@ -405,42 +280,10 @@ public class DashboardController extends BaseController {
@ApiParam(value = "JSON array with the list of customer ids") @ApiParam(value = "JSON array with the list of customer ids")
@RequestBody String[] strCustomerIds) throws ThingsboardException { @RequestBody String[] strCustomerIds) throws ThingsboardException {
checkParameter(DASHBOARD_ID, strDashboardId); checkParameter(DASHBOARD_ID, strDashboardId);
try { DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); Dashboard dashboard = checkDashboardId(dashboardId, Operation.ASSIGN_TO_CUSTOMER);
Dashboard dashboard = checkDashboardId(dashboardId, Operation.ASSIGN_TO_CUSTOMER); Set<CustomerId> customerIds = customerIdFromStr(strCustomerIds, dashboard);
return tbDashboardService.addDashboardCustomers(dashboard, customerIds, getCurrentUser());
Set<CustomerId> customerIds = new HashSet<>();
if (strCustomerIds != null) {
for (String strCustomerId : strCustomerIds) {
CustomerId customerId = new CustomerId(toUUID(strCustomerId));
if (!dashboard.isAssignedToCustomer(customerId)) {
customerIds.add(customerId);
}
}
}
if (customerIds.isEmpty()) {
return dashboard;
} else {
Dashboard savedDashboard = null;
for (CustomerId customerId : customerIds) {
savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(getCurrentUser().getTenantId(), dashboardId, customerId));
ShortCustomerInfo customerInfo = savedDashboard.getAssignedCustomerInfo(customerId);
logEntityAction(dashboardId, savedDashboard,
customerId,
ActionType.ASSIGNED_TO_CUSTOMER, null, strDashboardId, customerId.toString(), customerInfo.getTitle());
sendEntityAssignToCustomerNotificationMsg(savedDashboard.getTenantId(), savedDashboard.getId(), customerId, EdgeEventActionType.ASSIGNED_TO_CUSTOMER);
}
return savedDashboard;
}
} catch (Exception e) {
logEntityAction(emptyId(EntityType.DASHBOARD), null,
null,
ActionType.ASSIGNED_TO_CUSTOMER, e, strDashboardId);
throw handleException(e);
}
} }
@ApiOperation(value = "Remove the Dashboard Customers (removeDashboardCustomers)", @ApiOperation(value = "Remove the Dashboard Customers (removeDashboardCustomers)",
@ -457,42 +300,10 @@ public class DashboardController extends BaseController {
@ApiParam(value = "JSON array with the list of customer ids") @ApiParam(value = "JSON array with the list of customer ids")
@RequestBody String[] strCustomerIds) throws ThingsboardException { @RequestBody String[] strCustomerIds) throws ThingsboardException {
checkParameter(DASHBOARD_ID, strDashboardId); checkParameter(DASHBOARD_ID, strDashboardId);
try { DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); Dashboard dashboard = checkDashboardId(dashboardId, Operation.UNASSIGN_FROM_CUSTOMER);
Dashboard dashboard = checkDashboardId(dashboardId, Operation.UNASSIGN_FROM_CUSTOMER); Set<CustomerId> customerIds = customerIdFromStr(strCustomerIds, dashboard);
return tbDashboardService.removeDashboardCustomers(dashboard, customerIds, getCurrentUser());
Set<CustomerId> customerIds = new HashSet<>();
if (strCustomerIds != null) {
for (String strCustomerId : strCustomerIds) {
CustomerId customerId = new CustomerId(toUUID(strCustomerId));
if (dashboard.isAssignedToCustomer(customerId)) {
customerIds.add(customerId);
}
}
}
if (customerIds.isEmpty()) {
return dashboard;
} else {
Dashboard savedDashboard = null;
for (CustomerId customerId : customerIds) {
ShortCustomerInfo customerInfo = dashboard.getAssignedCustomerInfo(customerId);
savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(getCurrentUser().getTenantId(), dashboardId, customerId));
logEntityAction(dashboardId, dashboard,
customerId,
ActionType.UNASSIGNED_FROM_CUSTOMER, null, strDashboardId, customerId.toString(), customerInfo.getTitle());
sendEntityAssignToCustomerNotificationMsg(savedDashboard.getTenantId(), savedDashboard.getId(), customerId, EdgeEventActionType.UNASSIGNED_FROM_CUSTOMER);
}
return savedDashboard;
}
} catch (Exception e) {
logEntityAction(emptyId(EntityType.DASHBOARD), null,
null,
ActionType.UNASSIGNED_FROM_CUSTOMER, e, strDashboardId);
throw handleException(e);
}
} }
@ApiOperation(value = "Assign the Dashboard to Public Customer (assignDashboardToPublicCustomer)", @ApiOperation(value = "Assign the Dashboard to Public Customer (assignDashboardToPublicCustomer)",
@ -510,25 +321,9 @@ public class DashboardController extends BaseController {
@ApiParam(value = DASHBOARD_ID_PARAM_DESCRIPTION) @ApiParam(value = DASHBOARD_ID_PARAM_DESCRIPTION)
@PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException { @PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException {
checkParameter(DASHBOARD_ID, strDashboardId); checkParameter(DASHBOARD_ID, strDashboardId);
try { DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); checkDashboardId(dashboardId, Operation.ASSIGN_TO_CUSTOMER);
Dashboard dashboard = checkDashboardId(dashboardId, Operation.ASSIGN_TO_CUSTOMER); return tbDashboardService.assignDashboardToPublicCustomer(dashboardId, getCurrentUser());
Customer publicCustomer = customerService.findOrCreatePublicCustomer(dashboard.getTenantId());
Dashboard savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(getCurrentUser().getTenantId(), dashboardId, publicCustomer.getId()));
logEntityAction(dashboardId, savedDashboard,
publicCustomer.getId(),
ActionType.ASSIGNED_TO_CUSTOMER, null, strDashboardId, publicCustomer.getId().toString(), publicCustomer.getName());
return savedDashboard;
} catch (Exception e) {
logEntityAction(emptyId(EntityType.DASHBOARD), null,
null,
ActionType.ASSIGNED_TO_CUSTOMER, e, strDashboardId);
throw handleException(e);
}
} }
@ApiOperation(value = "Unassign the Dashboard from Public Customer (unassignDashboardFromPublicCustomer)", @ApiOperation(value = "Unassign the Dashboard from Public Customer (unassignDashboardFromPublicCustomer)",
@ -542,26 +337,9 @@ public class DashboardController extends BaseController {
@ApiParam(value = DASHBOARD_ID_PARAM_DESCRIPTION) @ApiParam(value = DASHBOARD_ID_PARAM_DESCRIPTION)
@PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException { @PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException {
checkParameter(DASHBOARD_ID, strDashboardId); checkParameter(DASHBOARD_ID, strDashboardId);
try { DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); Dashboard dashboard = checkDashboardId(dashboardId, Operation.UNASSIGN_FROM_CUSTOMER);
Dashboard dashboard = checkDashboardId(dashboardId, Operation.UNASSIGN_FROM_CUSTOMER); return tbDashboardService.unassignDashboardFromPublicCustomer(dashboard, getCurrentUser());
Customer publicCustomer = customerService.findOrCreatePublicCustomer(dashboard.getTenantId());
Dashboard savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(getCurrentUser().getTenantId(), dashboardId, publicCustomer.getId()));
logEntityAction(dashboardId, dashboard,
publicCustomer.getId(),
ActionType.UNASSIGNED_FROM_CUSTOMER, null, strDashboardId, publicCustomer.getId().toString(), publicCustomer.getName());
return savedDashboard;
} catch (Exception e) {
logEntityAction(emptyId(EntityType.DASHBOARD), null,
null,
ActionType.UNASSIGNED_FROM_CUSTOMER, e, strDashboardId);
throw handleException(e);
}
} }
@ApiOperation(value = "Get Tenant Dashboards by System Administrator (getTenantDashboards)", @ApiOperation(value = "Get Tenant Dashboards by System Administrator (getTenantDashboards)",
@ -775,6 +553,7 @@ public class DashboardController extends BaseController {
public void setTenantHomeDashboardInfo( public void setTenantHomeDashboardInfo(
@ApiParam(value = "A JSON object that represents home dashboard id and other parameters", required = true) @ApiParam(value = "A JSON object that represents home dashboard id and other parameters", required = true)
@RequestBody HomeDashboardInfo homeDashboardInfo) throws ThingsboardException { @RequestBody HomeDashboardInfo homeDashboardInfo) throws ThingsboardException {
try { try {
if (homeDashboardInfo.getDashboardId() != null) { if (homeDashboardInfo.getDashboardId() != null) {
checkDashboardId(homeDashboardInfo.getDashboardId(), Operation.READ); checkDashboardId(homeDashboardInfo.getDashboardId(), Operation.READ);
@ -847,30 +626,13 @@ public class DashboardController extends BaseController {
@PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException { @PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException {
checkParameter("edgeId", strEdgeId); checkParameter("edgeId", strEdgeId);
checkParameter(DASHBOARD_ID, strDashboardId); checkParameter(DASHBOARD_ID, strDashboardId);
try {
EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
Edge edge = checkEdgeId(edgeId, Operation.READ);
DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
checkDashboardId(dashboardId, Operation.READ); Edge edge = checkEdgeId(edgeId, Operation.READ);
Dashboard savedDashboard = checkNotNull(dashboardService.assignDashboardToEdge(getCurrentUser().getTenantId(), dashboardId, edgeId));
logEntityAction(dashboardId, savedDashboard,
null,
ActionType.ASSIGNED_TO_EDGE, null, strDashboardId, strEdgeId, edge.getName());
sendEntityAssignToEdgeNotificationMsg(getTenantId(), edgeId, savedDashboard.getId(), EdgeEventActionType.ASSIGNED_TO_EDGE); DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
checkDashboardId(dashboardId, Operation.READ);
return savedDashboard; return tbDashboardService.asignDashboardToEdge(dashboardId, edge, getCurrentUser());
} catch (Exception e) {
logEntityAction(emptyId(EntityType.DASHBOARD), null,
null,
ActionType.ASSIGNED_TO_EDGE, e, strDashboardId, strEdgeId);
throw handleException(e);
}
} }
@ApiOperation(value = "Unassign dashboard from edge (unassignDashboardFromEdge)", @ApiOperation(value = "Unassign dashboard from edge (unassignDashboardFromEdge)",
@ -886,37 +648,22 @@ public class DashboardController extends BaseController {
@ResponseBody @ResponseBody
public Dashboard unassignDashboardFromEdge(@PathVariable("edgeId") String strEdgeId, public Dashboard unassignDashboardFromEdge(@PathVariable("edgeId") String strEdgeId,
@PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException { @PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException {
checkParameter("edgeId", strEdgeId); checkParameter(EDGE_ID, strEdgeId);
checkParameter(DASHBOARD_ID, strDashboardId); checkParameter(DASHBOARD_ID, strDashboardId);
try {
EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
Edge edge = checkEdgeId(edgeId, Operation.READ);
DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
Dashboard dashboard = checkDashboardId(dashboardId, Operation.READ);
Dashboard savedDashboard = checkNotNull(dashboardService.unassignDashboardFromEdge(getCurrentUser().getTenantId(), dashboardId, edgeId));
logEntityAction(dashboardId, dashboard, EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
null, Edge edge = checkEdgeId(edgeId, Operation.READ);
ActionType.UNASSIGNED_FROM_EDGE, null, strDashboardId, strEdgeId, edge.getName());
sendEntityAssignToEdgeNotificationMsg(getTenantId(), edgeId, savedDashboard.getId(), EdgeEventActionType.UNASSIGNED_FROM_EDGE); DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
Dashboard dashboard = checkDashboardId(dashboardId, Operation.READ);
return savedDashboard; return tbDashboardService.unassignDashboardFromEdge(dashboard, edge, getCurrentUser());
} catch (Exception e) {
logEntityAction(emptyId(EntityType.DASHBOARD), null,
null,
ActionType.UNASSIGNED_FROM_EDGE, e, strDashboardId, strEdgeId);
throw handleException(e);
}
} }
@ApiOperation(value = "Get Edge Dashboards (getEdgeDashboards)", @ApiOperation(value = "Get Edge Dashboards (getEdgeDashboards)",
notes = "Returns a page of dashboard info objects assigned to the specified edge. " notes = "Returns a page of dashboard info objects assigned to the specified edge. "
+ DASHBOARD_INFO_DEFINITION + " " + PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, + DASHBOARD_INFO_DEFINITION + " " + PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
produces = MediaType.APPLICATION_JSON_VALUE) produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/edge/{edgeId}/dashboards", params = {"pageSize", "page"}, method = RequestMethod.GET) @RequestMapping(value = "/edge/{edgeId}/dashboards", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -957,4 +704,17 @@ public class DashboardController extends BaseController {
throw handleException(e); throw handleException(e);
} }
} }
private Set<CustomerId> customerIdFromStr(String [] strCustomerIds, Dashboard dashboard) {
Set<CustomerId> customerIds = new HashSet<>();
if (strCustomerIds != null) {
for (String strCustomerId : strCustomerIds) {
CustomerId customerId = new CustomerId(UUID.fromString(strCustomerId));
if (dashboard.isAssignedToCustomer(customerId)) {
customerIds.add(customerId);
}
}
}
return customerIds;
}
} }

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

@ -206,7 +206,7 @@ public class DeviceController extends BaseController {
DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); DeviceId deviceId = new DeviceId(toUUID(strDeviceId));
Device device = checkDeviceId(deviceId, Operation.DELETE); Device device = checkDeviceId(deviceId, Operation.DELETE);
try { try {
tbDeviceService.deleteDevice(device, getCurrentUser()).get(); tbDeviceService.delete(device, getCurrentUser()).get();
} catch (Exception e) { } catch (Exception e) {
throw handleException(e); throw handleException(e);
} }

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

@ -164,7 +164,7 @@ public class EdgeController extends BaseController {
accessControlService.checkPermission(getCurrentUser(), Resource.EDGE, operation, edge.getId(), edge); accessControlService.checkPermission(getCurrentUser(), Resource.EDGE, operation, edge.getId(), edge);
return tbEdgeService.saveEdge(edge, edgeTemplateRootRuleChain, getCurrentUser()); return tbEdgeService.save(edge, edgeTemplateRootRuleChain, getCurrentUser());
} }
@ApiOperation(value = "Delete edge (deleteEdge)", @ApiOperation(value = "Delete edge (deleteEdge)",
@ -177,7 +177,7 @@ public class EdgeController extends BaseController {
checkParameter(EDGE_ID, strEdgeId); checkParameter(EDGE_ID, strEdgeId);
EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
Edge edge = checkEdgeId(edgeId, Operation.DELETE); Edge edge = checkEdgeId(edgeId, Operation.DELETE);
tbEdgeService.deleteEdge(edge, getCurrentUser()); tbEdgeService.delete(edge, getCurrentUser());
} }
@ApiOperation(value = "Get Tenant Edges (getEdges)", @ApiOperation(value = "Get Tenant Edges (getEdges)",

2
application/src/main/java/org/thingsboard/server/service/edge/EdgeBulkImportService.java

@ -76,7 +76,7 @@ public class EdgeBulkImportService extends AbstractBulkImportService<Edge> {
@Override @Override
protected Edge saveEntity(SecurityUser user, Edge entity, Map<BulkImportColumnType, String> fields) { protected Edge saveEntity(SecurityUser user, Edge entity, Map<BulkImportColumnType, String> fields) {
RuleChain edgeTemplateRootRuleChain = ruleChainService.getEdgeTemplateRootRuleChain(user.getTenantId()); RuleChain edgeTemplateRootRuleChain = ruleChainService.getEdgeTemplateRootRuleChain(user.getTenantId());
return tbEdgeService.saveEdge(entity, edgeTemplateRootRuleChain, user); return tbEdgeService.save(entity, edgeTemplateRootRuleChain, user);
} }
@Override @Override

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

@ -41,6 +41,7 @@ import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.dao.alarm.AlarmService; import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.ClaimDevicesService; import org.thingsboard.server.dao.device.ClaimDevicesService;
import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceCredentialsService;
import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.DeviceService;
@ -105,6 +106,8 @@ public abstract class AbstractTbEntityService {
protected EdgeNotificationService edgeNotificationService; protected EdgeNotificationService edgeNotificationService;
@Autowired @Autowired
protected QueueService queueService; protected QueueService queueService;
@Autowired
protected DashboardService dashboardService;
protected ListenableFuture<Void> removeAlarmsByEntityId(TenantId tenantId, EntityId entityId) { protected ListenableFuture<Void> removeAlarmsByEntityId(TenantId tenantId, EntityId entityId) {
ListenableFuture<PageData<AlarmInfo>> alarmsFuture = ListenableFuture<PageData<AlarmInfo>> alarmsFuture =

48
application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java

@ -22,7 +22,6 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.rule.engine.api.msg.DeviceCredentialsUpdateNotificationMsg; import org.thingsboard.rule.engine.api.msg.DeviceCredentialsUpdateNotificationMsg;
import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.Customer;
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.HasName; import org.thingsboard.server.common.data.HasName;
@ -73,7 +72,16 @@ public class DefaultTbNotificationEntityService implements TbNotificationEntityS
List<EdgeId> relatedEdgeIds, List<EdgeId> relatedEdgeIds,
SecurityUser user, Object... additionalInfo) { SecurityUser user, Object... additionalInfo) {
logEntityAction(tenantId, entityId, entity, customerId, actionType, user, additionalInfo); logEntityAction(tenantId, entityId, entity, customerId, actionType, user, additionalInfo);
sendDeleteNotificationMsg(tenantId, entityId, entity, relatedEdgeIds); sendDeleteNotificationMsg(tenantId, entityId, entity, relatedEdgeIds);
}
public void notifyDeleteAlarm(TenantId tenantId, Alarm alarm, EntityId originatorId,
CustomerId customerId, ActionType actionType,
List<EdgeId> relatedEdgeIds,
SecurityUser user,
String body, Object... additionalInfo) {
logEntityAction(tenantId, originatorId, alarm, customerId, actionType, user, additionalInfo);
sendAlarmDeleteNotificationMsg(tenantId, alarm, relatedEdgeIds, body);
} }
@Override @Override
@ -126,7 +134,7 @@ public class DefaultTbNotificationEntityService implements TbNotificationEntityS
gatewayNotificationsService.onDeviceDeleted(device); gatewayNotificationsService.onDeviceDeleted(device);
tbClusterService.onDeviceDeleted(device, null); tbClusterService.onDeviceDeleted(device, null);
notifyDeleteEntity(tenantId, deviceId, device, customerId, ActionType.DELETED, relatedEdgeIds, user, false, additionalInfo); notifyDeleteEntity(tenantId, deviceId, device, customerId, ActionType.DELETED, relatedEdgeIds, user, additionalInfo);
} }
@Override @Override
@ -145,9 +153,9 @@ public class DefaultTbNotificationEntityService implements TbNotificationEntityS
} }
@Override @Override
public <E extends HasName, I extends EntityId> void notifyCreateOrUpdateEntity(TenantId tenantId, I entityId, E entity, CustomerId customerId, ActionType actionType, SecurityUser user, Object... additionalInfo) { public <E extends HasName, I extends EntityId> void notifyCreateOrUpdateEntity(TenantId tenantId, I entityId, E entity, CustomerId customerId, ActionType actionType, SecurityUser user, Object... additionalInfo) {
logEntityAction(tenantId, entityId, entity, customerId, actionType, user, additionalInfo); logEntityAction(tenantId, entityId, entity, customerId, actionType, user, additionalInfo);
if (actionType == ActionType.UPDATED) { if (actionType == ActionType.UPDATED) {
sendEntityNotificationMsg(tenantId, entityId, EdgeEventActionType.UPDATED); sendEntityNotificationMsg(tenantId, entityId, EdgeEventActionType.UPDATED);
} }
} }
@ -191,20 +199,7 @@ public class DefaultTbNotificationEntityService implements TbNotificationEntityS
@Override @Override
public void notifyCreateOrUpdateAlarm(Alarm alarm, ActionType actionType, SecurityUser user, Object... additionalInfo) { public void notifyCreateOrUpdateAlarm(Alarm alarm, ActionType actionType, SecurityUser user, Object... additionalInfo) {
logEntityAction(alarm.getTenantId(), alarm.getOriginator(), alarm, alarm.getCustomerId(), actionType, user, additionalInfo); logEntityAction(alarm.getTenantId(), alarm.getOriginator(), alarm, alarm.getCustomerId(), actionType, user, additionalInfo);
sendEntityNotificationMsg(alarm.getTenantId(), alarm.getId(), edgeTypeByActionType (actionType)); sendEntityNotificationMsg(alarm.getTenantId(), alarm.getId(), edgeTypeByActionType(actionType));
}
@Override
public void notifyDeleteAlarm(Alarm alarm, SecurityUser user, List<EdgeId> relatedEdgeIds) {
logEntityAction(alarm.getTenantId(), alarm.getOriginator(), alarm, alarm.getCustomerId(), ActionType.ALARM_DELETE, user, null);
sendAlarmDeleteNotificationMsg(alarm, relatedEdgeIds);
}
@Override
public void notifyDeleteCustomer(Customer customer, SecurityUser user, List<EdgeId> edgeIds) {
logEntityAction(customer.getTenantId(), customer.getId(), customer, customer.getId(), ActionType.DELETED, user, null);
sendDeleteNotificationMsg(customer.getTenantId(), customer.getId(), customer, edgeIds);
tbClusterService.broadcastEntityStateChangeEvent(customer.getTenantId(), customer.getId(), ComponentLifecycleEvent.DELETED);
} }
private <E extends HasName, I extends EntityId> void logEntityAction(TenantId tenantId, I entityId, E entity, CustomerId customerId, private <E extends HasName, I extends EntityId> void logEntityAction(TenantId tenantId, I entityId, E entity, CustomerId customerId,
@ -233,19 +228,20 @@ public class DefaultTbNotificationEntityService implements TbNotificationEntityS
} }
} }
protected <E extends HasName, I extends EntityId> void sendDeleteNotificationMsg(TenantId tenantId, I entityId, E entity, List<EdgeId> edgeIds) { protected void sendAlarmDeleteNotificationMsg(TenantId tenantId, Alarm alarm, List<EdgeId> edgeIds, String body) {
try { try {
sendDeleteNotificationMsg(tenantId, entityId, edgeIds, null); sendDeleteNotificationMsg(tenantId, alarm.getId(), edgeIds, body);
} catch (Exception e) { } catch (Exception e) {
log.warn("Failed to push delete " + entity.getClass().getName() + " msg to core: {}", entity, e); log.warn("Failed to push delete msg to core: {}", alarm, e);
} }
} }
protected void sendAlarmDeleteNotificationMsg(Alarm alarm, List<EdgeId> relatedEdgeIds) { protected <E extends HasName, I extends EntityId> void sendDeleteNotificationMsg(TenantId tenantId, I entityId, E entity,
List<EdgeId> edgeIds) {
try { try {
sendDeleteNotificationMsg(alarm.getTenantId(), alarm.getId(), relatedEdgeIds, json.writeValueAsString(alarm)); sendDeleteNotificationMsg(tenantId, entityId, edgeIds, null);
} catch (Exception e) { } catch (Exception e) {
log.warn("Failed to push delete alarm msg to core: {}", alarm, e); log.warn("Failed to push delete msg to core: {}", entity, e);
} }
} }
@ -289,7 +285,7 @@ public class DefaultTbNotificationEntityService implements TbNotificationEntityS
return null; return null;
} }
private EdgeEventActionType edgeTypeByActionType (ActionType actionType) { private EdgeEventActionType edgeTypeByActionType(ActionType actionType) {
switch (actionType) { switch (actionType) {
case ADDED: case ADDED:
return EdgeEventActionType.ADDED; return EdgeEventActionType.ADDED;

4
application/src/main/java/org/thingsboard/server/service/entitiy/SimpleTbEntityService.java

@ -20,6 +20,8 @@ import org.thingsboard.server.service.security.model.SecurityUser;
public interface SimpleTbEntityService<T> { public interface SimpleTbEntityService<T> {
T save(T entity, SecurityUser user) throws ThingsboardException; T save(T entity, SecurityUser user) throws ThingsboardException;
void delete (T entity, SecurityUser user) throws ThingsboardException;
} }

17
application/src/main/java/org/thingsboard/server/service/entitiy/TbNotificationEntityService.java

@ -15,7 +15,6 @@
*/ */
package org.thingsboard.server.service.entitiy; package org.thingsboard.server.service.entitiy;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.Tenant;
@ -44,9 +43,15 @@ public interface TbNotificationEntityService {
CustomerId customerId, ActionType actionType, CustomerId customerId, ActionType actionType,
SecurityUser user, Object... additionalInfo); SecurityUser user, Object... additionalInfo);
<E extends HasName, I extends EntityId> void notifyDeleteEntity(TenantId tenantId, I entityId, E entity, CustomerId customerId, <E extends HasName, I extends EntityId> void notifyDeleteEntity(TenantId tenantId, I entityId, E entity,
ActionType actionType, List<EdgeId> relatedEdgeIds, SecurityUser user, CustomerId customerId, ActionType actionType,
Object... additionalInfo); List<EdgeId> relatedEdgeIds,
SecurityUser user, Object... additionalInfo);
void notifyDeleteAlarm(TenantId tenantId, Alarm alarm, EntityId originatorId,
CustomerId customerId, ActionType actionType,
List<EdgeId> relatedEdgeIds,
SecurityUser user, String body, Object... additionalInfo);
<E extends HasName, I extends EntityId> void notifyAssignOrUnassignEntityToCustomer(TenantId tenantId, I entityId, <E extends HasName, I extends EntityId> void notifyAssignOrUnassignEntityToCustomer(TenantId tenantId, I entityId,
CustomerId customerId, E entity, CustomerId customerId, E entity,
@ -80,8 +85,4 @@ public interface TbNotificationEntityService {
void notifyEdge(TenantId tenantId, EdgeId edgeId, CustomerId customerId, Edge edge, ActionType actionType, SecurityUser user, Object... additionalInfo); void notifyEdge(TenantId tenantId, EdgeId edgeId, CustomerId customerId, Edge edge, ActionType actionType, SecurityUser user, Object... additionalInfo);
void notifyCreateOrUpdateAlarm(Alarm alarm, ActionType actionType, SecurityUser user, Object... additionalInfo); void notifyCreateOrUpdateAlarm(Alarm alarm, ActionType actionType, SecurityUser user, Object... additionalInfo);
void notifyDeleteAlarm(Alarm alarm, SecurityUser user, List<EdgeId> relatedEdgeIds);
void notifyDeleteCustomer(Customer customer, SecurityUser user, List<EdgeId> relatedEdgeIds);
} }

14
application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java

@ -17,6 +17,7 @@ package org.thingsboard.server.service.entitiy.alarm;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.alarm.AlarmStatus;
@ -77,8 +78,13 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb
@Override @Override
public Boolean delete(Alarm alarm, SecurityUser user) throws ThingsboardException { public Boolean delete(Alarm alarm, SecurityUser user) throws ThingsboardException {
List<EdgeId> relatedEdgeIds = findRelatedEdgeIds(alarm.getTenantId(), alarm.getOriginator()); try {
notificationEntityService.notifyDeleteAlarm(alarm, user, relatedEdgeIds); List<EdgeId> relatedEdgeIds = findRelatedEdgeIds(user.getTenantId(), alarm.getOriginator());
return alarmService.deleteAlarm(alarm.getTenantId(), alarm.getId()).isSuccessful(); notificationEntityService.notifyDeleteAlarm(user.getTenantId(), alarm, alarm.getOriginator(), user.getCustomerId(),
ActionType.DELETED, relatedEdgeIds, user, JacksonUtil.OBJECT_MAPPER.writeValueAsString(alarm));
return alarmService.deleteAlarm(user.getTenantId(), alarm.getId()).isSuccessful();
} catch (Exception e) {
throw handleException(e);
}
} }
} }

6
application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java

@ -16,12 +16,12 @@
package org.thingsboard.server.service.entitiy.alarm; package org.thingsboard.server.service.entitiy.alarm;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.service.entitiy.SimpleTbEntityService;
import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.SecurityUser;
public interface TbAlarmService extends SimpleTbEntityService<Alarm> { public interface TbAlarmService {
Alarm save(Alarm entity, SecurityUser user) throws ThingsboardException;
void ack(Alarm alarm, SecurityUser user) throws ThingsboardException; void ack(Alarm alarm, SecurityUser user) throws ThingsboardException;

6
application/src/main/java/org/thingsboard/server/service/entitiy/asset/DefaultTbAssetService.java

@ -39,6 +39,7 @@ import java.util.List;
@TbCoreComponent @TbCoreComponent
@AllArgsConstructor @AllArgsConstructor
public class DefaultTbAssetService extends AbstractTbEntityService implements TbAssetService { public class DefaultTbAssetService extends AbstractTbEntityService implements TbAssetService {
@Override @Override
public Asset save(Asset asset, SecurityUser user) throws ThingsboardException { public Asset save(Asset asset, SecurityUser user) throws ThingsboardException {
ActionType actionType = asset.getId() == null ? ActionType.ADDED : ActionType.UPDATED; ActionType actionType = asset.getId() == null ? ActionType.ADDED : ActionType.UPDATED;
@ -60,12 +61,13 @@ public class DefaultTbAssetService extends AbstractTbEntityService implements Tb
try { try {
List<EdgeId> relatedEdgeIds = findRelatedEdgeIds(tenantId, assetId); List<EdgeId> relatedEdgeIds = findRelatedEdgeIds(tenantId, assetId);
assetService.deleteAsset(tenantId, assetId); assetService.deleteAsset(tenantId, assetId);
notificationEntityService.notifyDeleteEntity(tenantId, assetId, asset, asset.getCustomerId(), ActionType.DELETED, relatedEdgeIds, user, false, asset.toString()); notificationEntityService.notifyDeleteEntity(tenantId, assetId, asset, asset.getCustomerId(), ActionType.DELETED,
relatedEdgeIds, user, assetId.toString());
return removeAlarmsByEntityId(tenantId, assetId); return removeAlarmsByEntityId(tenantId, assetId);
} catch (Exception e) { } catch (Exception e) {
notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.ASSET), null, null, notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.ASSET), null, null,
ActionType.DELETED, user, e, asset.toString()); ActionType.DELETED, user, e, assetId.toString());
throw handleException(e); throw handleException(e);
} }
} }

5
application/src/main/java/org/thingsboard/server/service/entitiy/asset/TbAssetService.java

@ -22,10 +22,11 @@ import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.service.entitiy.SimpleTbEntityService;
import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.SecurityUser;
public interface TbAssetService extends SimpleTbEntityService<Asset> { public interface TbAssetService {
Asset save(Asset asset, SecurityUser user) throws ThingsboardException;
ListenableFuture<Void> delete(Asset asset, SecurityUser user) throws ThingsboardException; ListenableFuture<Void> delete(Asset asset, SecurityUser user) throws ThingsboardException;

18
application/src/main/java/org/thingsboard/server/service/entitiy/customer/DefaultTbCustomerService.java

@ -17,12 +17,15 @@ package org.thingsboard.server.service.entitiy.customer;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.AbstractTbEntityService; import org.thingsboard.server.service.entitiy.AbstractTbEntityService;
import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.SecurityUser;
@ -34,6 +37,8 @@ import java.util.List;
@AllArgsConstructor @AllArgsConstructor
public class DefaultTbCustomerService extends AbstractTbEntityService implements TbCustomerService { public class DefaultTbCustomerService extends AbstractTbEntityService implements TbCustomerService {
private final TbClusterService tbClusterService;
@Override @Override
public Customer save(Customer customer, SecurityUser user) throws ThingsboardException { public Customer save(Customer customer, SecurityUser user) throws ThingsboardException {
ActionType actionType = customer.getId() == null ? ActionType.ADDED : ActionType.UPDATED; ActionType actionType = customer.getId() == null ? ActionType.ADDED : ActionType.UPDATED;
@ -48,15 +53,20 @@ public class DefaultTbCustomerService extends AbstractTbEntityService implements
} }
} }
@Override @Override
public void delete(Customer customer, SecurityUser user) throws ThingsboardException { public void delete(Customer customer, SecurityUser user) throws ThingsboardException {
TenantId tenantId = customer.getTenantId(); TenantId tenantId = customer.getTenantId();
CustomerId customerId = customer.getId();
try { try {
List<EdgeId> relatedEdgeIds = findRelatedEdgeIds(tenantId, customer.getId()); List<EdgeId> relatedEdgeIds = findRelatedEdgeIds(tenantId, customerId);
customerService.deleteCustomer(tenantId, customer.getId()); customerService.deleteCustomer(tenantId, customerId);
notificationEntityService.notifyDeleteCustomer(customer, user, relatedEdgeIds); notificationEntityService.notifyDeleteEntity(tenantId, customerId, customer, customerId,
ActionType.DELETED, relatedEdgeIds, user, customerId.toString());
tbClusterService.broadcastEntityStateChangeEvent(tenantId, customerId, ComponentLifecycleEvent.DELETED);
} catch (Exception e) { } catch (Exception e) {
notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.CUSTOMER), null, null, ActionType.DELETED, user, e); notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.CUSTOMER), null, null,
ActionType.DELETED, user, e, customerId.toString());
throw handleException(e); throw handleException(e);
} }
} }

4
application/src/main/java/org/thingsboard/server/service/entitiy/customer/TbCustomerService.java

@ -16,12 +16,8 @@
package org.thingsboard.server.service.entitiy.customer; package org.thingsboard.server.service.entitiy.customer;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.service.entitiy.SimpleTbEntityService; import org.thingsboard.server.service.entitiy.SimpleTbEntityService;
import org.thingsboard.server.service.security.model.SecurityUser;
public interface TbCustomerService extends SimpleTbEntityService<Customer> { public interface TbCustomerService extends SimpleTbEntityService<Customer> {
void delete(Customer customer, SecurityUser user) throws ThingsboardException;
} }

275
application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/DefaultTbDashboardService.java

@ -0,0 +1,275 @@
/**
* 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.entitiy.dashboard;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.ShortCustomerInfo;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.AbstractTbEntityService;
import org.thingsboard.server.service.security.model.SecurityUser;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@Service
@TbCoreComponent
@AllArgsConstructor
public class DefaultTbDashboardService extends AbstractTbEntityService implements TbDashboardService {
@Override
public Dashboard save(Dashboard dashboard, SecurityUser user) throws ThingsboardException {
ActionType actionType = dashboard.getId() == null ? ActionType.ADDED : ActionType.UPDATED;
TenantId tenantId = dashboard.getTenantId();
try {
Dashboard savedDashboard = checkNotNull(dashboardService.saveDashboard(dashboard));
notificationEntityService.notifyCreateOrUpdateEntity(tenantId, savedDashboard.getId(), savedDashboard,
null, actionType, user);
return savedDashboard;
} catch (Exception e) {
notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.DASHBOARD), dashboard, null, actionType, user, e);
throw handleException(e);
}
}
@Override
public void delete(Dashboard dashboard, SecurityUser user) throws ThingsboardException {
TenantId tenantId = dashboard.getTenantId();
DashboardId dashboardId = dashboard.getId();
try {
List<EdgeId> relatedEdgeIds = findRelatedEdgeIds(tenantId, dashboardId);
dashboardService.deleteDashboard(tenantId, dashboardId);
notificationEntityService.notifyDeleteEntity(tenantId, dashboardId, dashboard, user.getCustomerId(),
ActionType.DELETED, relatedEdgeIds, user, dashboardId.toString());
} catch (Exception e) {
notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.DASHBOARD), null, null,
ActionType.DELETED, user, e, dashboardId.toString());
throw handleException(e);
}
}
@Override
public Dashboard assignDashboardToCustomer(DashboardId dashboardId, Customer customer, SecurityUser user) throws ThingsboardException {
ActionType actionType = ActionType.ASSIGNED_TO_CUSTOMER;
CustomerId customerId = customer.getId();
try {
Dashboard savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(user.getTenantId(), dashboardId, customerId));
notificationEntityService.notifyAssignOrUnassignEntityToCustomer(user.getTenantId(), dashboardId, customerId, savedDashboard,
actionType, EdgeEventActionType.ASSIGNED_TO_CUSTOMER, user, true, customerId.toString(), customer.getName());
return savedDashboard;
} catch (Exception e) {
notificationEntityService.notifyEntity(user.getTenantId(), emptyId(EntityType.DASHBOARD), null, null,
actionType, user, e, dashboardId.toString(), customerId.toString());
throw handleException(e);
}
}
@Override
public Dashboard assignDashboardToPublicCustomer(DashboardId dashboardId, SecurityUser user) throws ThingsboardException {
ActionType actionType = ActionType.ASSIGNED_TO_CUSTOMER;
try {
Customer publicCustomer = customerService.findOrCreatePublicCustomer(user.getTenantId());
Dashboard savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(user.getTenantId(), dashboardId, publicCustomer.getId()));
notificationEntityService.notifyAssignOrUnassignEntityToCustomer(user.getTenantId(), dashboardId, user.getCustomerId(), savedDashboard,
actionType, null, user, false, dashboardId.toString(),
publicCustomer.getId().toString(), publicCustomer.getName());
return savedDashboard;
} catch (Exception e) {
notificationEntityService.notifyEntity(user.getTenantId(), emptyId(EntityType.DASHBOARD), null, null,
actionType, user, e, dashboardId.toString());
throw handleException(e);
}
}
@Override
public Dashboard unassignDashboardFromPublicCustomer(Dashboard dashboard, SecurityUser user) throws ThingsboardException {
ActionType actionType = ActionType.UNASSIGNED_FROM_CUSTOMER;
try {
Customer publicCustomer = customerService.findOrCreatePublicCustomer(dashboard.getTenantId());
Dashboard savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(user.getTenantId(), dashboard.getId(), publicCustomer.getId()));
notificationEntityService.notifyAssignOrUnassignEntityToCustomer(user.getTenantId(), dashboard.getId(), user.getCustomerId(), dashboard,
actionType, null, user, false, dashboard.getId().toString(),
publicCustomer.getId().toString(), publicCustomer.getName());
return savedDashboard;
} catch (Exception e) {
notificationEntityService.notifyEntity(user.getTenantId(), emptyId(EntityType.DASHBOARD), null, null,
actionType, user, e, dashboard.getId().toString());
throw handleException(e);
}
}
@Override
public Dashboard updateDashboardCustomers(Dashboard dashboard, Set<CustomerId> customerIds, SecurityUser user) throws ThingsboardException {
ActionType actionType = ActionType.ASSIGNED_TO_CUSTOMER;
TenantId tenantId = user.getTenantId();
try {
Set<CustomerId> addedCustomerIds = new HashSet<>();
Set<CustomerId> removedCustomerIds = new HashSet<>();
for (CustomerId customerId : customerIds) {
if (!dashboard.isAssignedToCustomer(customerId)) {
addedCustomerIds.add(customerId);
}
}
Set<ShortCustomerInfo> assignedCustomers = dashboard.getAssignedCustomers();
if (assignedCustomers != null) {
for (ShortCustomerInfo customerInfo : assignedCustomers) {
if (!customerIds.contains(customerInfo.getCustomerId())) {
removedCustomerIds.add(customerInfo.getCustomerId());
}
}
}
if (addedCustomerIds.isEmpty() && removedCustomerIds.isEmpty()) {
return dashboard;
} else {
Dashboard savedDashboard = null;
for (CustomerId customerId : addedCustomerIds) {
savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(tenantId, dashboard.getId(), customerId));
ShortCustomerInfo customerInfo = savedDashboard.getAssignedCustomerInfo(customerId);
notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, savedDashboard.getId(), customerId, savedDashboard,
actionType, EdgeEventActionType.ASSIGNED_TO_CUSTOMER, user, true, customerInfo.getTitle());
}
for (CustomerId customerId : removedCustomerIds) {
ShortCustomerInfo customerInfo = dashboard.getAssignedCustomerInfo(customerId);
savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(tenantId, dashboard.getId(), customerId));
notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, savedDashboard.getId(), customerId, savedDashboard,
ActionType.UNASSIGNED_FROM_CUSTOMER, EdgeEventActionType.UNASSIGNED_FROM_CUSTOMER, user, true, customerInfo.getTitle());
}
return savedDashboard;
}
} catch (Exception e) {
notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.DASHBOARD), null, null,
actionType, user, e, dashboard.getId().toString());
throw handleException(e);
}
}
@Override
public Dashboard addDashboardCustomers(Dashboard dashboard, Set<CustomerId> customerIds, SecurityUser user) throws ThingsboardException {
ActionType actionType = ActionType.ASSIGNED_TO_CUSTOMER;
TenantId tenantId = user.getTenantId();
try {
if (customerIds.isEmpty()) {
return dashboard;
} else {
Dashboard savedDashboard = null;
for (CustomerId customerId : customerIds) {
savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(tenantId, dashboard.getId(), customerId));
ShortCustomerInfo customerInfo = savedDashboard.getAssignedCustomerInfo(customerId);
notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, savedDashboard.getId(), customerId, savedDashboard,
actionType, EdgeEventActionType.ASSIGNED_TO_CUSTOMER, user, true, customerInfo.getTitle());
}
return savedDashboard;
}
} catch (Exception e) {
notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.DASHBOARD), null, null,
actionType, user, e, dashboard.getId().toString());
throw handleException(e);
}
}
@Override
public Dashboard removeDashboardCustomers(Dashboard dashboard, Set<CustomerId> customerIds, SecurityUser user) throws ThingsboardException {
ActionType actionType = ActionType.UNASSIGNED_FROM_CUSTOMER;
TenantId tenantId = user.getTenantId();
try {
if (customerIds.isEmpty()) {
return dashboard;
} else {
Dashboard savedDashboard = null;
for (CustomerId customerId : customerIds) {
ShortCustomerInfo customerInfo = dashboard.getAssignedCustomerInfo(customerId);
savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(tenantId, dashboard.getId(), customerId));
notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, savedDashboard.getId(), customerId, savedDashboard,
actionType, EdgeEventActionType.UNASSIGNED_FROM_CUSTOMER, user, true, customerInfo.getTitle());
}
return savedDashboard;
}
} catch (Exception e) {
notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.DASHBOARD), null, null,
actionType, user, e, dashboard.getId().toString());
throw handleException(e);
}
}
@Override
public Dashboard asignDashboardToEdge(DashboardId dashboardId, Edge edge, SecurityUser user) throws ThingsboardException {
ActionType actionType = ActionType.ASSIGNED_TO_EDGE;
TenantId tenantId = user.getTenantId();
EdgeId edgeId = edge.getId();
try {
Dashboard savedDashboard = checkNotNull(dashboardService.assignDashboardToEdge(tenantId, dashboardId, edgeId));
notificationEntityService.notifyAssignOrUnassignEntityToEdge(tenantId, dashboardId, user.getCustomerId(),
edgeId, savedDashboard, actionType, EdgeEventActionType.ASSIGNED_TO_EDGE, user, dashboardId.toString(),
edgeId.toString(), edge.getName());
return savedDashboard;
} catch (Exception e) {
notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.DEVICE), null, null,
actionType, user, e, dashboardId.toString(), edgeId.toString());
throw handleException(e);
}
}
@Override
public Dashboard unassignDashboardFromEdge(Dashboard dashboard, Edge edge, SecurityUser user) throws ThingsboardException {
ActionType actionType = ActionType.UNASSIGNED_FROM_EDGE;
TenantId tenantId = dashboard.getTenantId();
DashboardId dashboardId = dashboard.getId();
EdgeId edgeId = edge.getId();
try {
Dashboard savedDevice = checkNotNull(dashboardService.unassignDashboardFromEdge(tenantId, dashboardId, edgeId));
notificationEntityService.notifyAssignOrUnassignEntityToEdge(tenantId, dashboardId, user.getCustomerId(),
edgeId, dashboard, actionType, EdgeEventActionType.UNASSIGNED_FROM_EDGE, user, dashboardId.toString(),
edgeId.toString(), edge.getName());
return savedDevice;
} catch (Exception e) {
notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.DASHBOARD), null, null,
actionType, user, e, dashboardId.toString(), edgeId.toString());
throw handleException(e);
}
}
@Override
public Dashboard unassignDashboardFromCustomer(Dashboard dashboard, Customer customer, SecurityUser user) throws ThingsboardException {
ActionType actionType = ActionType.UNASSIGNED_FROM_CUSTOMER;
TenantId tenantId = dashboard.getTenantId();
try {
Dashboard savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(tenantId, dashboard.getId(), customer.getId()));
notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, dashboard.getId(), customer.getId(), savedDashboard,
actionType, EdgeEventActionType.UNASSIGNED_FROM_CUSTOMER, user, true, customer.getId().toString(), customer.getName());
return savedDashboard;
} catch (Exception e) {
notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.DASHBOARD), null, null,
actionType, user, e, dashboard.getId().toString());
throw handleException(e);
}
}
}

49
application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/TbDashboardService.java

@ -0,0 +1,49 @@
/**
* 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.entitiy.dashboard;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.service.entitiy.SimpleTbEntityService;
import org.thingsboard.server.service.security.model.SecurityUser;
import java.util.Set;
public interface TbDashboardService extends SimpleTbEntityService<Dashboard> {
Dashboard assignDashboardToCustomer(DashboardId dashboardId, Customer customer, SecurityUser user) throws ThingsboardException;
Dashboard assignDashboardToPublicCustomer(DashboardId dashboardId, SecurityUser user) throws ThingsboardException;
Dashboard unassignDashboardFromPublicCustomer(Dashboard dashboard, SecurityUser user) throws ThingsboardException;
Dashboard updateDashboardCustomers(Dashboard dashboard, Set<CustomerId> customerIds, SecurityUser user) throws ThingsboardException;
Dashboard addDashboardCustomers(Dashboard dashboard, Set<CustomerId> customerIds, SecurityUser user) throws ThingsboardException;
Dashboard removeDashboardCustomers(Dashboard dashboard, Set<CustomerId> customerIds, SecurityUser user) throws ThingsboardException;
Dashboard asignDashboardToEdge(DashboardId dashboardId, Edge edge, SecurityUser user) throws ThingsboardException;
Dashboard unassignDashboardFromEdge(Dashboard dashboard, Edge edge, SecurityUser user) throws ThingsboardException;
Dashboard unassignDashboardFromCustomer(Dashboard dashboard, Customer customer, SecurityUser user) throws ThingsboardException;
}

2
application/src/main/java/org/thingsboard/server/service/entitiy/device/DefaultTbDeviceService.java

@ -80,7 +80,7 @@ public class DefaultTbDeviceService extends AbstractTbEntityService implements T
} }
@Override @Override
public ListenableFuture<Void> deleteDevice(Device device, SecurityUser user) throws ThingsboardException { public ListenableFuture<Void> delete(Device device, SecurityUser user) throws ThingsboardException {
TenantId tenantId = device.getTenantId(); TenantId tenantId = device.getTenantId();
DeviceId deviceId = device.getId(); DeviceId deviceId = device.getId();
try { try {

2
application/src/main/java/org/thingsboard/server/service/entitiy/device/TbDeviceService.java

@ -35,7 +35,7 @@ public interface TbDeviceService {
Device saveDeviceWithCredentials(TenantId tenantId, Device device, DeviceCredentials deviceCredentials, SecurityUser user) throws ThingsboardException; Device saveDeviceWithCredentials(TenantId tenantId, Device device, DeviceCredentials deviceCredentials, SecurityUser user) throws ThingsboardException;
ListenableFuture<Void> deleteDevice(Device device, SecurityUser user) throws ThingsboardException; ListenableFuture<Void> delete(Device device, SecurityUser user) throws ThingsboardException;
Device assignDeviceToCustomer(TenantId tenantId, DeviceId deviceId, Customer customer, SecurityUser user) throws ThingsboardException; Device assignDeviceToCustomer(TenantId tenantId, DeviceId deviceId, Customer customer, SecurityUser user) throws ThingsboardException;

8
application/src/main/java/org/thingsboard/server/service/entitiy/edge/DefaultTbEdgeService.java

@ -39,7 +39,7 @@ import org.thingsboard.server.service.security.model.SecurityUser;
public class DefaultTbEdgeService extends AbstractTbEntityService implements TbEdgeService { public class DefaultTbEdgeService extends AbstractTbEntityService implements TbEdgeService {
@Override @Override
public Edge saveEdge(Edge edge, RuleChain edgeTemplateRootRuleChain, SecurityUser user) throws ThingsboardException { public Edge save(Edge edge, RuleChain edgeTemplateRootRuleChain, SecurityUser user) throws ThingsboardException {
ActionType actionType = edge.getId() == null ? ActionType.ADDED : ActionType.UPDATED; ActionType actionType = edge.getId() == null ? ActionType.ADDED : ActionType.UPDATED;
TenantId tenantId = edge.getTenantId(); TenantId tenantId = edge.getTenantId();
try { try {
@ -62,16 +62,16 @@ public class DefaultTbEdgeService extends AbstractTbEntityService implements TbE
} }
@Override @Override
public void deleteEdge(Edge edge, SecurityUser user) throws ThingsboardException { public void delete(Edge edge, SecurityUser user) throws ThingsboardException {
ActionType actionType = ActionType.DELETED; ActionType actionType = ActionType.DELETED;
EdgeId edgeId = edge.getId(); EdgeId edgeId = edge.getId();
TenantId tenantId = edge.getTenantId(); TenantId tenantId = edge.getTenantId();
try { try {
edgeService.deleteEdge(tenantId, edgeId); edgeService.deleteEdge(tenantId, edgeId);
notificationEntityService.notifyEdge(tenantId, edgeId, edge.getCustomerId(), edge, actionType, user, edgeId.toString()); notificationEntityService.notifyEdge(tenantId, edgeId, edge.getCustomerId(), edge, actionType, user, edgeId.toString());
} catch (Exception e) { } catch (Exception e) {
notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.EDGE), edge, null, actionType, user, e); notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.EDGE), edge, null, actionType,
user, e, edgeId.toString());
throw handleException(e); throw handleException(e);
} }
} }

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

@ -25,9 +25,9 @@ import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.SecurityUser;
public interface TbEdgeService { public interface TbEdgeService {
Edge saveEdge(Edge edge, RuleChain edgeTemplateRootRuleChain, SecurityUser user) throws ThingsboardException; Edge save(Edge edge, RuleChain edgeTemplateRootRuleChain, SecurityUser user) throws ThingsboardException;
void deleteEdge(Edge edge, SecurityUser user) throws ThingsboardException; void delete(Edge edge, SecurityUser user) throws ThingsboardException;
Edge assignEdgeToCustomer(TenantId tenantId, EdgeId edgeId, Customer customer, SecurityUser user) throws ThingsboardException; Edge assignEdgeToCustomer(TenantId tenantId, EdgeId edgeId, Customer customer, SecurityUser user) throws ThingsboardException;

73
application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java

@ -228,7 +228,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
} }
} else if (!theCtx.isInitialDataSent()) { } else if (!theCtx.isInitialDataSent()) {
EntityDataUpdate update = new EntityDataUpdate(theCtx.getCmdId(), theCtx.getData(), null, theCtx.getMaxEntitiesPerDataSubscription()); EntityDataUpdate update = new EntityDataUpdate(theCtx.getCmdId(), theCtx.getData(), null, theCtx.getMaxEntitiesPerDataSubscription());
wsService.sendWsMsg(theCtx.getSessionId(), update); theCtx.sendWsMsg(update);
theCtx.setInitialDataSent(true); theCtx.setInitialDataSent(true);
} }
} catch (RuntimeException e) { } catch (RuntimeException e) {
@ -287,7 +287,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
ctx.clearEntitySubscriptions(); ctx.clearEntitySubscriptions();
if (entities.isEmpty()) { if (entities.isEmpty()) {
AlarmDataUpdate update = new AlarmDataUpdate(cmd.getCmdId(), new PageData<>(), null, 0, 0); AlarmDataUpdate update = new AlarmDataUpdate(cmd.getCmdId(), new PageData<>(), null, 0, 0);
wsService.sendWsMsg(ctx.getSessionId(), update); ctx.sendWsMsg(update);
} else { } else {
ctx.fetchAlarms(); ctx.fetchAlarms();
ctx.createLatestValuesSubscriptions(cmd.getQuery().getLatestValues()); ctx.createLatestValuesSubscriptions(cmd.getQuery().getLatestValues());
@ -420,22 +420,26 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
} }
} catch (InterruptedException | ExecutionException e) { } catch (InterruptedException | ExecutionException e) {
log.warn("[{}][{}][{}] Failed to fetch historical data", ctx.getSessionId(), ctx.getCmdId(), entityData.getEntityId(), e); log.warn("[{}][{}][{}] Failed to fetch historical data", ctx.getSessionId(), ctx.getCmdId(), entityData.getEntityId(), e);
wsService.sendWsMsg(ctx.getSessionId(), ctx.sendWsMsg(new EntityDataUpdate(ctx.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR.getCode(), "Failed to fetch historical data!"));
new EntityDataUpdate(ctx.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR.getCode(), "Failed to fetch historical data!"));
} }
}); });
EntityDataUpdate update; ctx.getWsLock().lock();
if (!ctx.isInitialDataSent()) { try {
update = new EntityDataUpdate(ctx.getCmdId(), ctx.getData(), null, ctx.getMaxEntitiesPerDataSubscription()); EntityDataUpdate update;
ctx.setInitialDataSent(true); if (!ctx.isInitialDataSent()) {
} else { update = new EntityDataUpdate(ctx.getCmdId(), ctx.getData(), null, ctx.getMaxEntitiesPerDataSubscription());
update = new EntityDataUpdate(ctx.getCmdId(), null, ctx.getData().getData(), ctx.getMaxEntitiesPerDataSubscription()); ctx.setInitialDataSent(true);
} } else {
wsService.sendWsMsg(ctx.getSessionId(), update); update = new EntityDataUpdate(ctx.getCmdId(), null, ctx.getData().getData(), ctx.getMaxEntitiesPerDataSubscription());
if (subscribe) { }
ctx.createTimeseriesSubscriptions(keys.stream().map(key -> new EntityKey(EntityKeyType.TIME_SERIES, key)).collect(Collectors.toList()), cmd.getStartTs(), cmd.getEndTs()); if (subscribe) {
ctx.createTimeseriesSubscriptions(keys.stream().map(key -> new EntityKey(EntityKeyType.TIME_SERIES, key)).collect(Collectors.toList()), cmd.getStartTs(), cmd.getEndTs());
}
ctx.sendWsMsg(update);
ctx.getData().getData().forEach(ed -> ed.getTimeseries().clear());
} finally {
ctx.getWsLock().unlock();
} }
ctx.getData().getData().forEach(ed -> ed.getTimeseries().clear());
return ctx; return ctx;
}, wsCallBackExecutor); }, wsCallBackExecutor);
} }
@ -464,7 +468,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
ListenableFuture<List<TsKvEntry>> missingTsData = tsService.findLatest(ctx.getTenantId(), entityData.getEntityId(), missingTsKeys); ListenableFuture<List<TsKvEntry>> missingTsData = tsService.findLatest(ctx.getTenantId(), entityData.getEntityId(), missingTsKeys);
missingTelemetryFutures.put(entityData, Futures.transform(missingTsData, this::toTsValue, MoreExecutors.directExecutor())); missingTelemetryFutures.put(entityData, Futures.transform(missingTsData, this::toTsValue, MoreExecutors.directExecutor()));
} }
Futures.addCallback(Futures.allAsList(missingTelemetryFutures.values()), new FutureCallback<List<Map<String, TsValue>>>() { Futures.addCallback(Futures.allAsList(missingTelemetryFutures.values()), new FutureCallback<>() {
@Override @Override
public void onSuccess(@Nullable List<Map<String, TsValue>> result) { public void onSuccess(@Nullable List<Map<String, TsValue>> result) {
missingTelemetryFutures.forEach((key, value) -> { missingTelemetryFutures.forEach((key, value) -> {
@ -475,30 +479,39 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
} }
}); });
EntityDataUpdate update; EntityDataUpdate update;
if (!ctx.isInitialDataSent()) { ctx.getWsLock().lock();
update = new EntityDataUpdate(ctx.getCmdId(), ctx.getData(), null, ctx.getMaxEntitiesPerDataSubscription()); try {
ctx.setInitialDataSent(true); ctx.createLatestValuesSubscriptions(latestCmd.getKeys());
} else { if (!ctx.isInitialDataSent()) {
update = new EntityDataUpdate(ctx.getCmdId(), null, ctx.getData().getData(), ctx.getMaxEntitiesPerDataSubscription()); update = new EntityDataUpdate(ctx.getCmdId(), ctx.getData(), null, ctx.getMaxEntitiesPerDataSubscription());
ctx.setInitialDataSent(true);
} else {
update = new EntityDataUpdate(ctx.getCmdId(), null, ctx.getData().getData(), ctx.getMaxEntitiesPerDataSubscription());
}
ctx.sendWsMsg(update);
} finally {
ctx.getWsLock().unlock();
} }
wsService.sendWsMsg(ctx.getSessionId(), update);
ctx.createLatestValuesSubscriptions(latestCmd.getKeys());
} }
@Override @Override
public void onFailure(Throwable t) { public void onFailure(Throwable t) {
log.warn("[{}][{}] Failed to process websocket command: {}:{}", ctx.getSessionId(), ctx.getCmdId(), ctx.getQuery(), latestCmd, t); log.warn("[{}][{}] Failed to process websocket command: {}:{}", ctx.getSessionId(), ctx.getCmdId(), ctx.getQuery(), latestCmd, t);
wsService.sendWsMsg(ctx.getSessionId(), ctx.sendWsMsg(new EntityDataUpdate(ctx.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR.getCode(), "Failed to process websocket command!"));
new EntityDataUpdate(ctx.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR.getCode(), "Failed to process websocket command!"));
} }
}, wsCallBackExecutor); }, wsCallBackExecutor);
} else { } else {
if (!ctx.isInitialDataSent()) { ctx.getWsLock().lock();
EntityDataUpdate update = new EntityDataUpdate(ctx.getCmdId(), ctx.getData(), null, ctx.getMaxEntitiesPerDataSubscription()); try {
wsService.sendWsMsg(ctx.getSessionId(), update); ctx.createLatestValuesSubscriptions(latestCmd.getKeys());
ctx.setInitialDataSent(true); if (!ctx.isInitialDataSent()) {
EntityDataUpdate update = new EntityDataUpdate(ctx.getCmdId(), ctx.getData(), null, ctx.getMaxEntitiesPerDataSubscription());
ctx.sendWsMsg(update);
ctx.setInitialDataSent(true);
}
} finally {
ctx.getWsLock().unlock();
} }
ctx.createLatestValuesSubscriptions(latestCmd.getKeys());
} }
} }

16
application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractSubCtx.java

@ -41,6 +41,7 @@ import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.entity.EntityService; import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.service.telemetry.TelemetryWebSocketService; import org.thingsboard.server.service.telemetry.TelemetryWebSocketService;
import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef; import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef;
import org.thingsboard.server.service.telemetry.cmd.v2.CmdUpdate;
import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate; import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate;
import java.util.ArrayList; import java.util.ArrayList;
@ -52,14 +53,18 @@ import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j @Slf4j
@Data @Data
public abstract class TbAbstractSubCtx<T extends EntityCountQuery> { public abstract class TbAbstractSubCtx<T extends EntityCountQuery> {
@Getter
protected final Lock wsLock = new ReentrantLock(true);
protected final String serviceId; protected final String serviceId;
protected final SubscriptionServiceStatistics stats; protected final SubscriptionServiceStatistics stats;
protected final TelemetryWebSocketService wsService; private final TelemetryWebSocketService wsService;
protected final EntityService entityService; protected final EntityService entityService;
protected final TbLocalSubscriptionService localSubscriptionService; protected final TbLocalSubscriptionService localSubscriptionService;
protected final AttributesService attributesService; protected final AttributesService attributesService;
@ -314,4 +319,13 @@ public abstract class TbAbstractSubCtx<T extends EntityCountQuery> {
private final String sourceAttribute; private final String sourceAttribute;
} }
public void sendWsMsg(CmdUpdate update) {
wsLock.lock();
try {
wsService.sendWsMsg(sessionRef.getSessionId(), update);
} finally {
wsLock.unlock();
}
}
} }

6
application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java

@ -117,7 +117,7 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx<AlarmDataQuery> {
} else { } else {
update = new AlarmDataUpdate(cmdId, new PageData<>(), null, maxEntitiesPerAlarmSubscription, data.getTotalElements()); update = new AlarmDataUpdate(cmdId, new PageData<>(), null, maxEntitiesPerAlarmSubscription, data.getTotalElements());
} }
wsService.sendWsMsg(getSessionId(), update); sendWsMsg(update);
} }
public void fetchData() { public void fetchData() {
@ -198,7 +198,7 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx<AlarmDataQuery> {
return alarm; return alarm;
}).collect(Collectors.toList()); }).collect(Collectors.toList());
if (!update.isEmpty()) { if (!update.isEmpty()) {
wsService.sendWsMsg(sessionId, new AlarmDataUpdate(cmdId, null, update, maxEntitiesPerAlarmSubscription, data.getTotalElements())); sendWsMsg(new AlarmDataUpdate(cmdId, null, update, maxEntitiesPerAlarmSubscription, data.getTotalElements()));
} }
} else { } else {
log.trace("[{}][{}][{}][{}] Received stale subscription update: {}", sessionId, cmdId, subscriptionUpdate.getSubscriptionId(), keyType, subscriptionUpdate); log.trace("[{}][{}][{}][{}] Received stale subscription update: {}", sessionId, cmdId, subscriptionUpdate.getSubscriptionId(), keyType, subscriptionUpdate);
@ -222,7 +222,7 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx<AlarmDataQuery> {
AlarmData updated = new AlarmData(alarm, current.getOriginatorName(), current.getEntityId()); AlarmData updated = new AlarmData(alarm, current.getOriginatorName(), current.getEntityId());
updated.getLatest().putAll(current.getLatest()); updated.getLatest().putAll(current.getLatest());
alarmsMap.put(alarmId, updated); alarmsMap.put(alarmId, updated);
wsService.sendWsMsg(sessionId, new AlarmDataUpdate(cmdId, null, Collections.singletonList(updated), maxEntitiesPerAlarmSubscription, data.getTotalElements())); sendWsMsg(new AlarmDataUpdate(cmdId, null, Collections.singletonList(updated), maxEntitiesPerAlarmSubscription, data.getTotalElements()));
} else { } else {
fetchAlarms(); fetchAlarms();
} }

7
application/src/main/java/org/thingsboard/server/service/subscription/TbEntityCountSubCtx.java

@ -17,14 +17,11 @@ package org.thingsboard.server.service.subscription;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.query.EntityCountQuery; import org.thingsboard.server.common.data.query.EntityCountQuery;
import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.entity.EntityService; import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.service.telemetry.TelemetryWebSocketService; import org.thingsboard.server.service.telemetry.TelemetryWebSocketService;
import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef; import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountUpdate; import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountUpdate;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate;
import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate;
@Slf4j @Slf4j
public class TbEntityCountSubCtx extends TbAbstractSubCtx<EntityCountQuery> { public class TbEntityCountSubCtx extends TbAbstractSubCtx<EntityCountQuery> {
@ -40,7 +37,7 @@ public class TbEntityCountSubCtx extends TbAbstractSubCtx<EntityCountQuery> {
@Override @Override
public void fetchData() { public void fetchData() {
result = (int) entityService.countEntitiesByQuery(getTenantId(), getCustomerId(), query); result = (int) entityService.countEntitiesByQuery(getTenantId(), getCustomerId(), query);
wsService.sendWsMsg(sessionRef.getSessionId(), new EntityCountUpdate(cmdId, result)); sendWsMsg(new EntityCountUpdate(cmdId, result));
} }
@Override @Override
@ -48,7 +45,7 @@ public class TbEntityCountSubCtx extends TbAbstractSubCtx<EntityCountQuery> {
int newCount = (int) entityService.countEntitiesByQuery(getTenantId(), getCustomerId(), query); int newCount = (int) entityService.countEntitiesByQuery(getTenantId(), getCustomerId(), query);
if (newCount != result) { if (newCount != result) {
result = newCount; result = newCount;
wsService.sendWsMsg(sessionRef.getSessionId(), new EntityCountUpdate(cmdId, result)); sendWsMsg(new EntityCountUpdate(cmdId, result));
} }
} }

8
application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubCtx.java

@ -51,7 +51,7 @@ public class TbEntityDataSubCtx extends TbAbstractDataSubCtx<EntityDataQuery> {
@Getter @Getter
@Setter @Setter
private boolean initialDataSent; private volatile boolean initialDataSent;
private TimeSeriesCmd curTsCmd; private TimeSeriesCmd curTsCmd;
private LatestValueCmd latestValueCmd; private LatestValueCmd latestValueCmd;
@Getter @Getter
@ -121,7 +121,7 @@ public class TbEntityDataSubCtx extends TbAbstractDataSubCtx<EntityDataQuery> {
if (!latestUpdate.isEmpty()) { if (!latestUpdate.isEmpty()) {
Map<EntityKeyType, Map<String, TsValue>> latestMap = Collections.singletonMap(keyType, latestUpdate); Map<EntityKeyType, Map<String, TsValue>> latestMap = Collections.singletonMap(keyType, latestUpdate);
entityData = new EntityData(entityId, latestMap, null); entityData = new EntityData(entityId, latestMap, null);
wsService.sendWsMsg(sessionId, new EntityDataUpdate(cmdId, null, Collections.singletonList(entityData), maxEntitiesPerDataSubscription)); sendWsMsg(new EntityDataUpdate(cmdId, null, Collections.singletonList(entityData), maxEntitiesPerDataSubscription));
} }
} }
@ -162,7 +162,7 @@ public class TbEntityDataSubCtx extends TbAbstractDataSubCtx<EntityDataQuery> {
Map<String, TsValue[]> tsMap = new HashMap<>(); Map<String, TsValue[]> tsMap = new HashMap<>();
tsUpdate.forEach((key, tsValue) -> tsMap.put(key, tsValue.toArray(new TsValue[tsValue.size()]))); tsUpdate.forEach((key, tsValue) -> tsMap.put(key, tsValue.toArray(new TsValue[tsValue.size()])));
EntityData entityData = new EntityData(entityId, null, tsMap); EntityData entityData = new EntityData(entityId, null, tsMap);
wsService.sendWsMsg(sessionId, new EntityDataUpdate(cmdId, null, Collections.singletonList(entityData), maxEntitiesPerDataSubscription)); sendWsMsg(new EntityDataUpdate(cmdId, null, Collections.singletonList(entityData), maxEntitiesPerDataSubscription));
} }
} }
@ -219,9 +219,9 @@ public class TbEntityDataSubCtx extends TbAbstractDataSubCtx<EntityDataQuery> {
} }
} }
} }
wsService.sendWsMsg(sessionRef.getSessionId(), new EntityDataUpdate(cmdId, data, null, maxEntitiesPerDataSubscription));
subIdsToCancel.forEach(subId -> localSubscriptionService.cancelSubscription(getSessionId(), subId)); subIdsToCancel.forEach(subId -> localSubscriptionService.cancelSubscription(getSessionId(), subId));
subsToAdd.forEach(localSubscriptionService::addSubscription); subsToAdd.forEach(localSubscriptionService::addSubscription);
sendWsMsg(new EntityDataUpdate(cmdId, data, null, maxEntitiesPerDataSubscription));
} }
public void setCurrentCmd(EntityDataCmd cmd) { public void setCurrentCmd(EntityDataCmd cmd) {

6
application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java

@ -442,7 +442,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
private void handleWsAttributesSubscriptionByKeys(TelemetryWebSocketSessionRef sessionRef, private void handleWsAttributesSubscriptionByKeys(TelemetryWebSocketSessionRef sessionRef,
AttributesSubscriptionCmd cmd, String sessionId, EntityId entityId, AttributesSubscriptionCmd cmd, String sessionId, EntityId entityId,
List<String> keys) { List<String> keys) {
FutureCallback<List<AttributeKvEntry>> callback = new FutureCallback<List<AttributeKvEntry>>() { FutureCallback<List<AttributeKvEntry>> callback = new FutureCallback<>() {
@Override @Override
public void onSuccess(List<AttributeKvEntry> data) { public void onSuccess(List<AttributeKvEntry> data) {
List<TsKvEntry> attributesData = data.stream().map(d -> new BasicTsKvEntry(d.getLastUpdateTs(), d)).collect(Collectors.toList()); List<TsKvEntry> attributesData = data.stream().map(d -> new BasicTsKvEntry(d.getLastUpdateTs(), d)).collect(Collectors.toList());
@ -542,7 +542,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
private void handleWsAttributesSubscription(TelemetryWebSocketSessionRef sessionRef, private void handleWsAttributesSubscription(TelemetryWebSocketSessionRef sessionRef,
AttributesSubscriptionCmd cmd, String sessionId, EntityId entityId) { AttributesSubscriptionCmd cmd, String sessionId, EntityId entityId) {
FutureCallback<List<AttributeKvEntry>> callback = new FutureCallback<List<AttributeKvEntry>>() { FutureCallback<List<AttributeKvEntry>> callback = new FutureCallback<>() {
@Override @Override
public void onSuccess(List<AttributeKvEntry> data) { public void onSuccess(List<AttributeKvEntry> data) {
List<TsKvEntry> attributesData = data.stream().map(d -> new BasicTsKvEntry(d.getLastUpdateTs(), d)).collect(Collectors.toList()); List<TsKvEntry> attributesData = data.stream().map(d -> new BasicTsKvEntry(d.getLastUpdateTs(), d)).collect(Collectors.toList());
@ -666,7 +666,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
} }
private FutureCallback<List<TsKvEntry>> getSubscriptionCallback(final TelemetryWebSocketSessionRef sessionRef, final TimeseriesSubscriptionCmd cmd, final String sessionId, final EntityId entityId, final long startTs, final List<String> keys) { private FutureCallback<List<TsKvEntry>> getSubscriptionCallback(final TelemetryWebSocketSessionRef sessionRef, final TimeseriesSubscriptionCmd cmd, final String sessionId, final EntityId entityId, final long startTs, final List<String> keys) {
return new FutureCallback<List<TsKvEntry>>() { return new FutureCallback<>() {
@Override @Override
public void onSuccess(List<TsKvEntry> data) { public void onSuccess(List<TsKvEntry> data) {
sendWsMsg(sessionRef, new TelemetrySubscriptionUpdate(cmd.getCmdId(), data)); sendWsMsg(sessionRef, new TelemetrySubscriptionUpdate(cmd.getCmdId(), data));

2
application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java

@ -298,7 +298,7 @@ public class DefaultTransportApiService implements TransportApiService {
tbClusterService.onDeviceUpdated(savedDevice, null); tbClusterService.onDeviceUpdated(savedDevice, null);
device = savedDevice; device = savedDevice;
relationService.saveRelationAsync(TenantId.SYS_TENANT_ID, new EntityRelation(gateway.getId(), device.getId(), "Created")); relationService.saveRelation(TenantId.SYS_TENANT_ID, new EntityRelation(gateway.getId(), device.getId(), "Created"));
TbMsgMetaData metaData = new TbMsgMetaData(); TbMsgMetaData metaData = new TbMsgMetaData();
CustomerId customerId = gateway.getCustomerId(); CustomerId customerId = gateway.getCustomerId();

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

@ -62,6 +62,7 @@ import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static java.util.concurrent.TimeUnit.HOURS;
import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS; import static java.util.concurrent.TimeUnit.SECONDS;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -368,7 +369,7 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
public void testTheCopyOfAttrsIntoTSForTheView() throws Exception { public void testTheCopyOfAttrsIntoTSForTheView() throws Exception {
Set<String> expectedActualAttributesSet = Set.of("caKey1", "caKey2", "caKey3", "caKey4"); Set<String> expectedActualAttributesSet = Set.of("caKey1", "caKey2", "caKey3", "caKey4");
Set<String> actualAttributesSet = Set<String> actualAttributesSet =
getAttributesByKeys("{\"caKey1\":\"value1\", \"caKey2\":true, \"caKey3\":42.0, \"caKey4\":73}", expectedActualAttributesSet); putAttributesAndWait("{\"caKey1\":\"value1\", \"caKey2\":true, \"caKey3\":42.0, \"caKey4\":73}", expectedActualAttributesSet);
log.debug("got correct actualAttributesSet, saving new entity view..."); log.debug("got correct actualAttributesSet, saving new entity view...");
EntityView savedView = getNewSavedEntityView("Test entity view"); EntityView savedView = getNewSavedEntityView("Test entity view");
@ -389,13 +390,15 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
@Test @Test
public void testTheCopyOfAttrsOutOfTSForTheView() throws Exception { public void testTheCopyOfAttrsOutOfTSForTheView() throws Exception {
long now = System.currentTimeMillis();
Set<String> expectedActualAttributesSet = Set.of("caKey1", "caKey2", "caKey3", "caKey4"); Set<String> expectedActualAttributesSet = Set.of("caKey1", "caKey2", "caKey3", "caKey4");
Set<String> actualAttributesSet = Set<String> actualAttributesSet =
getAttributesByKeys("{\"caKey1\":\"value1\", \"caKey2\":true, \"caKey3\":42.0, \"caKey4\":73}", expectedActualAttributesSet); putAttributesAndWait("{\"caKey1\":\"value1\", \"caKey2\":true, \"caKey3\":42.0, \"caKey4\":73}", expectedActualAttributesSet);
List<Map<String, Object>> valueTelemetryOfDevices = doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + testDevice.getId().getId().toString() + List<Map<String, Object>> values = doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + testDevice.getId() +
"/values/attributes?keys=" + String.join(",", actualAttributesSet), new TypeReference<>() { "/values/attributes?keys=" + String.join(",", expectedActualAttributesSet), new TypeReference<>() {
}); });
assertEquals(expectedActualAttributesSet.size(), values.size());
EntityView view = new EntityView(); EntityView view = new EntityView();
view.setEntityId(testDevice.getId()); view.setEntityId(testDevice.getId());
@ -403,12 +406,12 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
view.setName("Test entity view"); view.setName("Test entity view");
view.setType("default"); view.setType("default");
view.setKeys(telemetry); view.setKeys(telemetry);
view.setStartTimeMs((long) getValue(valueTelemetryOfDevices, "lastActivityTime") * 10); view.setStartTimeMs(now - HOURS.toMillis(1));
view.setEndTimeMs((long) getValue(valueTelemetryOfDevices, "lastActivityTime") / 10); view.setEndTimeMs(now - 1);
EntityView savedView = doPost("/api/entityView", view, EntityView.class); EntityView savedView = doPost("/api/entityView", view, EntityView.class);
List<Map<String, Object>> values = doGetAsyncTyped("/api/plugins/telemetry/ENTITY_VIEW/" + savedView.getId().getId().toString() + values = doGetAsyncTyped("/api/plugins/telemetry/ENTITY_VIEW/" + savedView.getId().getId().toString() +
"/values/attributes?keys=" + String.join(",", actualAttributesSet), new TypeReference<>() { "/values/attributes?keys=" + String.join(",", expectedActualAttributesSet), new TypeReference<>() {
}); });
assertEquals(0, values.size()); assertEquals(0, values.size());
} }
@ -431,19 +434,19 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
uploadTelemetry("{\"tsKey1\":\"value1\", \"tsKey2\":true, \"tsKey3\":40.0}", accessToken); uploadTelemetry("{\"tsKey1\":\"value1\", \"tsKey2\":true, \"tsKey3\":40.0}", accessToken);
getWsClient().waitForUpdate(); getWsClient().waitForUpdate();
long startTimeMs = System.currentTimeMillis(); long startTimeMs = getCurTsButNotPrevTs(now);
getWsClient().registerWaitForUpdate(); getWsClient().registerWaitForUpdate();
uploadTelemetry("{\"tsKey1\":\"value2\", \"tsKey2\":false, \"tsKey3\":80.0}", accessToken); uploadTelemetry("{\"tsKey1\":\"value2\", \"tsKey2\":false, \"tsKey3\":80.0}", accessToken);
getWsClient().waitForUpdate(); getWsClient().waitForUpdate();
Thread.sleep(3); long middleOfTestMs = getCurTsButNotPrevTs(startTimeMs);
getWsClient().registerWaitForUpdate(); getWsClient().registerWaitForUpdate();
uploadTelemetry("{\"tsKey1\":\"value3\", \"tsKey2\":false, \"tsKey3\":120.0}", accessToken); uploadTelemetry("{\"tsKey1\":\"value3\", \"tsKey2\":false, \"tsKey3\":120.0}", accessToken);
getWsClient().waitForUpdate(); getWsClient().waitForUpdate();
long endTimeMs = System.currentTimeMillis(); long endTimeMs = getCurTsButNotPrevTs(middleOfTestMs);
getWsClient().registerWaitForUpdate(); getWsClient().registerWaitForUpdate();
uploadTelemetry("{\"tsKey1\":\"value4\", \"tsKey2\":true, \"tsKey3\":160.0}", accessToken); uploadTelemetry("{\"tsKey1\":\"value4\", \"tsKey2\":true, \"tsKey3\":160.0}", accessToken);
getWsClient().waitForUpdate(); getWsClient().waitForUpdate();
@ -455,15 +458,25 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
EntityView savedView = doPost("/api/entityView", view, EntityView.class); EntityView savedView = doPost("/api/entityView", view, EntityView.class);
String entityViewId = savedView.getId().getId().toString(); String entityViewId = savedView.getId().getId().toString();
Map<String, List<Map<String, String>>> expectedValues = getTelemetryValues("DEVICE", deviceId, keys, 0L, (startTimeMs + endTimeMs) / 2); Map<String, List<Map<String, String>>> actualDeviceValues = getTelemetryValues("DEVICE", deviceId, keys, 0L, middleOfTestMs);
Assert.assertEquals(2, expectedValues.get("tsKey1").size()); Assert.assertEquals(2, actualDeviceValues.get("tsKey1").size());
Assert.assertEquals(2, expectedValues.get("tsKey2").size()); Assert.assertEquals(2, actualDeviceValues.get("tsKey2").size());
Assert.assertEquals(2, expectedValues.get("tsKey3").size()); Assert.assertEquals(2, actualDeviceValues.get("tsKey3").size());
Map<String, List<Map<String, String>>> actualEntityViewValues = getTelemetryValues("ENTITY_VIEW", entityViewId, keys, 0L, middleOfTestMs);
Assert.assertEquals(1, actualEntityViewValues.get("tsKey1").size());
Assert.assertEquals(1, actualEntityViewValues.get("tsKey2").size());
Assert.assertEquals(1, actualEntityViewValues.get("tsKey3").size());
}
Map<String, List<Map<String, String>>> actualValues = getTelemetryValues("ENTITY_VIEW", entityViewId, keys, 0L, (startTimeMs + endTimeMs) / 2); private static long getCurTsButNotPrevTs(long prevTs) throws InterruptedException {
Assert.assertEquals(1, actualValues.get("tsKey1").size()); long result = System.currentTimeMillis();
Assert.assertEquals(1, actualValues.get("tsKey2").size()); if (prevTs == result) {
Assert.assertEquals(1, actualValues.get("tsKey3").size()); Thread.sleep(1);
return getCurTsButNotPrevTs(prevTs);
} else {
return result;
}
} }
private void uploadTelemetry(String strKvs, String accessToken) throws Exception { private void uploadTelemetry(String strKvs, String accessToken) throws Exception {
@ -504,7 +517,7 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
}); });
} }
private Set<String> getAttributesByKeys(String stringKV, Set<String> expectedKeySet) throws Exception { private Set<String> putAttributesAndWait(String stringKV, Set<String> expectedKeySet) throws Exception {
DeviceTypeFilter dtf = new DeviceTypeFilter(testDevice.getType(), testDevice.getName()); DeviceTypeFilter dtf = new DeviceTypeFilter(testDevice.getType(), testDevice.getName());
List<EntityKey> keysToSubscribe = expectedKeySet.stream() List<EntityKey> keysToSubscribe = expectedKeySet.stream()
.map(key -> new EntityKey(EntityKeyType.CLIENT_ATTRIBUTE, key)) .map(key -> new EntityKey(EntityKeyType.CLIENT_ATTRIBUTE, key))

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

@ -102,7 +102,6 @@ public abstract class BaseWebsocketApiTest extends AbstractControllerTest {
List<TsKvEntry> tsData = Arrays.asList(dataPoint1, dataPoint2, dataPoint3); List<TsKvEntry> tsData = Arrays.asList(dataPoint1, dataPoint2, dataPoint3);
sendTelemetry(device, tsData); sendTelemetry(device, tsData);
Thread.sleep(100);
update = getWsClient().sendHistoryCmd(keys, now, TimeUnit.HOURS.toMillis(1), dtf); update = getWsClient().sendHistoryCmd(keys, now, TimeUnit.HOURS.toMillis(1), dtf);
@ -136,7 +135,6 @@ public abstract class BaseWebsocketApiTest extends AbstractControllerTest {
List<TsKvEntry> tsData = Arrays.asList(dataPoint1, dataPoint2, dataPoint3); List<TsKvEntry> tsData = Arrays.asList(dataPoint1, dataPoint2, dataPoint3);
sendTelemetry(device, tsData); sendTelemetry(device, tsData);
Thread.sleep(100);
update = getWsClient().subscribeTsUpdate(List.of("temperature"), now, TimeUnit.HOURS.toMillis(1)); update = getWsClient().subscribeTsUpdate(List.of("temperature"), now, TimeUnit.HOURS.toMillis(1));
Assert.assertEquals(1, update.getCmdId()); Assert.assertEquals(1, update.getCmdId());
@ -153,7 +151,6 @@ public abstract class BaseWebsocketApiTest extends AbstractControllerTest {
now = System.currentTimeMillis(); now = System.currentTimeMillis();
TsKvEntry dataPoint4 = new BasicTsKvEntry(now, new LongDataEntry("temperature", 45L)); TsKvEntry dataPoint4 = new BasicTsKvEntry(now, new LongDataEntry("temperature", 45L));
getWsClient().registerWaitForUpdate(); getWsClient().registerWaitForUpdate();
Thread.sleep(100);
sendTelemetry(device, Arrays.asList(dataPoint4)); sendTelemetry(device, Arrays.asList(dataPoint4));
String msg = getWsClient().waitForUpdate(); String msg = getWsClient().waitForUpdate();
@ -309,13 +306,12 @@ public abstract class BaseWebsocketApiTest extends AbstractControllerTest {
Assert.assertEquals(0, pageData.getData().get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("temperature").getTs()); Assert.assertEquals(0, pageData.getData().get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("temperature").getTs());
Assert.assertEquals("", pageData.getData().get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("temperature").getValue()); Assert.assertEquals("", pageData.getData().get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("temperature").getValue());
getWsClient().registerWaitForUpdate();
TsKvEntry dataPoint1 = new BasicTsKvEntry(now - TimeUnit.MINUTES.toMillis(1), new LongDataEntry("temperature", 42L)); TsKvEntry dataPoint1 = new BasicTsKvEntry(now - TimeUnit.MINUTES.toMillis(1), new LongDataEntry("temperature", 42L));
List<TsKvEntry> tsData = Arrays.asList(dataPoint1); List<TsKvEntry> tsData = Arrays.asList(dataPoint1);
sendTelemetry(device, tsData); sendTelemetry(device, tsData);
Thread.sleep(100); update = getWsClient().parseDataReply(getWsClient().waitForUpdate());
update = getWsClient().subscribeLatestUpdate(keys, dtf);
Assert.assertEquals(1, update.getCmdId()); Assert.assertEquals(1, update.getCmdId());
@ -329,7 +325,6 @@ public abstract class BaseWebsocketApiTest extends AbstractControllerTest {
now = System.currentTimeMillis(); now = System.currentTimeMillis();
TsKvEntry dataPoint2 = new BasicTsKvEntry(now, new LongDataEntry("temperature", 52L)); TsKvEntry dataPoint2 = new BasicTsKvEntry(now, new LongDataEntry("temperature", 52L));
getWsClient().registerWaitForUpdate(); getWsClient().registerWaitForUpdate();
sendTelemetry(device, Arrays.asList(dataPoint2)); sendTelemetry(device, Arrays.asList(dataPoint2));
update = getWsClient().parseDataReply(getWsClient().waitForUpdate()); update = getWsClient().parseDataReply(getWsClient().waitForUpdate());
@ -371,7 +366,6 @@ public abstract class BaseWebsocketApiTest extends AbstractControllerTest {
Assert.assertEquals("", pageData.getData().get(0).getLatest().get(EntityKeyType.SERVER_ATTRIBUTE).get("serverAttributeKey").getValue()); Assert.assertEquals("", pageData.getData().get(0).getLatest().get(EntityKeyType.SERVER_ATTRIBUTE).get("serverAttributeKey").getValue());
getWsClient().registerWaitForUpdate(); getWsClient().registerWaitForUpdate();
Thread.sleep(500);
AttributeKvEntry dataPoint1 = new BaseAttributeKvEntry(now - TimeUnit.MINUTES.toMillis(1), new LongDataEntry("serverAttributeKey", 42L)); AttributeKvEntry dataPoint1 = new BaseAttributeKvEntry(now - TimeUnit.MINUTES.toMillis(1), new LongDataEntry("serverAttributeKey", 42L));
List<AttributeKvEntry> tsData = Arrays.asList(dataPoint1); List<AttributeKvEntry> tsData = Arrays.asList(dataPoint1);
@ -394,7 +388,6 @@ public abstract class BaseWebsocketApiTest extends AbstractControllerTest {
AttributeKvEntry dataPoint2 = new BaseAttributeKvEntry(now, new LongDataEntry("serverAttributeKey", 52L)); AttributeKvEntry dataPoint2 = new BaseAttributeKvEntry(now, new LongDataEntry("serverAttributeKey", 52L));
getWsClient().registerWaitForUpdate(); getWsClient().registerWaitForUpdate();
Thread.sleep(500);
sendAttributes(device, TbAttributeSubscriptionScope.SERVER_SCOPE, Arrays.asList(dataPoint2)); sendAttributes(device, TbAttributeSubscriptionScope.SERVER_SCOPE, Arrays.asList(dataPoint2));
msg = getWsClient().waitForUpdate(); msg = getWsClient().waitForUpdate();
Assert.assertNotNull(msg); Assert.assertNotNull(msg);
@ -411,14 +404,12 @@ public abstract class BaseWebsocketApiTest extends AbstractControllerTest {
//Sending update from the past, while latest value has new timestamp; //Sending update from the past, while latest value has new timestamp;
getWsClient().registerWaitForUpdate(); getWsClient().registerWaitForUpdate();
Thread.sleep(500);
sendAttributes(device, TbAttributeSubscriptionScope.SERVER_SCOPE, Arrays.asList(dataPoint1)); sendAttributes(device, TbAttributeSubscriptionScope.SERVER_SCOPE, Arrays.asList(dataPoint1));
msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1)); msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1));
Assert.assertNull(msg); Assert.assertNull(msg);
//Sending duplicate update again //Sending duplicate update again
getWsClient().registerWaitForUpdate(); getWsClient().registerWaitForUpdate();
Thread.sleep(500);
sendAttributes(device, TbAttributeSubscriptionScope.SERVER_SCOPE, Arrays.asList(dataPoint2)); sendAttributes(device, TbAttributeSubscriptionScope.SERVER_SCOPE, Arrays.asList(dataPoint2));
msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1)); msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1));
Assert.assertNull(msg); Assert.assertNull(msg);
@ -456,7 +447,6 @@ public abstract class BaseWebsocketApiTest extends AbstractControllerTest {
getWsClient().registerWaitForUpdate(); getWsClient().registerWaitForUpdate();
AttributeKvEntry dataPoint1 = new BaseAttributeKvEntry(now - TimeUnit.MINUTES.toMillis(1), new LongDataEntry("serverAttributeKey", 42L)); AttributeKvEntry dataPoint1 = new BaseAttributeKvEntry(now - TimeUnit.MINUTES.toMillis(1), new LongDataEntry("serverAttributeKey", 42L));
List<AttributeKvEntry> tsData = Arrays.asList(dataPoint1); List<AttributeKvEntry> tsData = Arrays.asList(dataPoint1);
Thread.sleep(100);
sendAttributes(device, TbAttributeSubscriptionScope.SERVER_SCOPE, tsData); sendAttributes(device, TbAttributeSubscriptionScope.SERVER_SCOPE, tsData);

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

@ -44,8 +44,8 @@ import java.util.concurrent.TimeUnit;
public class TbTestWebSocketClient extends WebSocketClient { public class TbTestWebSocketClient extends WebSocketClient {
private volatile String lastMsg; private volatile String lastMsg;
private CountDownLatch reply; private volatile CountDownLatch reply;
private CountDownLatch update; private volatile CountDownLatch update;
public TbTestWebSocketClient(URI serverUri) { public TbTestWebSocketClient(URI serverUri) {
super(serverUri); super(serverUri);
@ -60,12 +60,12 @@ public class TbTestWebSocketClient extends WebSocketClient {
public void onMessage(String s) { public void onMessage(String s) {
log.info("RECEIVED: {}", s); log.info("RECEIVED: {}", s);
lastMsg = s; lastMsg = s;
if (reply != null) {
reply.countDown();
}
if (update != null) { if (update != null) {
update.countDown(); update.countDown();
} }
if (reply != null) {
reply.countDown();
}
} }
@Override @Override
@ -79,8 +79,12 @@ public class TbTestWebSocketClient extends WebSocketClient {
} }
public void registerWaitForUpdate() { public void registerWaitForUpdate() {
registerWaitForUpdate(1);
}
public void registerWaitForUpdate(int count) {
lastMsg = null; lastMsg = null;
update = new CountDownLatch(1); update = new CountDownLatch(count);
} }
@Override @Override

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

@ -52,6 +52,7 @@ import static org.junit.Assert.assertNotNull;
@TestPropertySource(properties = { @TestPropertySource(properties = {
"transport.mqtt.enabled=true", "transport.mqtt.enabled=true",
"js.evaluator=mock",
}) })
@Slf4j @Slf4j
public abstract class AbstractMqttIntegrationTest extends AbstractTransportIntegrationTest { public abstract class AbstractMqttIntegrationTest extends AbstractTransportIntegrationTest {
@ -83,22 +84,6 @@ public abstract class AbstractMqttIntegrationTest extends AbstractTransportInteg
} }
} }
protected MqttAsyncClient getMqttAsyncClient(String accessToken) throws MqttException {
String clientId = MqttAsyncClient.generateClientId();
MqttAsyncClient client = new MqttAsyncClient(MQTT_URL, clientId, new MemoryPersistence());
MqttConnectOptions options = new MqttConnectOptions();
options.setUserName(accessToken);
client.connect(options).waitForCompletion();
return client;
}
protected void publishMqttMsg(MqttAsyncClient client, byte[] payload, String topic) throws MqttException {
MqttMessage message = new MqttMessage();
message.setPayload(payload);
client.publish(topic, message);
}
protected DeviceProfile createMqttDeviceProfile(MqttTestConfigProperties config) throws Exception { protected DeviceProfile createMqttDeviceProfile(MqttTestConfigProperties config) throws Exception {
TransportPayloadType transportPayloadType = config.getTransportPayloadType(); TransportPayloadType transportPayloadType = config.getTransportPayloadType();
if (transportPayloadType == null) { if (transportPayloadType == null) {

88
application/src/test/java/org/thingsboard/server/transport/mqtt/MqttTestCallback.java

@ -0,0 +1,88 @@
/**
* 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.transport.mqtt;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.internal.wire.MqttWireMessage;
import java.util.concurrent.CountDownLatch;
@Slf4j
@Data
public class MqttTestCallback implements MqttCallback {
protected CountDownLatch subscribeLatch;
protected final CountDownLatch deliveryLatch;
protected int qoS;
protected byte[] payloadBytes;
protected String awaitSubTopic;
protected boolean pubAckReceived;
public MqttTestCallback() {
this.subscribeLatch = new CountDownLatch(1);
this.deliveryLatch = new CountDownLatch(1);
}
public MqttTestCallback(int subscribeCount) {
this.subscribeLatch = new CountDownLatch(subscribeCount);
this.deliveryLatch = new CountDownLatch(1);
}
public MqttTestCallback(String awaitSubTopic) {
this.subscribeLatch = new CountDownLatch(1);
this.deliveryLatch = new CountDownLatch(1);
this.awaitSubTopic = awaitSubTopic;
}
@Override
public void connectionLost(Throwable throwable) {
log.warn("connectionLost: ", throwable);
deliveryLatch.countDown();
}
@Override
public void messageArrived(String requestTopic, MqttMessage mqttMessage) {
if (awaitSubTopic == null) {
log.warn("messageArrived on topic: {}", requestTopic);
qoS = mqttMessage.getQos();
payloadBytes = mqttMessage.getPayload();
subscribeLatch.countDown();
} else {
messageArrivedOnAwaitSubTopic(requestTopic, mqttMessage);
}
}
protected void messageArrivedOnAwaitSubTopic(String requestTopic, MqttMessage mqttMessage) {
log.warn("messageArrived on topic: {}, awaitSubTopic: {}", requestTopic, awaitSubTopic);
if (awaitSubTopic.equals(requestTopic)) {
qoS = mqttMessage.getQos();
payloadBytes = mqttMessage.getPayload();
subscribeLatch.countDown();
}
}
@Override
public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
log.warn("delivery complete: {}", iMqttDeliveryToken.getResponse());
pubAckReceived = iMqttDeliveryToken.getResponse().getType() == MqttWireMessage.MESSAGE_TYPE_PUBACK;
deliveryLatch.countDown();
}
}

126
application/src/test/java/org/thingsboard/server/transport/mqtt/MqttTestClient.java

@ -0,0 +1,126 @@
/**
* 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.transport.mqtt;
import io.netty.handler.codec.mqtt.MqttQoS;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.IMqttToken;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import org.thingsboard.server.common.data.StringUtils;
import java.util.concurrent.TimeUnit;
public class MqttTestClient {
private static final String MQTT_URL = "tcp://localhost:1883";
private static final int TIMEOUT = 30; // seconds
private static final long TIMEOUT_MS = TimeUnit.SECONDS.toMillis(TIMEOUT);
private final MqttAsyncClient client;
public void setCallback(MqttTestCallback callback) {
client.setCallback(callback);
}
public MqttTestClient() throws MqttException {
this.client = createClient();
}
public MqttTestClient(String clientId) throws MqttException {
this.client = createClient(clientId);
}
public void connectAndWait(String userName, String password) throws MqttException {
IMqttToken connect = connect(userName, password);
connect.waitForCompletion(TIMEOUT_MS);
}
public void connectAndWait(String userName) throws MqttException {
connectAndWait(userName, null);
}
public void connectAndWait() throws MqttException {
connectAndWait(null, null);
}
private IMqttToken connect(String userName, String password) throws MqttException {
if (client == null) {
throw new RuntimeException("Failed to connect! MqttAsyncClient is not initialized!");
}
MqttConnectOptions options = new MqttConnectOptions();
if (StringUtils.isNotEmpty(userName)) {
options.setUserName(userName);
}
if (StringUtils.isNotEmpty(password)) {
options.setPassword(password.toCharArray());
}
return client.connect(options);
}
public void disconnectAndWait() throws MqttException {
disconnect().waitForCompletion(TIMEOUT_MS);
}
public IMqttToken disconnect() throws MqttException {
return client.disconnect();
}
public void disconnectForcibly() throws MqttException {
client.disconnectForcibly(TIMEOUT_MS);
}
public void publishAndWait(String topic, byte[] payload) throws MqttException {
publish(topic, payload).waitForCompletion(TIMEOUT_MS);
}
public IMqttDeliveryToken publish(String topic, byte[] payload) throws MqttException {
MqttMessage message = new MqttMessage();
message.setPayload(payload);
return client.publish(topic, message);
}
public void subscribeAndWait(String topic, MqttQoS qoS) throws MqttException {
subscribe(topic, qoS).waitForCompletion(TIMEOUT_MS);
}
public IMqttToken subscribe(String topic, MqttQoS qoS) throws MqttException {
return client.subscribe(topic, qoS.value());
}
public void enableManualAcks() {
client.setManualAcks(true);
}
public void messageArrivedComplete(MqttMessage mqttMessage) throws MqttException {
client.messageArrivedComplete(mqttMessage.getId(), mqttMessage.getQos());
}
private MqttAsyncClient createClient(String clientId) throws MqttException {
if (StringUtils.isEmpty(clientId)) {
clientId = MqttAsyncClient.generateClientId();
}
return new MqttAsyncClient(MQTT_URL, clientId, new MemoryPersistence());
}
private MqttAsyncClient createClient() throws MqttException {
return createClient(null);
}
}

653
application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/AbstractMqttAttributesIntegrationTest.java

@ -22,35 +22,44 @@ import com.google.protobuf.InvalidProtocolBufferException;
import com.squareup.wire.schema.internal.parser.ProtoFileElement; import com.squareup.wire.schema.internal.parser.ProtoFileElement;
import io.netty.handler.codec.mqtt.MqttQoS; import io.netty.handler.codec.mqtt.MqttQoS;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; import org.springframework.test.context.TestPropertySource;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Device; 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.DeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration;
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.MqttTopics;
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;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.DeviceTypeFilter;
import org.thingsboard.server.common.data.query.EntityData;
import org.thingsboard.server.common.data.query.EntityKey;
import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.common.data.query.SingleEntityFilter;
import org.thingsboard.server.gen.transport.TransportApiProtos; import org.thingsboard.server.gen.transport.TransportApiProtos;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate;
import org.thingsboard.server.transport.mqtt.AbstractMqttIntegrationTest; import org.thingsboard.server.transport.mqtt.AbstractMqttIntegrationTest;
import org.thingsboard.server.transport.mqtt.MqttTestCallback;
import org.thingsboard.server.transport.mqtt.MqttTestClient;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
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;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.GATEWAY_ATTRIBUTES_REQUEST_TOPIC;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.GATEWAY_ATTRIBUTES_RESPONSE_TOPIC;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.GATEWAY_ATTRIBUTES_TOPIC;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.GATEWAY_CONNECT_TOPIC;
import static org.thingsboard.server.common.data.query.EntityKeyType.CLIENT_ATTRIBUTE;
import static org.thingsboard.server.common.data.query.EntityKeyType.SHARED_ATTRIBUTE;
@Slf4j @Slf4j
public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqttIntegrationTest { public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqttIntegrationTest {
@ -60,11 +69,11 @@ public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqtt
"package test;\n" + "package test;\n" +
"\n" + "\n" +
"message PostAttributes {\n" + "message PostAttributes {\n" +
" string attribute1 = 1;\n" + " string clientStr = 1;\n" +
" bool attribute2 = 2;\n" + " bool clientBool = 2;\n" +
" double attribute3 = 3;\n" + " double clientDbl = 3;\n" +
" int32 attribute4 = 4;\n" + " int32 clientLong = 4;\n" +
" JsonObject attribute5 = 5;\n" + " JsonObject clientJson = 5;\n" +
"\n" + "\n" +
" message JsonObject {\n" + " message JsonObject {\n" +
" int32 someNumber = 6;\n" + " int32 someNumber = 6;\n" +
@ -76,17 +85,20 @@ public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqtt
" }\n" + " }\n" +
"}"; "}";
protected static final String POST_ATTRIBUTES_PAYLOAD = "{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73," + private static final String CLIENT_ATTRIBUTES_PAYLOAD = "{\"clientStr\":\"value1\",\"clientBool\":true,\"clientDbl\":42.0,\"clientLong\":73," +
"\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}"; "\"clientJson\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}";
private static final String RESPONSE_ATTRIBUTES_PAYLOAD_DELETED = "{\"deleted\":[\"attribute5\"]}"; private static final String SHARED_ATTRIBUTES_PAYLOAD = "{\"sharedStr\":\"value1\",\"sharedBool\":true,\"sharedDbl\":42.0,\"sharedLong\":73," +
"\"sharedJson\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}";
protected List<TransportProtos.TsKvProto> getTsKvProtoList() { private static final String SHARED_ATTRIBUTES_DELETED_RESPONSE = "{\"deleted\":[\"sharedJson\"]}";
TransportProtos.TsKvProto tsKvProtoAttribute1 = getTsKvProto("attribute1", "value1", TransportProtos.KeyValueType.STRING_V);
TransportProtos.TsKvProto tsKvProtoAttribute2 = getTsKvProto("attribute2", "true", TransportProtos.KeyValueType.BOOLEAN_V); private List<TransportProtos.TsKvProto> getTsKvProtoList(String attributePrefix) {
TransportProtos.TsKvProto tsKvProtoAttribute3 = getTsKvProto("attribute3", "42.0", TransportProtos.KeyValueType.DOUBLE_V); TransportProtos.TsKvProto tsKvProtoAttribute1 = getTsKvProto(attributePrefix + "Str", "value1", TransportProtos.KeyValueType.STRING_V);
TransportProtos.TsKvProto tsKvProtoAttribute4 = getTsKvProto("attribute4", "73", TransportProtos.KeyValueType.LONG_V); TransportProtos.TsKvProto tsKvProtoAttribute2 = getTsKvProto(attributePrefix + "Bool", "true", TransportProtos.KeyValueType.BOOLEAN_V);
TransportProtos.TsKvProto tsKvProtoAttribute5 = getTsKvProto("attribute5", "{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}", TransportProtos.KeyValueType.JSON_V); TransportProtos.TsKvProto tsKvProtoAttribute3 = getTsKvProto(attributePrefix + "Dbl", "42.0", TransportProtos.KeyValueType.DOUBLE_V);
TransportProtos.TsKvProto tsKvProtoAttribute4 = getTsKvProto(attributePrefix + "Long", "73", TransportProtos.KeyValueType.LONG_V);
TransportProtos.TsKvProto tsKvProtoAttribute5 = getTsKvProto(attributePrefix + "Json", "{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}", TransportProtos.KeyValueType.JSON_V);
List<TransportProtos.TsKvProto> tsKvProtoList = new ArrayList<>(); List<TransportProtos.TsKvProto> tsKvProtoList = new ArrayList<>();
tsKvProtoList.add(tsKvProtoAttribute1); tsKvProtoList.add(tsKvProtoAttribute1);
tsKvProtoList.add(tsKvProtoAttribute2); tsKvProtoList.add(tsKvProtoAttribute2);
@ -103,118 +115,56 @@ public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqtt
return tsKvProtoBuilder.build(); return tsKvProtoBuilder.build();
} }
protected TestMqttCallback getTestMqttCallback() {
CountDownLatch latch = new CountDownLatch(1);
return new TestMqttCallback(latch);
}
protected static class TestMqttCallback implements MqttCallback {
private final CountDownLatch latch;
private Integer qoS;
private byte[] payloadBytes;
TestMqttCallback(CountDownLatch latch) {
this.latch = latch;
}
public int getQoS() {
return qoS;
}
public byte[] getPayloadBytes() {
return payloadBytes;
}
public CountDownLatch getLatch() {
return latch;
}
@Override
public void connectionLost(Throwable throwable) {
}
@Override
public void messageArrived(String requestTopic, MqttMessage mqttMessage) throws Exception {
qoS = mqttMessage.getQos();
payloadBytes = mqttMessage.getPayload();
latch.countDown();
}
@Override
public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
}
}
// subscribe to attributes updates from server methods // subscribe to attributes updates from server methods
protected void processJsonTestSubscribeToAttributesUpdates(String attrSubTopic) throws Exception { protected void processJsonTestSubscribeToAttributesUpdates(String attrSubTopic) throws Exception {
MqttTestClient client = new MqttTestClient();
MqttAsyncClient client = getMqttAsyncClient(accessToken); client.connectAndWait(accessToken);
MqttTestCallback onUpdateCallback = new MqttTestCallback();
TestMqttCallback onUpdateCallback = getTestMqttCallback();
client.setCallback(onUpdateCallback); client.setCallback(onUpdateCallback);
client.subscribeAndWait(attrSubTopic, MqttQoS.AT_MOST_ONCE);
client.subscribe(attrSubTopic, MqttQoS.AT_MOST_ONCE.value()); doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", SHARED_ATTRIBUTES_PAYLOAD, String.class, status().isOk());
onUpdateCallback.getSubscribeLatch().await(3, TimeUnit.SECONDS);
Thread.sleep(1000);
doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk()); validateUpdateAttributesJsonResponse(onUpdateCallback, SHARED_ATTRIBUTES_PAYLOAD);
onUpdateCallback.getLatch().await(3, TimeUnit.SECONDS);
validateUpdateAttributesJsonResponse(onUpdateCallback); MqttTestCallback onDeleteCallback = new MqttTestCallback();
TestMqttCallback onDeleteCallback = getTestMqttCallback();
client.setCallback(onDeleteCallback); client.setCallback(onDeleteCallback);
doDelete("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/SHARED_SCOPE?keys=sharedJson", String.class);
doDelete("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/SHARED_SCOPE?keys=attribute5", String.class); onDeleteCallback.getSubscribeLatch().await(3, TimeUnit.SECONDS);
onDeleteCallback.getLatch().await(3, TimeUnit.SECONDS); validateUpdateAttributesJsonResponse(onDeleteCallback, SHARED_ATTRIBUTES_DELETED_RESPONSE);
client.disconnect();
validateDeleteAttributesJsonResponse(onDeleteCallback);
} }
protected void processProtoTestSubscribeToAttributesUpdates(String attrSubTopic) throws Exception { protected void processProtoTestSubscribeToAttributesUpdates(String attrSubTopic) throws Exception {
MqttTestClient client = new MqttTestClient();
MqttAsyncClient client = getMqttAsyncClient(accessToken); client.connectAndWait(accessToken);
MqttTestCallback onUpdateCallback = new MqttTestCallback();
TestMqttCallback onUpdateCallback = getTestMqttCallback();
client.setCallback(onUpdateCallback); client.setCallback(onUpdateCallback);
client.subscribeAndWait(attrSubTopic, MqttQoS.AT_MOST_ONCE);
client.subscribe(attrSubTopic, MqttQoS.AT_MOST_ONCE.value()); doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", SHARED_ATTRIBUTES_PAYLOAD, String.class, status().isOk());
onUpdateCallback.getSubscribeLatch().await(3, TimeUnit.SECONDS);
Thread.sleep(1000);
doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk());
onUpdateCallback.getLatch().await(3, TimeUnit.SECONDS);
validateUpdateAttributesProtoResponse(onUpdateCallback); validateUpdateAttributesProtoResponse(onUpdateCallback);
TestMqttCallback onDeleteCallback = getTestMqttCallback(); MqttTestCallback onDeleteCallback = new MqttTestCallback();
client.setCallback(onDeleteCallback); client.setCallback(onDeleteCallback);
doDelete("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/SHARED_SCOPE?keys=sharedJson", String.class);
doDelete("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/SHARED_SCOPE?keys=attribute5", String.class); onDeleteCallback.getSubscribeLatch().await(3, TimeUnit.SECONDS);
onDeleteCallback.getLatch().await(3, TimeUnit.SECONDS);
validateDeleteAttributesProtoResponse(onDeleteCallback); validateDeleteAttributesProtoResponse(onDeleteCallback);
client.disconnect();
} }
protected void validateUpdateAttributesJsonResponse(TestMqttCallback callback) throws InvalidProtocolBufferException { protected void validateUpdateAttributesJsonResponse(MqttTestCallback callback, String expectedResponse) {
assertNotNull(callback.getPayloadBytes()); assertNotNull(callback.getPayloadBytes());
String response = new String(callback.getPayloadBytes(), StandardCharsets.UTF_8); assertEquals(JacksonUtil.toJsonNode(expectedResponse), JacksonUtil.fromBytes(callback.getPayloadBytes()));
assertEquals(JacksonUtil.toJsonNode(POST_ATTRIBUTES_PAYLOAD), JacksonUtil.toJsonNode(response));
} }
protected void validateDeleteAttributesJsonResponse(TestMqttCallback callback) throws InvalidProtocolBufferException { protected void validateUpdateAttributesProtoResponse(MqttTestCallback callback) throws InvalidProtocolBufferException {
assertNotNull(callback.getPayloadBytes());
String response = new String(callback.getPayloadBytes(), StandardCharsets.UTF_8);
assertEquals(JacksonUtil.toJsonNode(RESPONSE_ATTRIBUTES_PAYLOAD_DELETED), JacksonUtil.toJsonNode(response));
}
protected void validateUpdateAttributesProtoResponse(TestMqttCallback callback) throws InvalidProtocolBufferException {
assertNotNull(callback.getPayloadBytes()); assertNotNull(callback.getPayloadBytes());
TransportProtos.AttributeUpdateNotificationMsg.Builder attributeUpdateNotificationMsgBuilder = TransportProtos.AttributeUpdateNotificationMsg.newBuilder(); TransportProtos.AttributeUpdateNotificationMsg.Builder attributeUpdateNotificationMsgBuilder = TransportProtos.AttributeUpdateNotificationMsg.newBuilder();
List<TransportProtos.TsKvProto> tsKvProtoList = getTsKvProtoList(); List<TransportProtos.TsKvProto> tsKvProtoList = getTsKvProtoList("shared");
attributeUpdateNotificationMsgBuilder.addAllSharedUpdated(tsKvProtoList); attributeUpdateNotificationMsgBuilder.addAllSharedUpdated(tsKvProtoList);
TransportProtos.AttributeUpdateNotificationMsg expectedAttributeUpdateNotificationMsg = attributeUpdateNotificationMsgBuilder.build(); TransportProtos.AttributeUpdateNotificationMsg expectedAttributeUpdateNotificationMsg = attributeUpdateNotificationMsgBuilder.build();
@ -227,134 +177,99 @@ public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqtt
assertTrue(actualSharedUpdatedList.containsAll(expectedSharedUpdatedList)); assertTrue(actualSharedUpdatedList.containsAll(expectedSharedUpdatedList));
} }
protected void validateDeleteAttributesProtoResponse(TestMqttCallback callback) throws InvalidProtocolBufferException { protected void validateDeleteAttributesProtoResponse(MqttTestCallback callback) throws InvalidProtocolBufferException {
assertNotNull(callback.getPayloadBytes()); assertNotNull(callback.getPayloadBytes());
TransportProtos.AttributeUpdateNotificationMsg.Builder attributeUpdateNotificationMsgBuilder = TransportProtos.AttributeUpdateNotificationMsg.newBuilder(); TransportProtos.AttributeUpdateNotificationMsg.Builder attributeUpdateNotificationMsgBuilder = TransportProtos.AttributeUpdateNotificationMsg.newBuilder();
attributeUpdateNotificationMsgBuilder.addSharedDeleted("attribute5"); attributeUpdateNotificationMsgBuilder.addSharedDeleted("sharedJson");
TransportProtos.AttributeUpdateNotificationMsg expectedAttributeUpdateNotificationMsg = attributeUpdateNotificationMsgBuilder.build(); TransportProtos.AttributeUpdateNotificationMsg expectedAttributeUpdateNotificationMsg = attributeUpdateNotificationMsgBuilder.build();
TransportProtos.AttributeUpdateNotificationMsg actualAttributeUpdateNotificationMsg = TransportProtos.AttributeUpdateNotificationMsg.parseFrom(callback.getPayloadBytes()); TransportProtos.AttributeUpdateNotificationMsg actualAttributeUpdateNotificationMsg = TransportProtos.AttributeUpdateNotificationMsg.parseFrom(callback.getPayloadBytes());
assertEquals(expectedAttributeUpdateNotificationMsg.getSharedDeletedList().size(), actualAttributeUpdateNotificationMsg.getSharedDeletedList().size()); assertEquals(expectedAttributeUpdateNotificationMsg.getSharedDeletedList().size(), actualAttributeUpdateNotificationMsg.getSharedDeletedList().size());
assertEquals("attribute5", actualAttributeUpdateNotificationMsg.getSharedDeletedList().get(0)); assertEquals("sharedJson", actualAttributeUpdateNotificationMsg.getSharedDeletedList().get(0));
} }
protected void processJsonGatewayTestSubscribeToAttributesUpdates() throws Exception { protected void processJsonGatewayTestSubscribeToAttributesUpdates() throws Exception {
MqttTestClient client = new MqttTestClient();
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken); client.connectAndWait(gatewayAccessToken);
MqttTestCallback onUpdateCallback = new MqttTestCallback();
TestMqttCallback onUpdateCallback = getTestMqttCallback();
client.setCallback(onUpdateCallback); client.setCallback(onUpdateCallback);
Device device = new Device(); String deviceName = "Gateway Device Subscribe to attribute updates";
device.setName("Gateway Device Subscribe to attribute updates"); byte[] connectPayloadBytes = getJsonConnectPayloadBytes(deviceName, deviceProfile.getTransportType().name());
device.setType("default");
byte[] connectPayloadBytes = getJsonConnectPayloadBytes();
publishMqttMsg(client, connectPayloadBytes, MqttTopics.GATEWAY_CONNECT_TOPIC); client.publishAndWait(GATEWAY_CONNECT_TOPIC, connectPayloadBytes);
Device savedDevice = doExecuteWithRetriesAndInterval(() -> doGet("/api/tenant/devices?deviceName=" + "Gateway Device Subscribe to attribute updates", Device.class), Device savedDevice = doExecuteWithRetriesAndInterval(() -> doGet("/api/tenant/devices?deviceName=" + deviceName, Device.class),
20, 20,
100); 100);
assertNotNull(savedDevice); assertNotNull(savedDevice);
client.subscribe(MqttTopics.GATEWAY_ATTRIBUTES_TOPIC, MqttQoS.AT_MOST_ONCE.value()); client.subscribeAndWait(GATEWAY_ATTRIBUTES_TOPIC, MqttQoS.AT_MOST_ONCE);
Thread.sleep(1000); doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", SHARED_ATTRIBUTES_PAYLOAD, String.class, status().isOk());
onUpdateCallback.getSubscribeLatch().await(3, TimeUnit.SECONDS);
doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk()); validateJsonGatewayUpdateAttributesResponse(onUpdateCallback, deviceName, SHARED_ATTRIBUTES_PAYLOAD);
onUpdateCallback.getLatch().await(3, TimeUnit.SECONDS);
validateJsonGatewayUpdateAttributesResponse(onUpdateCallback); MqttTestCallback onDeleteCallback = new MqttTestCallback();
TestMqttCallback onDeleteCallback = getTestMqttCallback();
client.setCallback(onDeleteCallback); client.setCallback(onDeleteCallback);
doDelete("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/SHARED_SCOPE?keys=attribute5", String.class); doDelete("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/SHARED_SCOPE?keys=sharedJson", String.class);
onDeleteCallback.getLatch().await(3, TimeUnit.SECONDS); onDeleteCallback.getSubscribeLatch().await(3, TimeUnit.SECONDS);
validateJsonGatewayDeleteAttributesResponse(onDeleteCallback);
validateJsonGatewayUpdateAttributesResponse(onDeleteCallback, deviceName, SHARED_ATTRIBUTES_DELETED_RESPONSE);
client.disconnect();
} }
protected void processProtoGatewayTestSubscribeToAttributesUpdates() throws Exception { protected void processProtoGatewayTestSubscribeToAttributesUpdates() throws Exception {
MqttTestClient client = new MqttTestClient();
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken); client.connectAndWait(gatewayAccessToken);
MqttTestCallback onUpdateCallback = new MqttTestCallback();
TestMqttCallback onUpdateCallback = getTestMqttCallback();
client.setCallback(onUpdateCallback); client.setCallback(onUpdateCallback);
String deviceName = "Gateway Device Subscribe to attribute updates";
Device device = new Device(); byte[] connectPayloadBytes = getProtoConnectPayloadBytes(deviceName, TransportPayloadType.PROTOBUF.name());
device.setName("Gateway Device Subscribe to attribute updates"); client.publishAndWait(GATEWAY_CONNECT_TOPIC, connectPayloadBytes);
device.setType("default"); Device device = doExecuteWithRetriesAndInterval(() -> doGet("/api/tenant/devices?deviceName=" + deviceName, Device.class),
byte[] connectPayloadBytes = getProtoConnectPayloadBytes();
publishMqttMsg(client, connectPayloadBytes, MqttTopics.GATEWAY_CONNECT_TOPIC);
Device savedDevice = doExecuteWithRetriesAndInterval(() -> doGet("/api/tenant/devices?deviceName=" + "Gateway Device Subscribe to attribute updates", Device.class),
20, 20,
100); 100);
assertNotNull(device);
assertNotNull(savedDevice); client.subscribeAndWait(GATEWAY_ATTRIBUTES_TOPIC, MqttQoS.AT_MOST_ONCE);
doPostAsync("/api/plugins/telemetry/DEVICE/" + device.getId().getId() + "/attributes/SHARED_SCOPE", SHARED_ATTRIBUTES_PAYLOAD, String.class, status().isOk());
client.subscribe(MqttTopics.GATEWAY_ATTRIBUTES_TOPIC, MqttQoS.AT_MOST_ONCE.value()); validateProtoGatewayUpdateAttributesResponse(onUpdateCallback, deviceName);
MqttTestCallback onDeleteCallback = new MqttTestCallback();
Thread.sleep(1000);
doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk());
onUpdateCallback.getLatch().await(3, TimeUnit.SECONDS);
validateProtoGatewayUpdateAttributesResponse(onUpdateCallback);
TestMqttCallback onDeleteCallback = getTestMqttCallback();
client.setCallback(onDeleteCallback); client.setCallback(onDeleteCallback);
doDelete("/api/plugins/telemetry/DEVICE/" + device.getId().getId() + "/SHARED_SCOPE?keys=sharedJson", String.class);
doDelete("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/SHARED_SCOPE?keys=attribute5", String.class); validateProtoGatewayDeleteAttributesResponse(onDeleteCallback, deviceName);
onDeleteCallback.getLatch().await(3, TimeUnit.SECONDS); client.disconnect();
validateProtoGatewayDeleteAttributesResponse(onDeleteCallback);
}
protected void validateJsonGatewayUpdateAttributesResponse(TestMqttCallback callback) throws InvalidProtocolBufferException {
assertNotNull(callback.getPayloadBytes());
String s = new String(callback.getPayloadBytes(), StandardCharsets.UTF_8);
assertEquals(getJsonResponseGatewayAttributesUpdatedPayload(), s);
} }
protected void validateJsonGatewayDeleteAttributesResponse(TestMqttCallback callback) throws InvalidProtocolBufferException { protected void validateJsonGatewayUpdateAttributesResponse(MqttTestCallback callback, String deviceName, String expectResultData) {
assertNotNull(callback.getPayloadBytes()); assertNotNull(callback.getPayloadBytes());
String s = new String(callback.getPayloadBytes(), StandardCharsets.UTF_8); assertEquals(JacksonUtil.toJsonNode(getGatewayAttributesResponseJson(deviceName, expectResultData)), JacksonUtil.fromBytes(callback.getPayloadBytes()));
assertEquals(s, getJsonResponseGatewayAttributesDeletedPayload());
} }
protected byte[] getJsonConnectPayloadBytes() { protected byte[] getJsonConnectPayloadBytes(String deviceName, String deviceType) {
String connectPayload = "{\"device\": \"Gateway Device Subscribe to attribute updates\", \"type\": \"" + TransportPayloadType.JSON.name() + "\"}"; String connectPayload = "{\"device\":\"" + deviceName + "\", \"type\": \"" + deviceType + "\"}";
return connectPayload.getBytes(); return connectPayload.getBytes();
} }
private static String getJsonResponseGatewayAttributesUpdatedPayload() { private static String getGatewayAttributesResponseJson(String deviceName, String expectResultData) {
return "{\"device\":\"" + "Gateway Device Subscribe to attribute updates" + "\"," + return "{\"device\":\"" + deviceName + "\"," + "\"data\":" + expectResultData + "}";
"\"data\":{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73,\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}}";
}
private static String getJsonResponseGatewayAttributesDeletedPayload() {
return "{\"device\":\"" + "Gateway Device Subscribe to attribute updates" + "\",\"data\":{\"deleted\":[\"attribute5\"]}}";
} }
protected void validateProtoGatewayUpdateAttributesResponse(TestMqttCallback callback) throws InvalidProtocolBufferException { protected void validateProtoGatewayUpdateAttributesResponse(MqttTestCallback callback, String deviceName) throws InvalidProtocolBufferException, InterruptedException {
callback.getSubscribeLatch().await(3, TimeUnit.SECONDS);
assertNotNull(callback.getPayloadBytes()); assertNotNull(callback.getPayloadBytes());
TransportProtos.AttributeUpdateNotificationMsg.Builder attributeUpdateNotificationMsgBuilder = TransportProtos.AttributeUpdateNotificationMsg.newBuilder(); TransportProtos.AttributeUpdateNotificationMsg.Builder attributeUpdateNotificationMsgBuilder = TransportProtos.AttributeUpdateNotificationMsg.newBuilder();
List<TransportProtos.TsKvProto> tsKvProtoList = getTsKvProtoList(); List<TransportProtos.TsKvProto> tsKvProtoList = getTsKvProtoList("shared");
attributeUpdateNotificationMsgBuilder.addAllSharedUpdated(tsKvProtoList); attributeUpdateNotificationMsgBuilder.addAllSharedUpdated(tsKvProtoList);
TransportProtos.AttributeUpdateNotificationMsg expectedAttributeUpdateNotificationMsg = attributeUpdateNotificationMsgBuilder.build(); TransportProtos.AttributeUpdateNotificationMsg expectedAttributeUpdateNotificationMsg = attributeUpdateNotificationMsgBuilder.build();
TransportApiProtos.GatewayAttributeUpdateNotificationMsg.Builder gatewayAttributeUpdateNotificationMsgBuilder = TransportApiProtos.GatewayAttributeUpdateNotificationMsg.newBuilder(); TransportApiProtos.GatewayAttributeUpdateNotificationMsg.Builder gatewayAttributeUpdateNotificationMsgBuilder = TransportApiProtos.GatewayAttributeUpdateNotificationMsg.newBuilder();
gatewayAttributeUpdateNotificationMsgBuilder.setDeviceName("Gateway Device Subscribe to attribute updates"); gatewayAttributeUpdateNotificationMsgBuilder.setDeviceName(deviceName);
gatewayAttributeUpdateNotificationMsgBuilder.setNotificationMsg(expectedAttributeUpdateNotificationMsg); gatewayAttributeUpdateNotificationMsgBuilder.setNotificationMsg(expectedAttributeUpdateNotificationMsg);
TransportApiProtos.GatewayAttributeUpdateNotificationMsg expectedGatewayAttributeUpdateNotificationMsg = gatewayAttributeUpdateNotificationMsgBuilder.build(); TransportApiProtos.GatewayAttributeUpdateNotificationMsg expectedGatewayAttributeUpdateNotificationMsg = gatewayAttributeUpdateNotificationMsgBuilder.build();
@ -367,17 +282,17 @@ public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqtt
assertEquals(expectedSharedUpdatedList.size(), actualSharedUpdatedList.size()); assertEquals(expectedSharedUpdatedList.size(), actualSharedUpdatedList.size());
assertTrue(actualSharedUpdatedList.containsAll(expectedSharedUpdatedList)); assertTrue(actualSharedUpdatedList.containsAll(expectedSharedUpdatedList));
} }
protected void validateProtoGatewayDeleteAttributesResponse(TestMqttCallback callback) throws InvalidProtocolBufferException { protected void validateProtoGatewayDeleteAttributesResponse(MqttTestCallback callback, String deviceName) throws InvalidProtocolBufferException, InterruptedException {
callback.getSubscribeLatch().await(3, TimeUnit.SECONDS);
assertNotNull(callback.getPayloadBytes()); assertNotNull(callback.getPayloadBytes());
TransportProtos.AttributeUpdateNotificationMsg.Builder attributeUpdateNotificationMsgBuilder = TransportProtos.AttributeUpdateNotificationMsg.newBuilder(); TransportProtos.AttributeUpdateNotificationMsg.Builder attributeUpdateNotificationMsgBuilder = TransportProtos.AttributeUpdateNotificationMsg.newBuilder();
attributeUpdateNotificationMsgBuilder.addSharedDeleted("attribute5"); attributeUpdateNotificationMsgBuilder.addSharedDeleted("sharedJson");
TransportProtos.AttributeUpdateNotificationMsg attributeUpdateNotificationMsg = attributeUpdateNotificationMsgBuilder.build(); TransportProtos.AttributeUpdateNotificationMsg attributeUpdateNotificationMsg = attributeUpdateNotificationMsgBuilder.build();
TransportApiProtos.GatewayAttributeUpdateNotificationMsg.Builder gatewayAttributeUpdateNotificationMsgBuilder = TransportApiProtos.GatewayAttributeUpdateNotificationMsg.newBuilder(); TransportApiProtos.GatewayAttributeUpdateNotificationMsg.Builder gatewayAttributeUpdateNotificationMsgBuilder = TransportApiProtos.GatewayAttributeUpdateNotificationMsg.newBuilder();
gatewayAttributeUpdateNotificationMsgBuilder.setDeviceName("Gateway Device Subscribe to attribute updates"); gatewayAttributeUpdateNotificationMsgBuilder.setDeviceName(deviceName);
gatewayAttributeUpdateNotificationMsgBuilder.setNotificationMsg(attributeUpdateNotificationMsg); gatewayAttributeUpdateNotificationMsgBuilder.setNotificationMsg(attributeUpdateNotificationMsg);
TransportApiProtos.GatewayAttributeUpdateNotificationMsg expectedGatewayAttributeUpdateNotificationMsg = gatewayAttributeUpdateNotificationMsgBuilder.build(); TransportApiProtos.GatewayAttributeUpdateNotificationMsg expectedGatewayAttributeUpdateNotificationMsg = gatewayAttributeUpdateNotificationMsgBuilder.build();
@ -389,118 +304,190 @@ public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqtt
TransportProtos.AttributeUpdateNotificationMsg actualAttributeUpdateNotificationMsg = actualGatewayAttributeUpdateNotificationMsg.getNotificationMsg(); TransportProtos.AttributeUpdateNotificationMsg actualAttributeUpdateNotificationMsg = actualGatewayAttributeUpdateNotificationMsg.getNotificationMsg();
assertEquals(expectedAttributeUpdateNotificationMsg.getSharedDeletedList().size(), actualAttributeUpdateNotificationMsg.getSharedDeletedList().size()); assertEquals(expectedAttributeUpdateNotificationMsg.getSharedDeletedList().size(), actualAttributeUpdateNotificationMsg.getSharedDeletedList().size());
assertEquals("attribute5", actualAttributeUpdateNotificationMsg.getSharedDeletedList().get(0)); assertEquals("sharedJson", actualAttributeUpdateNotificationMsg.getSharedDeletedList().get(0));
}
protected byte[] getProtoConnectPayloadBytes() {
TransportApiProtos.ConnectMsg connectProto = getConnectProto();
return connectProto.toByteArray();
} }
private TransportApiProtos.ConnectMsg getConnectProto() { private byte[] getProtoConnectPayloadBytes(String deviceName, String deviceType) {
TransportApiProtos.ConnectMsg.Builder builder = TransportApiProtos.ConnectMsg.newBuilder(); TransportApiProtos.ConnectMsg connectMsg = TransportApiProtos.ConnectMsg.newBuilder()
builder.setDeviceName("Gateway Device Subscribe to attribute updates"); .setDeviceName(deviceName)
builder.setDeviceType(TransportPayloadType.PROTOBUF.name()); .setDeviceType(deviceType)
return builder.build(); .build();
return connectMsg.toByteArray();
} }
// request attributes from server methods // request attributes from server methods
protected void processJsonTestRequestAttributesValuesFromTheServer(String attrPubTopic, String attrSubTopic, String attrReqTopicPrefix) throws Exception { protected void processJsonTestRequestAttributesValuesFromTheServer(String attrPubTopic, String attrSubTopic, String attrReqTopicPrefix) throws Exception {
MqttTestClient client = new MqttTestClient();
MqttAsyncClient client = getMqttAsyncClient(accessToken); client.connectAndWait(accessToken);
SingleEntityFilter dtf = new SingleEntityFilter();
postJsonAttributesAndSubscribeToTopic(savedDevice, client, attrPubTopic, attrSubTopic); dtf.setSingleEntity(savedDevice.getId());
String clientKeysStr = "clientStr,clientBool,clientDbl,clientLong,clientJson";
Thread.sleep(5000); String sharedKeysStr = "sharedStr,sharedBool,sharedDbl,sharedLong,sharedJson";
List<String> clientKeysList = List.of(clientKeysStr.split(","));
TestMqttCallback callback = getTestMqttCallback(); List<String> sharedKeysList = List.of(sharedKeysStr.split(","));
List<EntityKey> csKeys = getEntityKeys(clientKeysList, CLIENT_ATTRIBUTE);
List<EntityKey> shKeys = getEntityKeys(sharedKeysList, SHARED_ATTRIBUTE);
List<EntityKey> keys = new ArrayList<>();
keys.addAll(csKeys);
keys.addAll(shKeys);
getWsClient().subscribeLatestUpdate(keys, dtf);
getWsClient().registerWaitForUpdate(2);
doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE",
SHARED_ATTRIBUTES_PAYLOAD, String.class, status().isOk());
client.publishAndWait(attrPubTopic, CLIENT_ATTRIBUTES_PAYLOAD.getBytes());
client.subscribeAndWait(attrSubTopic, MqttQoS.AT_MOST_ONCE);
String update = getWsClient().waitForUpdate();
assertThat(update).as("ws update received").isNotBlank();
MqttTestCallback callback = new MqttTestCallback(attrSubTopic.replace("+", "1"));
client.setCallback(callback); client.setCallback(callback);
String payloadStr = "{\"clientKeys\":\"" + clientKeysStr + "\", \"sharedKeys\":\"" + sharedKeysStr + "\"}";
validateJsonResponse(client, callback.getLatch(), callback, attrReqTopicPrefix); client.publishAndWait(attrReqTopicPrefix + "1", payloadStr.getBytes());
String expectedResponse = "{\"client\":" + CLIENT_ATTRIBUTES_PAYLOAD + ",\"shared\":" + SHARED_ATTRIBUTES_PAYLOAD + "}";
validateJsonResponse(callback, expectedResponse);
client.disconnect();
} }
protected void processProtoTestRequestAttributesValuesFromTheServer(String attrPubTopic, String attrSubTopic, String attrReqTopicPrefix) throws Exception { protected void processProtoTestRequestAttributesValuesFromTheServer(String attrPubTopic, String attrSubTopic, String attrReqTopicPrefix) throws Exception {
MqttTestClient client = new MqttTestClient();
MqttAsyncClient client = getMqttAsyncClient(accessToken); client.connectAndWait(accessToken);
DeviceTypeFilter dtf = new DeviceTypeFilter(savedDevice.getType(), savedDevice.getName());
postProtoAttributesAndSubscribeToTopic(savedDevice, client, attrPubTopic, attrSubTopic); String clientKeysStr = "clientStr,clientBool,clientDbl,clientLong,clientJson";
String sharedKeysStr = "sharedStr,sharedBool,sharedDbl,sharedLong,sharedJson";
Thread.sleep(5000); List<String> clientKeysList = List.of(clientKeysStr.split(","));
List<String> sharedKeysList = List.of(sharedKeysStr.split(","));
TestMqttCallback callback = getTestMqttCallback(); List<EntityKey> csKeys = getEntityKeys(clientKeysList, CLIENT_ATTRIBUTE);
List<EntityKey> shKeys = getEntityKeys(sharedKeysList, SHARED_ATTRIBUTE);
List<EntityKey> keys = new ArrayList<>();
keys.addAll(csKeys);
keys.addAll(shKeys);
getWsClient().subscribeLatestUpdate(keys, dtf);
getWsClient().registerWaitForUpdate(2);
doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", SHARED_ATTRIBUTES_PAYLOAD, String.class, status().isOk());
client.publishAndWait(attrPubTopic, getAttributesProtoPayloadBytes());
client.subscribeAndWait(attrSubTopic, MqttQoS.AT_MOST_ONCE);
String update = getWsClient().waitForUpdate();
assertThat(update).as("ws update received").isNotBlank();
MqttTestCallback callback = new MqttTestCallback(attrSubTopic.replace("+", "1"));
client.setCallback(callback); client.setCallback(callback);
TransportApiProtos.AttributesRequest.Builder attributesRequestBuilder = TransportApiProtos.AttributesRequest.newBuilder();
validateProtoResponse(client, callback.getLatch(), callback, attrReqTopicPrefix); attributesRequestBuilder.setClientKeys(clientKeysStr);
attributesRequestBuilder.setSharedKeys(sharedKeysStr);
TransportApiProtos.AttributesRequest attributesRequest = attributesRequestBuilder.build();
client.publishAndWait(attrReqTopicPrefix + "1", attributesRequest.toByteArray());
validateProtoResponse(callback, getExpectedAttributeResponseMsg());
client.disconnect();
} }
protected void processJsonTestGatewayRequestAttributesValuesFromTheServer() throws Exception { protected void processJsonTestGatewayRequestAttributesValuesFromTheServer() throws Exception {
MqttTestClient client = new MqttTestClient();
client.connectAndWait(gatewayAccessToken);
String deviceName = "Gateway Device Request Attributes";
String postClientAttributes = "{\"" + deviceName + "\":" + CLIENT_ATTRIBUTES_PAYLOAD + "}";
client.publishAndWait(GATEWAY_ATTRIBUTES_TOPIC, postClientAttributes.getBytes());
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken); Device device = doExecuteWithRetriesAndInterval(() -> doGet("/api/tenant/devices?deviceName=" + deviceName, Device.class),
postJsonGatewayDeviceClientAttributes(client);
Device savedDevice = doExecuteWithRetriesAndInterval(() -> doGet("/api/tenant/devices?deviceName=" + "Gateway Device Request Attributes", Device.class),
20, 20,
100); 100);
assertNotNull(device);
assertNotNull(savedDevice);
SingleEntityFilter dtf = new SingleEntityFilter();
Thread.sleep(2000); dtf.setSingleEntity(device.getId());
String clientKeysStr = "clientStr,clientBool,clientDbl,clientLong,clientJson";
doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk()); String sharedKeysStr = "sharedStr,sharedBool,sharedDbl,sharedLong,sharedJson";
List<String> clientKeysList = List.of(clientKeysStr.split(","));
Thread.sleep(5000); List<String> sharedKeysList = List.of(sharedKeysStr.split(","));
List<EntityKey> csKeys = getEntityKeys(clientKeysList, CLIENT_ATTRIBUTE);
client.subscribe(MqttTopics.GATEWAY_ATTRIBUTES_RESPONSE_TOPIC, MqttQoS.AT_LEAST_ONCE.value()).waitForCompletion(TimeUnit.MINUTES.toMillis(1)); List<EntityKey> shKeys = getEntityKeys(sharedKeysList, SHARED_ATTRIBUTE);
List<EntityKey> keys = new ArrayList<>();
TestMqttCallback clientAttributesCallback = getTestMqttCallback(); keys.addAll(csKeys);
keys.addAll(shKeys);
EntityDataUpdate initUpdate = getWsClient().subscribeLatestUpdate(keys, dtf);
assertNotNull(initUpdate);
PageData<EntityData> data = initUpdate.getData();
assertNotNull(data);
assertFalse(data.getData().isEmpty());
getWsClient().registerWaitForUpdate();
doPostAsync("/api/plugins/telemetry/DEVICE/" + device.getId().getId() + "/attributes/SHARED_SCOPE", SHARED_ATTRIBUTES_PAYLOAD, String.class, status().isOk());
String update = getWsClient().waitForUpdate();
assertThat(update).as("ws update received").isNotBlank();
client.subscribeAndWait(GATEWAY_ATTRIBUTES_RESPONSE_TOPIC, MqttQoS.AT_LEAST_ONCE);
MqttTestCallback clientAttributesCallback = new MqttTestCallback(GATEWAY_ATTRIBUTES_RESPONSE_TOPIC);
client.setCallback(clientAttributesCallback); client.setCallback(clientAttributesCallback);
validateJsonClientResponseGateway(client, clientAttributesCallback); String csKeysStr = "[\"clientStr\", \"clientBool\", \"clientDbl\", \"clientLong\", \"clientJson\"]";
String csRequestPayloadStr = "{\"id\": 1, \"device\": \"" + deviceName + "\", \"client\": true, \"keys\": " + csKeysStr + "}";
client.publishAndWait(GATEWAY_ATTRIBUTES_REQUEST_TOPIC, csRequestPayloadStr.getBytes());
validateJsonResponseGateway(clientAttributesCallback, deviceName, CLIENT_ATTRIBUTES_PAYLOAD);
TestMqttCallback sharedAttributesCallback = getTestMqttCallback(); MqttTestCallback sharedAttributesCallback = new MqttTestCallback(GATEWAY_ATTRIBUTES_RESPONSE_TOPIC);
client.setCallback(sharedAttributesCallback); client.setCallback(sharedAttributesCallback);
validateJsonSharedResponseGateway(client, sharedAttributesCallback); String shKeysStr = "[\"sharedStr\", \"sharedBool\", \"sharedDbl\", \"sharedLong\", \"sharedJson\"]";
String shRequestPayloadStr = "{\"id\": 1, \"device\": \"" + deviceName + "\", \"client\": false, \"keys\": " + shKeysStr + "}";
client.publishAndWait(GATEWAY_ATTRIBUTES_REQUEST_TOPIC, shRequestPayloadStr.getBytes());
validateJsonResponseGateway(sharedAttributesCallback, deviceName, SHARED_ATTRIBUTES_PAYLOAD);
client.disconnect();
} }
protected void processProtoTestGatewayRequestAttributesValuesFromTheServer() throws Exception { protected void processProtoTestGatewayRequestAttributesValuesFromTheServer() throws Exception {
MqttTestClient client = new MqttTestClient();
client.connectAndWait(gatewayAccessToken);
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken); String deviceName = "Gateway Device Request Attributes";
String clientKeysStr = "clientStr,clientBool,clientDbl,clientLong,clientJson";
List<String> clientKeysList = List.of(clientKeysStr.split(","));
client.publishAndWait(GATEWAY_ATTRIBUTES_TOPIC, getProtoGatewayDeviceClientAttributesPayload(deviceName, clientKeysList));
postProtoGatewayDeviceClientAttributes(client); Device device = doExecuteWithRetriesAndInterval(() -> doGet("/api/tenant/devices?deviceName=" + deviceName, Device.class),
Device savedDevice = doExecuteWithRetriesAndInterval(() -> doGet("/api/tenant/devices?deviceName=" + "Gateway Device Request Attributes", Device.class),
20, 20,
100); 100);
assertNotNull(device);
assertNotNull(savedDevice);
SingleEntityFilter dtf = new SingleEntityFilter();
Thread.sleep(2000); dtf.setSingleEntity(device.getId());
String sharedKeysStr = "sharedStr,sharedBool,sharedDbl,sharedLong,sharedJson";
doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk()); List<String> sharedKeysList = List.of(sharedKeysStr.split(","));
List<EntityKey> csKeys = getEntityKeys(clientKeysList, CLIENT_ATTRIBUTE);
Thread.sleep(5000); List<EntityKey> shKeys = getEntityKeys(sharedKeysList, SHARED_ATTRIBUTE);
List<EntityKey> keys = new ArrayList<>();
client.subscribe(MqttTopics.GATEWAY_ATTRIBUTES_RESPONSE_TOPIC, MqttQoS.AT_LEAST_ONCE.value()).waitForCompletion(TimeUnit.MINUTES.toMillis(1)); keys.addAll(csKeys);
keys.addAll(shKeys);
TestMqttCallback clientAttributesCallback = getTestMqttCallback(); EntityDataUpdate initUpdate = getWsClient().subscribeLatestUpdate(keys, dtf);
assertNotNull(initUpdate);
PageData<EntityData> data = initUpdate.getData();
assertNotNull(data);
assertFalse(data.getData().isEmpty());
getWsClient().registerWaitForUpdate();
doPostAsync("/api/plugins/telemetry/DEVICE/" + device.getId().getId() + "/attributes/SHARED_SCOPE", SHARED_ATTRIBUTES_PAYLOAD, String.class, status().isOk());
String update = getWsClient().waitForUpdate();
assertThat(update).as("ws update received").isNotBlank();
client.subscribeAndWait(GATEWAY_ATTRIBUTES_RESPONSE_TOPIC, MqttQoS.AT_LEAST_ONCE);
MqttTestCallback clientAttributesCallback = new MqttTestCallback(GATEWAY_ATTRIBUTES_RESPONSE_TOPIC);
client.setCallback(clientAttributesCallback); client.setCallback(clientAttributesCallback);
validateProtoClientResponseGateway(client, clientAttributesCallback); TransportApiProtos.GatewayAttributesRequestMsg gatewayAttributesRequestMsg = getGatewayAttributesRequestMsg(deviceName, clientKeysList, true);
client.publishAndWait(GATEWAY_ATTRIBUTES_REQUEST_TOPIC, gatewayAttributesRequestMsg.toByteArray());
validateProtoClientResponseGateway(clientAttributesCallback, deviceName);
TestMqttCallback sharedAttributesCallback = getTestMqttCallback(); MqttTestCallback sharedAttributesCallback = new MqttTestCallback(GATEWAY_ATTRIBUTES_RESPONSE_TOPIC);
client.setCallback(sharedAttributesCallback); client.setCallback(sharedAttributesCallback);
validateProtoSharedResponseGateway(client, sharedAttributesCallback); gatewayAttributesRequestMsg = getGatewayAttributesRequestMsg(deviceName, sharedKeysList, false);
client.publishAndWait(GATEWAY_ATTRIBUTES_REQUEST_TOPIC, gatewayAttributesRequestMsg.toByteArray());
validateProtoSharedResponseGateway(sharedAttributesCallback, deviceName);
client.disconnect();
} }
protected void postJsonAttributesAndSubscribeToTopic(Device savedDevice, MqttAsyncClient client, String attrPubTopic, String attrSubTopic) throws Exception { private List<EntityKey> getEntityKeys(List<String> keys, EntityKeyType scope) {
doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk()); return keys.stream().map(key -> new EntityKey(scope, key)).collect(Collectors.toList());
client.publish(attrPubTopic, new MqttMessage(POST_ATTRIBUTES_PAYLOAD.getBytes())).waitForCompletion(TimeUnit.MINUTES.toMillis(1));
client.subscribe(attrSubTopic, MqttQoS.AT_MOST_ONCE.value()).waitForCompletion(TimeUnit.MINUTES.toMillis(1));
} }
protected void postProtoAttributesAndSubscribeToTopic(Device savedDevice, MqttAsyncClient client, String attrPubTopic, String attrSubTopic) throws Exception { private byte[] getAttributesProtoPayloadBytes() {
doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", AbstractMqttAttributesIntegrationTest.POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk());
DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration();
assertTrue(transportConfiguration instanceof MqttDeviceProfileTransportConfiguration); assertTrue(transportConfiguration instanceof MqttDeviceProfileTransportConfiguration);
MqttDeviceProfileTransportConfiguration mqttTransportConfiguration = (MqttDeviceProfileTransportConfiguration) transportConfiguration; MqttDeviceProfileTransportConfiguration mqttTransportConfiguration = (MqttDeviceProfileTransportConfiguration) transportConfiguration;
@ -530,64 +517,39 @@ public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqtt
Descriptors.Descriptor postAttributesMsgDescriptor = postAttributesBuilder.getDescriptorForType(); Descriptors.Descriptor postAttributesMsgDescriptor = postAttributesBuilder.getDescriptorForType();
assertNotNull(postAttributesMsgDescriptor); assertNotNull(postAttributesMsgDescriptor);
DynamicMessage postAttributesMsg = postAttributesBuilder DynamicMessage postAttributesMsg = postAttributesBuilder
.setField(postAttributesMsgDescriptor.findFieldByName("attribute1"), "value1") .setField(postAttributesMsgDescriptor.findFieldByName("clientStr"), "value1")
.setField(postAttributesMsgDescriptor.findFieldByName("attribute2"), true) .setField(postAttributesMsgDescriptor.findFieldByName("clientBool"), true)
.setField(postAttributesMsgDescriptor.findFieldByName("attribute3"), 42.0) .setField(postAttributesMsgDescriptor.findFieldByName("clientDbl"), 42.0)
.setField(postAttributesMsgDescriptor.findFieldByName("attribute4"), 73) .setField(postAttributesMsgDescriptor.findFieldByName("clientLong"), 73)
.setField(postAttributesMsgDescriptor.findFieldByName("attribute5"), jsonObject) .setField(postAttributesMsgDescriptor.findFieldByName("clientJson"), jsonObject)
.build(); .build();
byte[] payload = postAttributesMsg.toByteArray(); return postAttributesMsg.toByteArray();
client.publish(attrPubTopic, new MqttMessage(payload));
client.subscribe(attrSubTopic, MqttQoS.AT_MOST_ONCE.value());
} }
protected void postJsonGatewayDeviceClientAttributes(MqttAsyncClient client) throws Exception { protected byte[] getProtoGatewayDeviceClientAttributesPayload(String deviceName, List<String> clientKeysList) {
String postClientAttributes = "{\"" + "Gateway Device Request Attributes" + "\":{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73,\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}}"; TransportProtos.PostAttributeMsg postAttributeMsg = getPostAttributeMsg(clientKeysList);
client.publish(MqttTopics.GATEWAY_ATTRIBUTES_TOPIC, new MqttMessage(postClientAttributes.getBytes())).waitForCompletion(TimeUnit.MINUTES.toMillis(1));
}
protected void postProtoGatewayDeviceClientAttributes(MqttAsyncClient client) throws Exception {
String keys = "attribute1,attribute2,attribute3,attribute4,attribute5";
List<String> expectedKeys = Arrays.asList(keys.split(","));
TransportProtos.PostAttributeMsg postAttributeMsg = getPostAttributeMsg(expectedKeys);
TransportApiProtos.AttributesMsg.Builder attributesMsgBuilder = TransportApiProtos.AttributesMsg.newBuilder(); TransportApiProtos.AttributesMsg.Builder attributesMsgBuilder = TransportApiProtos.AttributesMsg.newBuilder();
attributesMsgBuilder.setDeviceName("Gateway Device Request Attributes"); attributesMsgBuilder.setDeviceName(deviceName);
attributesMsgBuilder.setMsg(postAttributeMsg); attributesMsgBuilder.setMsg(postAttributeMsg);
TransportApiProtos.AttributesMsg attributesMsg = attributesMsgBuilder.build(); TransportApiProtos.AttributesMsg attributesMsg = attributesMsgBuilder.build();
TransportApiProtos.GatewayAttributesMsg.Builder gatewayAttributeMsgBuilder = TransportApiProtos.GatewayAttributesMsg.newBuilder(); TransportApiProtos.GatewayAttributesMsg.Builder gatewayAttributeMsgBuilder = TransportApiProtos.GatewayAttributesMsg.newBuilder();
gatewayAttributeMsgBuilder.addMsg(attributesMsg); gatewayAttributeMsgBuilder.addMsg(attributesMsg);
byte[] bytes = gatewayAttributeMsgBuilder.build().toByteArray(); return gatewayAttributeMsgBuilder.build().toByteArray();
client.publish(MqttTopics.GATEWAY_ATTRIBUTES_TOPIC, new MqttMessage(bytes));
} }
protected void validateJsonResponse(MqttAsyncClient client, CountDownLatch latch, TestMqttCallback callback, String attrReqTopicPrefix) throws MqttException, InterruptedException, InvalidProtocolBufferException { protected void validateJsonResponse(MqttTestCallback callback, String expectedResponse) throws InterruptedException {
String keys = "attribute1,attribute2,attribute3,attribute4,attribute5"; callback.getSubscribeLatch().await(3, TimeUnit.SECONDS);
String payloadStr = "{\"clientKeys\":\"" + keys + "\", \"sharedKeys\":\"" + keys + "\"}";
MqttMessage mqttMessage = new MqttMessage();
mqttMessage.setPayload(payloadStr.getBytes());
client.publish(attrReqTopicPrefix + "1", mqttMessage).waitForCompletion(TimeUnit.MINUTES.toMillis(1));
latch.await(1, TimeUnit.MINUTES);
assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS()); assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS());
String expectedRequestPayload = "{\"client\":{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73,\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}},\"shared\":{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73,\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}}"; assertEquals(JacksonUtil.toJsonNode(expectedResponse), JacksonUtil.fromBytes(callback.getPayloadBytes()));
assertEquals(JacksonUtil.toJsonNode(expectedRequestPayload), JacksonUtil.toJsonNode(new String(callback.getPayloadBytes(), StandardCharsets.UTF_8)));
} }
protected void validateProtoResponse(MqttAsyncClient client, CountDownLatch latch, TestMqttCallback callback, String attrReqTopic) throws MqttException, InterruptedException, InvalidProtocolBufferException { protected void validateProtoResponse(MqttTestCallback callback, TransportProtos.GetAttributeResponseMsg expectedResponse) throws InterruptedException, InvalidProtocolBufferException {
String keys = "attribute1,attribute2,attribute3,attribute4,attribute5"; callback.getSubscribeLatch().await(3, TimeUnit.SECONDS);
TransportApiProtos.AttributesRequest.Builder attributesRequestBuilder = TransportApiProtos.AttributesRequest.newBuilder();
attributesRequestBuilder.setClientKeys(keys);
attributesRequestBuilder.setSharedKeys(keys);
TransportApiProtos.AttributesRequest attributesRequest = attributesRequestBuilder.build();
MqttMessage mqttMessage = new MqttMessage();
mqttMessage.setPayload(attributesRequest.toByteArray());
client.publish(attrReqTopic + "1", mqttMessage);
latch.await(3, TimeUnit.SECONDS);
assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS()); assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS());
TransportProtos.GetAttributeResponseMsg expectedAttributesResponse = getExpectedAttributeResponseMsg();
TransportProtos.GetAttributeResponseMsg actualAttributesResponse = TransportProtos.GetAttributeResponseMsg.parseFrom(callback.getPayloadBytes()); TransportProtos.GetAttributeResponseMsg actualAttributesResponse = TransportProtos.GetAttributeResponseMsg.parseFrom(callback.getPayloadBytes());
assertEquals(expectedAttributesResponse.getRequestId(), actualAttributesResponse.getRequestId()); assertEquals(expectedResponse.getRequestId(), actualAttributesResponse.getRequestId());
List<TransportProtos.KeyValueProto> expectedClientKeyValueProtos = expectedAttributesResponse.getClientAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList()); List<TransportProtos.KeyValueProto> expectedClientKeyValueProtos = expectedResponse.getClientAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
List<TransportProtos.KeyValueProto> expectedSharedKeyValueProtos = expectedAttributesResponse.getSharedAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList()); List<TransportProtos.KeyValueProto> expectedSharedKeyValueProtos = expectedResponse.getSharedAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
List<TransportProtos.KeyValueProto> actualClientKeyValueProtos = actualAttributesResponse.getClientAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList()); List<TransportProtos.KeyValueProto> actualClientKeyValueProtos = actualAttributesResponse.getClientAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
List<TransportProtos.KeyValueProto> actualSharedKeyValueProtos = actualAttributesResponse.getSharedAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList()); List<TransportProtos.KeyValueProto> actualSharedKeyValueProtos = actualAttributesResponse.getSharedAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList());
assertTrue(actualClientKeyValueProtos.containsAll(expectedClientKeyValueProtos)); assertTrue(actualClientKeyValueProtos.containsAll(expectedClientKeyValueProtos));
@ -596,42 +558,25 @@ public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqtt
private TransportProtos.GetAttributeResponseMsg getExpectedAttributeResponseMsg() { private TransportProtos.GetAttributeResponseMsg getExpectedAttributeResponseMsg() {
TransportProtos.GetAttributeResponseMsg.Builder result = TransportProtos.GetAttributeResponseMsg.newBuilder(); TransportProtos.GetAttributeResponseMsg.Builder result = TransportProtos.GetAttributeResponseMsg.newBuilder();
List<TransportProtos.TsKvProto> tsKvProtoList = getTsKvProtoList(); List<TransportProtos.TsKvProto> csTsKvProtoList = getTsKvProtoList("client");
result.addAllClientAttributeList(tsKvProtoList); List<TransportProtos.TsKvProto> shTsKvProtoList = getTsKvProtoList("shared");
result.addAllSharedAttributeList(tsKvProtoList); result.addAllClientAttributeList(csTsKvProtoList);
result.addAllSharedAttributeList(shTsKvProtoList);
result.setRequestId(1); result.setRequestId(1);
return result.build(); return result.build();
} }
protected void validateJsonClientResponseGateway(MqttAsyncClient client, TestMqttCallback callback) throws MqttException, InterruptedException, InvalidProtocolBufferException { protected void validateJsonResponseGateway(MqttTestCallback callback, String deviceName, String expectedValues) throws InterruptedException {
String payloadStr = "{\"id\": 1, \"device\": \"" + "Gateway Device Request Attributes" + "\", \"client\": true, \"keys\": [\"attribute1\", \"attribute2\", \"attribute3\", \"attribute4\", \"attribute5\"]}"; callback.getSubscribeLatch().await(3, TimeUnit.SECONDS);
MqttMessage mqttMessage = new MqttMessage();
mqttMessage.setPayload(payloadStr.getBytes());
client.publish(MqttTopics.GATEWAY_ATTRIBUTES_REQUEST_TOPIC, mqttMessage).waitForCompletion(TimeUnit.MINUTES.toMillis(1));
callback.getLatch().await(1, TimeUnit.MINUTES);
assertEquals(MqttQoS.AT_LEAST_ONCE.value(), callback.getQoS());
String expectedRequestPayload = "{\"id\":1,\"device\":\"" + "Gateway Device Request Attributes" + "\",\"values\":{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73,\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}}";
assertEquals(JacksonUtil.toJsonNode(expectedRequestPayload), JacksonUtil.toJsonNode(new String(callback.getPayloadBytes(), StandardCharsets.UTF_8)));
}
protected void validateJsonSharedResponseGateway(MqttAsyncClient client, TestMqttCallback callback) throws MqttException, InterruptedException, InvalidProtocolBufferException {
String payloadStr = "{\"id\": 1, \"device\": \"" + "Gateway Device Request Attributes" + "\", \"client\": false, \"keys\": [\"attribute1\", \"attribute2\", \"attribute3\", \"attribute4\", \"attribute5\"]}";
MqttMessage mqttMessage = new MqttMessage();
mqttMessage.setPayload(payloadStr.getBytes());
client.publish(MqttTopics.GATEWAY_ATTRIBUTES_REQUEST_TOPIC, mqttMessage).waitForCompletion(TimeUnit.MINUTES.toMillis(1));
callback.getLatch().await(1, TimeUnit.MINUTES);
assertEquals(MqttQoS.AT_LEAST_ONCE.value(), callback.getQoS()); assertEquals(MqttQoS.AT_LEAST_ONCE.value(), callback.getQoS());
String expectedRequestPayload = "{\"id\":1,\"device\":\"" + "Gateway Device Request Attributes" + "\",\"values\":{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73,\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}}"; String expectedRequestPayload = "{\"id\":1,\"device\":\"" + deviceName + "\",\"values\":" + expectedValues + "}";
assertEquals(JacksonUtil.toJsonNode(expectedRequestPayload), JacksonUtil.toJsonNode(new String(callback.getPayloadBytes(), StandardCharsets.UTF_8))); assertEquals(JacksonUtil.toJsonNode(expectedRequestPayload), JacksonUtil.fromBytes(callback.getPayloadBytes()));
} }
protected void validateProtoClientResponseGateway(MqttAsyncClient client, AbstractMqttAttributesIntegrationTest.TestMqttCallback callback) throws MqttException, InterruptedException, InvalidProtocolBufferException { protected void validateProtoClientResponseGateway(MqttTestCallback callback, String deviceName) throws InterruptedException, InvalidProtocolBufferException {
String keys = "attribute1,attribute2,attribute3,attribute4,attribute5"; callback.getSubscribeLatch().await(3, TimeUnit.SECONDS);
TransportApiProtos.GatewayAttributesRequestMsg gatewayAttributesRequestMsg = getGatewayAttributesRequestMsg(keys, true);
client.publish(MqttTopics.GATEWAY_ATTRIBUTES_REQUEST_TOPIC, new MqttMessage(gatewayAttributesRequestMsg.toByteArray()));
callback.getLatch().await(3, TimeUnit.SECONDS);
assertEquals(MqttQoS.AT_LEAST_ONCE.value(), callback.getQoS()); assertEquals(MqttQoS.AT_LEAST_ONCE.value(), callback.getQoS());
TransportApiProtos.GatewayAttributeResponseMsg expectedGatewayAttributeResponseMsg = getExpectedGatewayAttributeResponseMsg(true); TransportApiProtos.GatewayAttributeResponseMsg expectedGatewayAttributeResponseMsg = getExpectedGatewayAttributeResponseMsg(deviceName, true);
TransportApiProtos.GatewayAttributeResponseMsg actualGatewayAttributeResponseMsg = TransportApiProtos.GatewayAttributeResponseMsg.parseFrom(callback.getPayloadBytes()); TransportApiProtos.GatewayAttributeResponseMsg actualGatewayAttributeResponseMsg = TransportApiProtos.GatewayAttributeResponseMsg.parseFrom(callback.getPayloadBytes());
assertEquals(expectedGatewayAttributeResponseMsg.getDeviceName(), actualGatewayAttributeResponseMsg.getDeviceName()); assertEquals(expectedGatewayAttributeResponseMsg.getDeviceName(), actualGatewayAttributeResponseMsg.getDeviceName());
@ -644,13 +589,10 @@ public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqtt
assertTrue(actualClientKeyValueProtos.containsAll(expectedClientKeyValueProtos)); assertTrue(actualClientKeyValueProtos.containsAll(expectedClientKeyValueProtos));
} }
protected void validateProtoSharedResponseGateway(MqttAsyncClient client, AbstractMqttAttributesIntegrationTest.TestMqttCallback callback) throws MqttException, InterruptedException, InvalidProtocolBufferException { protected void validateProtoSharedResponseGateway(MqttTestCallback callback, String deviceName) throws InterruptedException, InvalidProtocolBufferException {
String keys = "attribute1,attribute2,attribute3,attribute4,attribute5"; callback.getSubscribeLatch().await(3, TimeUnit.SECONDS);
TransportApiProtos.GatewayAttributesRequestMsg gatewayAttributesRequestMsg = getGatewayAttributesRequestMsg(keys, false);
client.publish(MqttTopics.GATEWAY_ATTRIBUTES_REQUEST_TOPIC, new MqttMessage(gatewayAttributesRequestMsg.toByteArray()));
callback.getLatch().await(3, TimeUnit.SECONDS);
assertEquals(MqttQoS.AT_LEAST_ONCE.value(), callback.getQoS()); assertEquals(MqttQoS.AT_LEAST_ONCE.value(), callback.getQoS());
TransportApiProtos.GatewayAttributeResponseMsg expectedGatewayAttributeResponseMsg = getExpectedGatewayAttributeResponseMsg(false); TransportApiProtos.GatewayAttributeResponseMsg expectedGatewayAttributeResponseMsg = getExpectedGatewayAttributeResponseMsg(deviceName, false);
TransportApiProtos.GatewayAttributeResponseMsg actualGatewayAttributeResponseMsg = TransportApiProtos.GatewayAttributeResponseMsg.parseFrom(callback.getPayloadBytes()); TransportApiProtos.GatewayAttributeResponseMsg actualGatewayAttributeResponseMsg = TransportApiProtos.GatewayAttributeResponseMsg.parseFrom(callback.getPayloadBytes());
assertEquals(expectedGatewayAttributeResponseMsg.getDeviceName(), actualGatewayAttributeResponseMsg.getDeviceName()); assertEquals(expectedGatewayAttributeResponseMsg.getDeviceName(), actualGatewayAttributeResponseMsg.getDeviceName());
@ -664,27 +606,26 @@ public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqtt
assertTrue(actualSharedKeyValueProtos.containsAll(expectedSharedKeyValueProtos)); assertTrue(actualSharedKeyValueProtos.containsAll(expectedSharedKeyValueProtos));
} }
private TransportApiProtos.GatewayAttributeResponseMsg getExpectedGatewayAttributeResponseMsg(boolean client) { private TransportApiProtos.GatewayAttributeResponseMsg getExpectedGatewayAttributeResponseMsg(String deviceName, boolean client) {
TransportApiProtos.GatewayAttributeResponseMsg.Builder gatewayAttributeResponseMsg = TransportApiProtos.GatewayAttributeResponseMsg.newBuilder(); TransportApiProtos.GatewayAttributeResponseMsg.Builder gatewayAttributeResponseMsg = TransportApiProtos.GatewayAttributeResponseMsg.newBuilder();
TransportProtos.GetAttributeResponseMsg.Builder getAttributeResponseMsgBuilder = TransportProtos.GetAttributeResponseMsg.newBuilder(); TransportProtos.GetAttributeResponseMsg.Builder getAttributeResponseMsgBuilder = TransportProtos.GetAttributeResponseMsg.newBuilder();
List<TransportProtos.TsKvProto> tsKvProtoList = getTsKvProtoList();
if (client) { if (client) {
getAttributeResponseMsgBuilder.addAllClientAttributeList(tsKvProtoList); getAttributeResponseMsgBuilder.addAllClientAttributeList(getTsKvProtoList("client"));
} else { } else {
getAttributeResponseMsgBuilder.addAllSharedAttributeList(tsKvProtoList); getAttributeResponseMsgBuilder.addAllSharedAttributeList(getTsKvProtoList("shared"));
} }
getAttributeResponseMsgBuilder.setRequestId(1); getAttributeResponseMsgBuilder.setRequestId(1);
TransportProtos.GetAttributeResponseMsg getAttributeResponseMsg = getAttributeResponseMsgBuilder.build(); TransportProtos.GetAttributeResponseMsg getAttributeResponseMsg = getAttributeResponseMsgBuilder.build();
gatewayAttributeResponseMsg.setDeviceName("Gateway Device Request Attributes"); gatewayAttributeResponseMsg.setDeviceName(deviceName);
gatewayAttributeResponseMsg.setResponseMsg(getAttributeResponseMsg); gatewayAttributeResponseMsg.setResponseMsg(getAttributeResponseMsg);
return gatewayAttributeResponseMsg.build(); return gatewayAttributeResponseMsg.build();
} }
private TransportApiProtos.GatewayAttributesRequestMsg getGatewayAttributesRequestMsg(String keys, boolean client) { private TransportApiProtos.GatewayAttributesRequestMsg getGatewayAttributesRequestMsg(String deviceName, List<String> keysList, boolean client) {
return TransportApiProtos.GatewayAttributesRequestMsg.newBuilder() return TransportApiProtos.GatewayAttributesRequestMsg.newBuilder()
.setDeviceName(deviceName)
.addAllKeys(keysList)
.setClient(client) .setClient(client)
.addAllKeys(Arrays.asList(keys.split(",")))
.setDeviceName("Gateway Device Request Attributes")
.setId(1).build(); .setId(1).build();
} }
} }

2
application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/MqttAttributesRequestBackwardCompatibilityIntegrationTest.java

@ -85,7 +85,6 @@ public class MqttAttributesRequestBackwardCompatibilityIntegrationTest extends A
@Test @Test
public void testRequestAttributesValuesFromTheServerGatewayWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception { public void testRequestAttributesValuesFromTheServerGatewayWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception {
MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder()
.deviceName("Test Request attribute values from the server proto")
.gatewayName("Gateway Test Request attribute values from the server proto") .gatewayName("Gateway Test Request attribute values from the server proto")
.transportPayloadType(TransportPayloadType.PROTOBUF) .transportPayloadType(TransportPayloadType.PROTOBUF)
.enableCompatibilityWithJsonPayloadFormat(true) .enableCompatibilityWithJsonPayloadFormat(true)
@ -99,7 +98,6 @@ public class MqttAttributesRequestBackwardCompatibilityIntegrationTest extends A
public void testRequestAttributesValuesFromTheServerOnShortJsonTopicWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception { public void testRequestAttributesValuesFromTheServerOnShortJsonTopicWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception {
MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder()
.deviceName("Test Request attribute values from the server proto") .deviceName("Test Request attribute values from the server proto")
.gatewayName("Gateway Test Request attribute values from the server proto")
.transportPayloadType(TransportPayloadType.PROTOBUF) .transportPayloadType(TransportPayloadType.PROTOBUF)
.enableCompatibilityWithJsonPayloadFormat(true) .enableCompatibilityWithJsonPayloadFormat(true)
.useJsonPayloadFormatForDefaultDownlinkTopics(true) .useJsonPayloadFormatForDefaultDownlinkTopics(true)

1
application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/MqttAttributesRequestProtoIntegrationTest.java

@ -67,7 +67,6 @@ public class MqttAttributesRequestProtoIntegrationTest extends AbstractMqttAttri
@Test @Test
public void testRequestAttributesValuesFromTheServerGateway() throws Exception { public void testRequestAttributesValuesFromTheServerGateway() throws Exception {
MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder()
.deviceName("Test Request attribute values from the server proto")
.gatewayName("Gateway Test Request attribute values from the server proto") .gatewayName("Gateway Test Request attribute values from the server proto")
.transportPayloadType(TransportPayloadType.PROTOBUF) .transportPayloadType(TransportPayloadType.PROTOBUF)
.build(); .build();

18
application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/MqttAttributesUpdatesBackwardCompatibilityIntegrationTest.java

@ -16,14 +16,17 @@
package org.thingsboard.server.transport.mqtt.attributes.updates; package org.thingsboard.server.transport.mqtt.attributes.updates;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.junit.After;
import org.junit.Test; import org.junit.Test;
import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.TransportPayloadType;
import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties; import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties;
import org.thingsboard.server.transport.mqtt.attributes.AbstractMqttAttributesIntegrationTest; import org.thingsboard.server.transport.mqtt.attributes.AbstractMqttAttributesIntegrationTest;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_ATTRIBUTES_SHORT_JSON_TOPIC;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_ATTRIBUTES_SHORT_PROTO_TOPIC;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_ATTRIBUTES_SHORT_TOPIC;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_ATTRIBUTES_TOPIC;
@Slf4j @Slf4j
@DaoSqlTest @DaoSqlTest
public class MqttAttributesUpdatesBackwardCompatibilityIntegrationTest extends AbstractMqttAttributesIntegrationTest { public class MqttAttributesUpdatesBackwardCompatibilityIntegrationTest extends AbstractMqttAttributesIntegrationTest {
@ -36,7 +39,7 @@ public class MqttAttributesUpdatesBackwardCompatibilityIntegrationTest extends A
.enableCompatibilityWithJsonPayloadFormat(true) .enableCompatibilityWithJsonPayloadFormat(true)
.build(); .build();
processBeforeTest(configProperties); processBeforeTest(configProperties);
processProtoTestSubscribeToAttributesUpdates(MqttTopics.DEVICE_ATTRIBUTES_TOPIC); processProtoTestSubscribeToAttributesUpdates(DEVICE_ATTRIBUTES_TOPIC);
} }
@Test @Test
@ -48,7 +51,7 @@ public class MqttAttributesUpdatesBackwardCompatibilityIntegrationTest extends A
.useJsonPayloadFormatForDefaultDownlinkTopics(true) .useJsonPayloadFormatForDefaultDownlinkTopics(true)
.build(); .build();
super.processBeforeTest(configProperties); super.processBeforeTest(configProperties);
processJsonTestSubscribeToAttributesUpdates(MqttTopics.DEVICE_ATTRIBUTES_TOPIC); processJsonTestSubscribeToAttributesUpdates(DEVICE_ATTRIBUTES_TOPIC);
} }
@Test @Test
@ -60,7 +63,7 @@ public class MqttAttributesUpdatesBackwardCompatibilityIntegrationTest extends A
.useJsonPayloadFormatForDefaultDownlinkTopics(true) .useJsonPayloadFormatForDefaultDownlinkTopics(true)
.build(); .build();
super.processBeforeTest(configProperties); super.processBeforeTest(configProperties);
processProtoTestSubscribeToAttributesUpdates(MqttTopics.DEVICE_ATTRIBUTES_SHORT_TOPIC); processProtoTestSubscribeToAttributesUpdates(DEVICE_ATTRIBUTES_SHORT_TOPIC);
} }
@Test @Test
@ -72,7 +75,7 @@ public class MqttAttributesUpdatesBackwardCompatibilityIntegrationTest extends A
.useJsonPayloadFormatForDefaultDownlinkTopics(true) .useJsonPayloadFormatForDefaultDownlinkTopics(true)
.build(); .build();
super.processBeforeTest(configProperties); super.processBeforeTest(configProperties);
processJsonTestSubscribeToAttributesUpdates(MqttTopics.DEVICE_ATTRIBUTES_SHORT_JSON_TOPIC); processJsonTestSubscribeToAttributesUpdates(DEVICE_ATTRIBUTES_SHORT_JSON_TOPIC);
} }
@Test @Test
@ -84,13 +87,12 @@ public class MqttAttributesUpdatesBackwardCompatibilityIntegrationTest extends A
.useJsonPayloadFormatForDefaultDownlinkTopics(true) .useJsonPayloadFormatForDefaultDownlinkTopics(true)
.build(); .build();
super.processBeforeTest(configProperties); super.processBeforeTest(configProperties);
processProtoTestSubscribeToAttributesUpdates(MqttTopics.DEVICE_ATTRIBUTES_SHORT_PROTO_TOPIC); processProtoTestSubscribeToAttributesUpdates(DEVICE_ATTRIBUTES_SHORT_PROTO_TOPIC);
} }
@Test @Test
public void testProtoSubscribeToAttributesUpdatesFromTheServerGatewayWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception { public void testProtoSubscribeToAttributesUpdatesFromTheServerGatewayWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception {
MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder()
.deviceName("Test Subscribe to attribute updates")
.gatewayName("Gateway Test Subscribe to attribute updates") .gatewayName("Gateway Test Subscribe to attribute updates")
.transportPayloadType(TransportPayloadType.PROTOBUF) .transportPayloadType(TransportPayloadType.PROTOBUF)
.enableCompatibilityWithJsonPayloadFormat(true) .enableCompatibilityWithJsonPayloadFormat(true)

11
application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/MqttAttributesUpdatesIntegrationTest.java

@ -19,11 +19,14 @@ import lombok.extern.slf4j.Slf4j;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.TransportPayloadType;
import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties; import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties;
import org.thingsboard.server.transport.mqtt.attributes.AbstractMqttAttributesIntegrationTest; import org.thingsboard.server.transport.mqtt.attributes.AbstractMqttAttributesIntegrationTest;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_ATTRIBUTES_SHORT_JSON_TOPIC;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_ATTRIBUTES_SHORT_TOPIC;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_ATTRIBUTES_TOPIC;
@Slf4j @Slf4j
@DaoSqlTest @DaoSqlTest
public class MqttAttributesUpdatesIntegrationTest extends AbstractMqttAttributesIntegrationTest { public class MqttAttributesUpdatesIntegrationTest extends AbstractMqttAttributesIntegrationTest {
@ -40,17 +43,17 @@ public class MqttAttributesUpdatesIntegrationTest extends AbstractMqttAttributes
@Test @Test
public void testJsonSubscribeToAttributesUpdatesFromTheServer() throws Exception { public void testJsonSubscribeToAttributesUpdatesFromTheServer() throws Exception {
processJsonTestSubscribeToAttributesUpdates(MqttTopics.DEVICE_ATTRIBUTES_TOPIC); processJsonTestSubscribeToAttributesUpdates(DEVICE_ATTRIBUTES_TOPIC);
} }
@Test @Test
public void testJsonSubscribeToAttributesUpdatesFromTheServerOnShortTopic() throws Exception { public void testJsonSubscribeToAttributesUpdatesFromTheServerOnShortTopic() throws Exception {
processJsonTestSubscribeToAttributesUpdates(MqttTopics.DEVICE_ATTRIBUTES_SHORT_TOPIC); processJsonTestSubscribeToAttributesUpdates(DEVICE_ATTRIBUTES_SHORT_TOPIC);
} }
@Test @Test
public void testJsonSubscribeToAttributesUpdatesFromTheServerOnShortJsonTopic() throws Exception { public void testJsonSubscribeToAttributesUpdatesFromTheServerOnShortJsonTopic() throws Exception {
processJsonTestSubscribeToAttributesUpdates(MqttTopics.DEVICE_ATTRIBUTES_SHORT_JSON_TOPIC); processJsonTestSubscribeToAttributesUpdates(DEVICE_ATTRIBUTES_SHORT_JSON_TOPIC);
} }
@Test @Test

11
application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/MqttAttributesUpdatesJsonIntegrationTest.java

@ -19,11 +19,14 @@ import lombok.extern.slf4j.Slf4j;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.TransportPayloadType;
import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties; import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties;
import org.thingsboard.server.transport.mqtt.attributes.AbstractMqttAttributesIntegrationTest; import org.thingsboard.server.transport.mqtt.attributes.AbstractMqttAttributesIntegrationTest;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_ATTRIBUTES_SHORT_JSON_TOPIC;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_ATTRIBUTES_SHORT_TOPIC;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_ATTRIBUTES_TOPIC;
@Slf4j @Slf4j
@DaoSqlTest @DaoSqlTest
public class MqttAttributesUpdatesJsonIntegrationTest extends AbstractMqttAttributesIntegrationTest { public class MqttAttributesUpdatesJsonIntegrationTest extends AbstractMqttAttributesIntegrationTest {
@ -40,17 +43,17 @@ public class MqttAttributesUpdatesJsonIntegrationTest extends AbstractMqttAttrib
@Test @Test
public void testJsonSubscribeToAttributesUpdatesFromTheServer() throws Exception { public void testJsonSubscribeToAttributesUpdatesFromTheServer() throws Exception {
processJsonTestSubscribeToAttributesUpdates(MqttTopics.DEVICE_ATTRIBUTES_TOPIC); processJsonTestSubscribeToAttributesUpdates(DEVICE_ATTRIBUTES_TOPIC);
} }
@Test @Test
public void testJsonSubscribeToAttributesUpdatesFromTheServerOnShortTopic() throws Exception { public void testJsonSubscribeToAttributesUpdatesFromTheServerOnShortTopic() throws Exception {
processJsonTestSubscribeToAttributesUpdates(MqttTopics.DEVICE_ATTRIBUTES_SHORT_TOPIC); processJsonTestSubscribeToAttributesUpdates(DEVICE_ATTRIBUTES_SHORT_TOPIC);
} }
@Test @Test
public void testJsonSubscribeToAttributesUpdatesFromTheServerOnShortJsonTopic() throws Exception { public void testJsonSubscribeToAttributesUpdatesFromTheServerOnShortJsonTopic() throws Exception {
processJsonTestSubscribeToAttributesUpdates(MqttTopics.DEVICE_ATTRIBUTES_SHORT_JSON_TOPIC); processJsonTestSubscribeToAttributesUpdates(DEVICE_ATTRIBUTES_SHORT_JSON_TOPIC);
} }
@Test @Test

14
application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/MqttAttributesUpdatesProtoIntegrationTest.java

@ -19,11 +19,15 @@ import lombok.extern.slf4j.Slf4j;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.TransportPayloadType;
import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties; import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties;
import org.thingsboard.server.transport.mqtt.attributes.AbstractMqttAttributesIntegrationTest; import org.thingsboard.server.transport.mqtt.attributes.AbstractMqttAttributesIntegrationTest;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_ATTRIBUTES_SHORT_JSON_TOPIC;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_ATTRIBUTES_SHORT_PROTO_TOPIC;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_ATTRIBUTES_SHORT_TOPIC;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_ATTRIBUTES_TOPIC;
@Slf4j @Slf4j
@DaoSqlTest @DaoSqlTest
public class MqttAttributesUpdatesProtoIntegrationTest extends AbstractMqttAttributesIntegrationTest { public class MqttAttributesUpdatesProtoIntegrationTest extends AbstractMqttAttributesIntegrationTest {
@ -40,22 +44,22 @@ public class MqttAttributesUpdatesProtoIntegrationTest extends AbstractMqttAttri
@Test @Test
public void testProtoSubscribeToAttributesUpdatesFromTheServer() throws Exception { public void testProtoSubscribeToAttributesUpdatesFromTheServer() throws Exception {
processProtoTestSubscribeToAttributesUpdates(MqttTopics.DEVICE_ATTRIBUTES_TOPIC); processProtoTestSubscribeToAttributesUpdates(DEVICE_ATTRIBUTES_TOPIC);
} }
@Test @Test
public void testProtoSubscribeToAttributesUpdatesFromTheServerOnShortTopic() throws Exception { public void testProtoSubscribeToAttributesUpdatesFromTheServerOnShortTopic() throws Exception {
processProtoTestSubscribeToAttributesUpdates(MqttTopics.DEVICE_ATTRIBUTES_SHORT_TOPIC); processProtoTestSubscribeToAttributesUpdates(DEVICE_ATTRIBUTES_SHORT_TOPIC);
} }
@Test @Test
public void testProtoSubscribeToAttributesUpdatesFromTheServerOnShortJsonTopic() throws Exception { public void testProtoSubscribeToAttributesUpdatesFromTheServerOnShortJsonTopic() throws Exception {
processJsonTestSubscribeToAttributesUpdates(MqttTopics.DEVICE_ATTRIBUTES_SHORT_JSON_TOPIC); processJsonTestSubscribeToAttributesUpdates(DEVICE_ATTRIBUTES_SHORT_JSON_TOPIC);
} }
@Test @Test
public void testProtoSubscribeToAttributesUpdatesFromTheServerOnShortProtoTopic() throws Exception { public void testProtoSubscribeToAttributesUpdatesFromTheServerOnShortProtoTopic() throws Exception {
processProtoTestSubscribeToAttributesUpdates(MqttTopics.DEVICE_ATTRIBUTES_SHORT_PROTO_TOPIC); processProtoTestSubscribeToAttributesUpdates(DEVICE_ATTRIBUTES_SHORT_PROTO_TOPIC);
} }
@Test @Test

31
application/src/test/java/org/thingsboard/server/transport/mqtt/claim/MqttClaimDeviceTest.java

@ -16,28 +16,26 @@
package org.thingsboard.server.transport.mqtt.claim; package org.thingsboard.server.transport.mqtt.claim;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.eclipse.paho.client.mqttv3.MqttMessage;
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.ClaimRequest; import org.thingsboard.server.common.data.ClaimRequest;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.TransportPayloadType;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.device.claim.ClaimResponse; import org.thingsboard.server.dao.device.claim.ClaimResponse;
import org.thingsboard.server.dao.device.claim.ClaimResult; import org.thingsboard.server.dao.device.claim.ClaimResult;
import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.gen.transport.TransportApiProtos; import org.thingsboard.server.gen.transport.TransportApiProtos;
import org.thingsboard.server.transport.mqtt.AbstractMqttIntegrationTest; import org.thingsboard.server.transport.mqtt.AbstractMqttIntegrationTest;
import org.thingsboard.server.transport.mqtt.MqttTestClient;
import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties; import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_CLAIM_TOPIC;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.GATEWAY_CLAIM_TOPIC;
@Slf4j @Slf4j
@DaoSqlTest @DaoSqlTest
@ -99,7 +97,8 @@ public class MqttClaimDeviceTest extends AbstractMqttIntegrationTest {
protected void processTestClaimingDevice(boolean emptyPayload) throws Exception { protected void processTestClaimingDevice(boolean emptyPayload) throws Exception {
MqttAsyncClient client = getMqttAsyncClient(accessToken); MqttTestClient client = new MqttTestClient();
client.connectAndWait(accessToken);
byte[] payloadBytes; byte[] payloadBytes;
byte[] failurePayloadBytes; byte[] failurePayloadBytes;
if (emptyPayload) { if (emptyPayload) {
@ -112,8 +111,8 @@ public class MqttClaimDeviceTest extends AbstractMqttIntegrationTest {
validateClaimResponse(emptyPayload, client, payloadBytes, failurePayloadBytes); validateClaimResponse(emptyPayload, client, payloadBytes, failurePayloadBytes);
} }
protected void validateClaimResponse(boolean emptyPayload, MqttAsyncClient client, byte[] payloadBytes, byte[] failurePayloadBytes) throws Exception { protected void validateClaimResponse(boolean emptyPayload, MqttTestClient client, byte[] payloadBytes, byte[] failurePayloadBytes) throws Exception {
client.publish(MqttTopics.DEVICE_CLAIM_TOPIC, new MqttMessage(failurePayloadBytes)); client.publishAndWait(DEVICE_CLAIM_TOPIC, failurePayloadBytes);
loginUser(customerAdmin.getName(), CUSTOMER_USER_PASSWORD); loginUser(customerAdmin.getName(), CUSTOMER_USER_PASSWORD);
ClaimRequest claimRequest; ClaimRequest claimRequest;
@ -131,7 +130,8 @@ public class MqttClaimDeviceTest extends AbstractMqttIntegrationTest {
assertEquals(claimResponse, ClaimResponse.FAILURE); assertEquals(claimResponse, ClaimResponse.FAILURE);
client.publish(MqttTopics.DEVICE_CLAIM_TOPIC, new MqttMessage(payloadBytes)); client.publishAndWait(DEVICE_CLAIM_TOPIC, payloadBytes);
client.disconnect();
ClaimResult claimResult = doExecuteWithRetriesAndInterval( ClaimResult claimResult = doExecuteWithRetriesAndInterval(
() -> doPostClaimAsync("/api/customer/device/" + savedDevice.getName() + "/claim", claimRequest, ClaimResult.class, status().isOk()), () -> doPostClaimAsync("/api/customer/device/" + savedDevice.getName() + "/claim", claimRequest, ClaimResult.class, status().isOk()),
@ -148,8 +148,8 @@ public class MqttClaimDeviceTest extends AbstractMqttIntegrationTest {
assertEquals(claimResponse, ClaimResponse.CLAIMED); assertEquals(claimResponse, ClaimResponse.CLAIMED);
} }
protected void validateGatewayClaimResponse(String deviceName, boolean emptyPayload, MqttAsyncClient client, byte[] failurePayloadBytes, byte[] payloadBytes) throws Exception { protected void validateGatewayClaimResponse(String deviceName, boolean emptyPayload, MqttTestClient client, byte[] failurePayloadBytes, byte[] payloadBytes) throws Exception {
client.publish(MqttTopics.GATEWAY_CLAIM_TOPIC, new MqttMessage(failurePayloadBytes)); client.publishAndWait(GATEWAY_CLAIM_TOPIC, failurePayloadBytes);
Device savedDevice = doExecuteWithRetriesAndInterval( Device savedDevice = doExecuteWithRetriesAndInterval(
() -> doGet("/api/tenant/devices?deviceName=" + deviceName, Device.class), () -> doGet("/api/tenant/devices?deviceName=" + deviceName, Device.class),
@ -170,7 +170,8 @@ public class MqttClaimDeviceTest extends AbstractMqttIntegrationTest {
ClaimResponse claimResponse = doPostClaimAsync("/api/customer/device/" + deviceName + "/claim", claimRequest, ClaimResponse.class, status().isBadRequest()); ClaimResponse claimResponse = doPostClaimAsync("/api/customer/device/" + deviceName + "/claim", claimRequest, ClaimResponse.class, status().isBadRequest());
assertEquals(claimResponse, ClaimResponse.FAILURE); assertEquals(claimResponse, ClaimResponse.FAILURE);
client.publish(MqttTopics.GATEWAY_CLAIM_TOPIC, new MqttMessage(payloadBytes)); client.publishAndWait(GATEWAY_CLAIM_TOPIC, payloadBytes);
client.disconnect();
ClaimResult claimResult = doExecuteWithRetriesAndInterval( ClaimResult claimResult = doExecuteWithRetriesAndInterval(
() -> doPostClaimAsync("/api/customer/device/" + deviceName + "/claim", claimRequest, ClaimResult.class, status().isOk()), () -> doPostClaimAsync("/api/customer/device/" + deviceName + "/claim", claimRequest, ClaimResult.class, status().isOk()),
@ -189,7 +190,8 @@ public class MqttClaimDeviceTest extends AbstractMqttIntegrationTest {
} }
protected void processTestGatewayClaimingDevice(String deviceName, boolean emptyPayload) throws Exception { protected void processTestGatewayClaimingDevice(String deviceName, boolean emptyPayload) throws Exception {
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken); MqttTestClient client = new MqttTestClient();
client.connectAndWait(gatewayAccessToken);
byte[] failurePayloadBytes; byte[] failurePayloadBytes;
byte[] payloadBytes; byte[] payloadBytes;
String failurePayload; String failurePayload;
@ -207,7 +209,8 @@ public class MqttClaimDeviceTest extends AbstractMqttIntegrationTest {
} }
protected void processProtoTestGatewayClaimDevice(String deviceName, boolean emptyPayload) throws Exception { protected void processProtoTestGatewayClaimDevice(String deviceName, boolean emptyPayload) throws Exception {
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken); MqttTestClient client = new MqttTestClient();
client.connectAndWait(gatewayAccessToken);
byte[] failurePayloadBytes; byte[] failurePayloadBytes;
byte[] payloadBytes; byte[] payloadBytes;
if (emptyPayload) { if (emptyPayload) {

5
application/src/test/java/org/thingsboard/server/transport/mqtt/claim/MqttClaimProtoDeviceTest.java

@ -16,12 +16,12 @@
package org.thingsboard.server.transport.mqtt.claim; package org.thingsboard.server.transport.mqtt.claim;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.TransportPayloadType;
import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.gen.transport.TransportApiProtos; import org.thingsboard.server.gen.transport.TransportApiProtos;
import org.thingsboard.server.transport.mqtt.MqttTestClient;
import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties; import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties;
@Slf4j @Slf4j
@ -60,7 +60,8 @@ public class MqttClaimProtoDeviceTest extends MqttClaimDeviceTest {
} }
protected void processTestClaimingDevice(boolean emptyPayload) throws Exception { protected void processTestClaimingDevice(boolean emptyPayload) throws Exception {
MqttAsyncClient client = getMqttAsyncClient(accessToken); MqttTestClient client = new MqttTestClient();
client.connectAndWait(accessToken);
byte[] payloadBytes; byte[] payloadBytes;
if (emptyPayload) { if (emptyPayload) {
payloadBytes = getClaimDevice(0, emptyPayload).toByteArray(); payloadBytes = getClaimDevice(0, emptyPayload).toByteArray();

65
application/src/test/java/org/thingsboard/server/transport/mqtt/credentials/BasicMqttCredentialsTest.java

@ -17,22 +17,17 @@ package org.thingsboard.server.transport.mqtt.credentials;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.RandomStringUtils;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttSecurityException; import org.eclipse.paho.client.mqttv3.MqttSecurityException;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials;
import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.common.data.security.DeviceCredentialsType;
import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.transport.mqtt.AbstractMqttIntegrationTest; import org.thingsboard.server.transport.mqtt.AbstractMqttIntegrationTest;
import org.thingsboard.server.transport.mqtt.MqttTestClient;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
@ -43,6 +38,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_TELEMETRY_TOPIC;
@DaoSqlTest @DaoSqlTest
public class BasicMqttCredentialsTest extends AbstractMqttIntegrationTest { public class BasicMqttCredentialsTest extends AbstractMqttIntegrationTest {
@ -93,33 +89,51 @@ public class BasicMqttCredentialsTest extends AbstractMqttIntegrationTest {
@Test @Test
public void testCorrectCredentials() throws Exception { public void testCorrectCredentials() throws Exception {
// Check that correct devices receive telemetry // Check that correct devices receive telemetry
testTelemetryIsDelivered(accessTokenDevice, getMqttAsyncClient(null, USER_NAME1, null)); MqttTestClient mqttTestClient1 = new MqttTestClient();
testTelemetryIsDelivered(clientIdDevice, getMqttAsyncClient(CLIENT_ID, null, null)); mqttTestClient1.connectAndWait(USER_NAME1);
testTelemetryIsDelivered(clientIdAndUserNameDevice1, getMqttAsyncClient(CLIENT_ID, USER_NAME1, null));
testTelemetryIsDelivered(clientIdAndUserNameAndPasswordDevice2, getMqttAsyncClient(CLIENT_ID, USER_NAME2, PASSWORD)); MqttTestClient mqttTestClient2 = new MqttTestClient(CLIENT_ID);
mqttTestClient2.connectAndWait();
MqttTestClient mqttTestClient3 = new MqttTestClient(CLIENT_ID);
mqttTestClient3.connectAndWait(USER_NAME1);
MqttTestClient mqttTestClient4 = new MqttTestClient(CLIENT_ID);
mqttTestClient4.connectAndWait(USER_NAME2, PASSWORD);
// Also correct. Random clientId and password, but matches access token
MqttTestClient mqttTestClient5 = new MqttTestClient(RandomStringUtils.randomAlphanumeric(10));
mqttTestClient5.connectAndWait(USER_NAME2, RandomStringUtils.randomAlphanumeric(10));
testTelemetryIsDelivered(accessTokenDevice, mqttTestClient1);
testTelemetryIsDelivered(clientIdDevice, mqttTestClient2);
testTelemetryIsDelivered(clientIdAndUserNameDevice1, mqttTestClient3);
testTelemetryIsDelivered(clientIdAndUserNameAndPasswordDevice2, mqttTestClient4);
// Also correct. Random clientId and password, but matches access token // Also correct. Random clientId and password, but matches access token
testTelemetryIsDelivered(accessToken2Device, getMqttAsyncClient(RandomStringUtils.randomAlphanumeric(10), USER_NAME2, RandomStringUtils.randomAlphanumeric(10))); testTelemetryIsDelivered(accessToken2Device, mqttTestClient5);
} }
@Test(expected = MqttSecurityException.class) @Test(expected = MqttSecurityException.class)
public void testCorrectClientIdAndUserNameButWrongPassword() throws Exception { public void testCorrectClientIdAndUserNameButWrongPassword() throws Exception {
// Not correct. Correct clientId and username, but wrong password // Not correct. Correct clientId and username, but wrong password
testTelemetryIsNotDelivered(clientIdAndUserNameAndPasswordDevice3, getMqttAsyncClient(CLIENT_ID, USER_NAME3, "WRONG PASSWORD")); MqttTestClient mqttTestClient = new MqttTestClient(CLIENT_ID);
mqttTestClient.connectAndWait(USER_NAME3, "WRONG PASSWORD");
testTelemetryIsNotDelivered(clientIdAndUserNameAndPasswordDevice3, mqttTestClient);
} }
private void testTelemetryIsDelivered(Device device, MqttAsyncClient client) throws Exception { private void testTelemetryIsDelivered(Device device, MqttTestClient client) throws Exception {
testTelemetryIsDelivered(device, client, true); testTelemetryIsDelivered(device, client, true);
} }
private void testTelemetryIsNotDelivered(Device device, MqttAsyncClient client) throws Exception { private void testTelemetryIsNotDelivered(Device device, MqttTestClient client) throws Exception {
testTelemetryIsDelivered(device, client, false); testTelemetryIsDelivered(device, client, false);
} }
private void testTelemetryIsDelivered(Device device, MqttAsyncClient client, boolean ok) throws Exception { private void testTelemetryIsDelivered(Device device, MqttTestClient client, boolean ok) throws Exception {
String randomKey = RandomStringUtils.randomAlphanumeric(10); String randomKey = RandomStringUtils.randomAlphanumeric(10);
List<String> expectedKeys = Arrays.asList(randomKey); List<String> expectedKeys = Arrays.asList(randomKey);
publishMqttMsg(client, JacksonUtil.toString(JacksonUtil.newObjectNode().put(randomKey, true)).getBytes(), MqttTopics.DEVICE_TELEMETRY_TOPIC); client.publishAndWait(DEVICE_TELEMETRY_TOPIC, JacksonUtil.toString(JacksonUtil.newObjectNode().put(randomKey, true)).getBytes());
String deviceId = device.getId().getId().toString(); String deviceId = device.getId().getId().toString();
@ -146,24 +160,7 @@ public class BasicMqttCredentialsTest extends AbstractMqttIntegrationTest {
} else { } else {
assertNull(actualKeys); assertNull(actualKeys);
} }
client.disconnect().waitForCompletion(); client.disconnect();
}
protected MqttAsyncClient getMqttAsyncClient(String clientId, String username, String password) throws MqttException {
if (StringUtils.isEmpty(clientId)) {
clientId = MqttAsyncClient.generateClientId();
}
MqttAsyncClient client = new MqttAsyncClient(MQTT_URL, clientId, new MemoryPersistence());
MqttConnectOptions options = new MqttConnectOptions();
if (StringUtils.isNotEmpty(username)) {
options.setUserName(username);
}
if (StringUtils.isNotEmpty(password)) {
options.setPassword(password.toCharArray());
}
client.connect(options).waitForCompletion();
return client;
} }
private Device createDevice(String deviceName, BasicMqttCredentials clientIdCredValue) throws Exception { private Device createDevice(String deviceName, BasicMqttCredentials clientIdCredValue) throws Exception {

149
application/src/test/java/org/thingsboard/server/transport/mqtt/provision/MqttProvisionJsonDeviceTest.java

@ -15,13 +15,9 @@
*/ */
package org.thingsboard.server.transport.mqtt.provision; package org.thingsboard.server.transport.mqtt.provision;
import com.google.gson.JsonObject; import com.fasterxml.jackson.databind.JsonNode;
import io.netty.handler.codec.mqtt.MqttQoS; import io.netty.handler.codec.mqtt.MqttQoS;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -30,20 +26,22 @@ import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.DeviceProfileProvisionType;
import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.TransportPayloadType;
import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials;
import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.msg.EncryptionUtil; import org.thingsboard.server.common.msg.EncryptionUtil;
import org.thingsboard.server.common.transport.util.JsonUtils;
import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceCredentialsService;
import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus; import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus;
import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.transport.mqtt.AbstractMqttIntegrationTest; import org.thingsboard.server.transport.mqtt.AbstractMqttIntegrationTest;
import org.thingsboard.server.transport.mqtt.MqttTestCallback;
import org.thingsboard.server.transport.mqtt.MqttTestClient;
import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties; import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_PROVISION_REQUEST_TOPIC;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC;
@Slf4j @Slf4j
@DaoSqlTest @DaoSqlTest
public class MqttProvisionJsonDeviceTest extends AbstractMqttIntegrationTest { public class MqttProvisionJsonDeviceTest extends AbstractMqttIntegrationTest {
@ -97,10 +95,12 @@ public class MqttProvisionJsonDeviceTest extends AbstractMqttIntegrationTest {
.provisionType(DeviceProfileProvisionType.DISABLED) .provisionType(DeviceProfileProvisionType.DISABLED)
.build(); .build();
super.processBeforeTest(configProperties); super.processBeforeTest(configProperties);
byte[] result = createMqttClientAndPublish().getPayloadBytes(); byte[] result = createMqttClientAndPublish();
JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); JsonNode response = JacksonUtil.fromBytes(result);
Assert.assertEquals("Provision data was not found!", response.get("errorMsg").getAsString()); Assert.assertTrue(response.hasNonNull("errorMsg"));
Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.get("status").getAsString()); Assert.assertTrue(response.hasNonNull("status"));
Assert.assertEquals("Provision data was not found!", response.get("errorMsg").asText());
Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.get("status").asText());
} }
@ -113,8 +113,10 @@ public class MqttProvisionJsonDeviceTest extends AbstractMqttIntegrationTest {
.provisionSecret("testProvisionSecret") .provisionSecret("testProvisionSecret")
.build(); .build();
super.processBeforeTest(configProperties); super.processBeforeTest(configProperties);
byte[] result = createMqttClientAndPublish().getPayloadBytes(); byte[] result = createMqttClientAndPublish();
JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); JsonNode response = JacksonUtil.fromBytes(result);
Assert.assertTrue(response.hasNonNull("credentialsType"));
Assert.assertTrue(response.hasNonNull("status"));
Device createdDevice = deviceService.findDeviceByTenantIdAndName(tenantId, "Test Provision device"); Device createdDevice = deviceService.findDeviceByTenantIdAndName(tenantId, "Test Provision device");
@ -122,8 +124,8 @@ public class MqttProvisionJsonDeviceTest extends AbstractMqttIntegrationTest {
DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, createdDevice.getId()); DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, createdDevice.getId());
Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").asText());
Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("status").getAsString()); Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("status").asText());
} }
@ -137,8 +139,10 @@ public class MqttProvisionJsonDeviceTest extends AbstractMqttIntegrationTest {
.build(); .build();
super.processBeforeTest(configProperties); super.processBeforeTest(configProperties);
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);
JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); JsonNode response = JacksonUtil.fromBytes(result);
Assert.assertTrue(response.hasNonNull("credentialsType"));
Assert.assertTrue(response.hasNonNull("status"));
Device createdDevice = deviceService.findDeviceByTenantIdAndName(tenantId, "Test Provision device"); Device createdDevice = deviceService.findDeviceByTenantIdAndName(tenantId, "Test Provision device");
@ -146,10 +150,10 @@ public class MqttProvisionJsonDeviceTest extends AbstractMqttIntegrationTest {
DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, createdDevice.getId()); DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, createdDevice.getId());
Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").asText());
Assert.assertEquals(deviceCredentials.getCredentialsType().name(), "ACCESS_TOKEN"); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), "ACCESS_TOKEN");
Assert.assertEquals(deviceCredentials.getCredentialsId(), "test_token"); Assert.assertEquals(deviceCredentials.getCredentialsId(), "test_token");
Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("status").getAsString()); Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("status").asText());
} }
@ -163,8 +167,10 @@ public class MqttProvisionJsonDeviceTest extends AbstractMqttIntegrationTest {
.build(); .build();
super.processBeforeTest(configProperties); super.processBeforeTest(configProperties);
String requestCredentials = ",\"credentialsType\": \"X509_CERTIFICATE\",\"hash\": \"testHash\""; String requestCredentials = ",\"credentialsType\": \"X509_CERTIFICATE\",\"hash\": \"testHash\"";
byte[] result = createMqttClientAndPublish(requestCredentials).getPayloadBytes(); byte[] result = createMqttClientAndPublish(requestCredentials);
JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); JsonNode response = JacksonUtil.fromBytes(result);
Assert.assertTrue(response.hasNonNull("credentialsType"));
Assert.assertTrue(response.hasNonNull("status"));
Device createdDevice = deviceService.findDeviceByTenantIdAndName(tenantId, "Test Provision device"); Device createdDevice = deviceService.findDeviceByTenantIdAndName(tenantId, "Test Provision device");
@ -172,7 +178,7 @@ public class MqttProvisionJsonDeviceTest extends AbstractMqttIntegrationTest {
DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, createdDevice.getId()); DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, createdDevice.getId());
Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").asText());
Assert.assertEquals(deviceCredentials.getCredentialsType().name(), "X509_CERTIFICATE"); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), "X509_CERTIFICATE");
String cert = EncryptionUtil.certTrimNewLines(deviceCredentials.getCredentialsValue()); String cert = EncryptionUtil.certTrimNewLines(deviceCredentials.getCredentialsValue());
@ -181,7 +187,7 @@ public class MqttProvisionJsonDeviceTest extends AbstractMqttIntegrationTest {
Assert.assertEquals(deviceCredentials.getCredentialsId(), sha3Hash); Assert.assertEquals(deviceCredentials.getCredentialsId(), sha3Hash);
Assert.assertEquals(deviceCredentials.getCredentialsValue(), "testHash"); Assert.assertEquals(deviceCredentials.getCredentialsValue(), "testHash");
Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("status").getAsString()); Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("status").asText());
} }
@ -195,8 +201,10 @@ public class MqttProvisionJsonDeviceTest extends AbstractMqttIntegrationTest {
.build(); .build();
super.processBeforeTest(configProperties); super.processBeforeTest(configProperties);
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);
JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); JsonNode response = JacksonUtil.fromBytes(result);
Assert.assertTrue(response.hasNonNull("credentialsType"));
Assert.assertTrue(response.hasNonNull("status"));
Device createdDevice = deviceService.findDeviceByTenantIdAndName(tenantId, "Test Provision device"); Device createdDevice = deviceService.findDeviceByTenantIdAndName(tenantId, "Test Provision device");
@ -204,7 +212,7 @@ public class MqttProvisionJsonDeviceTest extends AbstractMqttIntegrationTest {
DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, createdDevice.getId()); DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, createdDevice.getId());
Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").asText());
Assert.assertEquals(deviceCredentials.getCredentialsType().name(), "MQTT_BASIC"); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), "MQTT_BASIC");
Assert.assertEquals(deviceCredentials.getCredentialsId(), EncryptionUtil.getSha3Hash("|", "test_clientId", "test_username")); Assert.assertEquals(deviceCredentials.getCredentialsId(), EncryptionUtil.getSha3Hash("|", "test_clientId", "test_username"));
@ -214,7 +222,7 @@ public class MqttProvisionJsonDeviceTest extends AbstractMqttIntegrationTest {
mqttCredentials.setPassword("test_password"); mqttCredentials.setPassword("test_password");
Assert.assertEquals(deviceCredentials.getCredentialsValue(), JacksonUtil.toString(mqttCredentials)); Assert.assertEquals(deviceCredentials.getCredentialsValue(), JacksonUtil.toString(mqttCredentials));
Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("status").getAsString()); Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("status").asText());
} }
protected void processTestProvisioningCheckPreProvisionedDevice() throws Exception { protected void processTestProvisioningCheckPreProvisionedDevice() throws Exception {
@ -226,13 +234,15 @@ public class MqttProvisionJsonDeviceTest extends AbstractMqttIntegrationTest {
.provisionSecret("testProvisionSecret") .provisionSecret("testProvisionSecret")
.build(); .build();
super.processBeforeTest(configProperties); super.processBeforeTest(configProperties);
byte[] result = createMqttClientAndPublish().getPayloadBytes(); byte[] result = createMqttClientAndPublish();
JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); JsonNode response = JacksonUtil.fromBytes(result);
Assert.assertTrue(response.hasNonNull("credentialsType"));
Assert.assertTrue(response.hasNonNull("status"));
DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, savedDevice.getId()); DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, savedDevice.getId());
Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").asText());
Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("status").getAsString()); Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("status").asText());
} }
protected void processTestProvisioningWithBadKeyDevice() throws Exception { protected void processTestProvisioningWithBadKeyDevice() throws Exception {
@ -244,72 +254,29 @@ public class MqttProvisionJsonDeviceTest extends AbstractMqttIntegrationTest {
.provisionSecret("testProvisionSecret") .provisionSecret("testProvisionSecret")
.build(); .build();
super.processBeforeTest(configProperties); super.processBeforeTest(configProperties);
byte[] result = createMqttClientAndPublish().getPayloadBytes(); byte[] result = createMqttClientAndPublish();
JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); JsonNode response = JacksonUtil.fromBytes(result);
Assert.assertEquals("Provision data was not found!", response.get("errorMsg").getAsString()); Assert.assertTrue(response.hasNonNull("errorMsg"));
Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.get("status").getAsString()); Assert.assertTrue(response.hasNonNull("status"));
Assert.assertEquals("Provision data was not found!", response.get("errorMsg").asText());
Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.get("status").asText());
} }
protected TestMqttCallback createMqttClientAndPublish() throws Exception { protected byte[] createMqttClientAndPublish() throws Exception {
return createMqttClientAndPublish(""); return createMqttClientAndPublish("");
} }
protected TestMqttCallback createMqttClientAndPublish(String deviceCredentials) throws Exception { protected byte[] createMqttClientAndPublish(String deviceCredentials) throws Exception {
String provisionRequestMsg = createTestProvisionMessage(deviceCredentials); String provisionRequestMsg = createTestProvisionMessage(deviceCredentials);
MqttAsyncClient client = getMqttAsyncClient("provision"); MqttTestClient client = new MqttTestClient();
TestMqttCallback onProvisionCallback = getTestMqttCallback(); client.connectAndWait("provision");
MqttTestCallback onProvisionCallback = new MqttTestCallback(DEVICE_PROVISION_RESPONSE_TOPIC);
client.setCallback(onProvisionCallback); client.setCallback(onProvisionCallback);
client.subscribe(MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC, MqttQoS.AT_MOST_ONCE.value()); client.subscribe(DEVICE_PROVISION_RESPONSE_TOPIC, MqttQoS.AT_MOST_ONCE);
Thread.sleep(2000); client.publishAndWait(DEVICE_PROVISION_REQUEST_TOPIC, provisionRequestMsg.getBytes());
client.publish(MqttTopics.DEVICE_PROVISION_REQUEST_TOPIC, new MqttMessage(provisionRequestMsg.getBytes())); onProvisionCallback.getSubscribeLatch().await(3, TimeUnit.SECONDS);
onProvisionCallback.getLatch().await(3, TimeUnit.SECONDS); client.disconnect();
return onProvisionCallback; return onProvisionCallback.getPayloadBytes();
}
protected TestMqttCallback getTestMqttCallback() {
CountDownLatch latch = new CountDownLatch(1);
return new TestMqttCallback(latch);
}
protected static class TestMqttCallback implements MqttCallback {
private final CountDownLatch latch;
private Integer qoS;
private byte[] payloadBytes;
TestMqttCallback(CountDownLatch latch) {
this.latch = latch;
}
public int getQoS() {
return qoS;
}
public byte[] getPayloadBytes() {
return payloadBytes;
}
public CountDownLatch getLatch() {
return latch;
}
@Override
public void connectionLost(Throwable throwable) {
}
@Override
public void messageArrived(String requestTopic, MqttMessage mqttMessage) throws Exception {
qoS = mqttMessage.getQos();
payloadBytes = mqttMessage.getPayload();
latch.countDown();
}
@Override
public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
}
} }
protected String createTestProvisionMessage(String deviceCredentials) { protected String createTestProvisionMessage(String deviceCredentials) {

114
application/src/test/java/org/thingsboard/server/transport/mqtt/provision/MqttProvisionProtoDeviceTest.java

@ -17,10 +17,6 @@ package org.thingsboard.server.transport.mqtt.provision;
import io.netty.handler.codec.mqtt.MqttQoS; import io.netty.handler.codec.mqtt.MqttQoS;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -29,7 +25,6 @@ import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.DeviceProfileProvisionType;
import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.TransportPayloadType;
import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials;
import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.common.data.security.DeviceCredentialsType;
import org.thingsboard.server.common.msg.EncryptionUtil; import org.thingsboard.server.common.msg.EncryptionUtil;
@ -46,11 +41,15 @@ import org.thingsboard.server.gen.transport.TransportProtos.ValidateBasicMqttCre
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg;
import org.thingsboard.server.transport.mqtt.AbstractMqttIntegrationTest; import org.thingsboard.server.transport.mqtt.AbstractMqttIntegrationTest;
import org.thingsboard.server.transport.mqtt.MqttTestCallback;
import org.thingsboard.server.transport.mqtt.MqttTestClient;
import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties; import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_PROVISION_REQUEST_TOPIC;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC;
@Slf4j @Slf4j
@DaoSqlTest @DaoSqlTest
public class MqttProvisionProtoDeviceTest extends AbstractMqttIntegrationTest { public class MqttProvisionProtoDeviceTest extends AbstractMqttIntegrationTest {
@ -104,9 +103,9 @@ public class MqttProvisionProtoDeviceTest extends AbstractMqttIntegrationTest {
.provisionType(DeviceProfileProvisionType.DISABLED) .provisionType(DeviceProfileProvisionType.DISABLED)
.build(); .build();
processBeforeTest(configProperties); processBeforeTest(configProperties);
ProvisionDeviceResponseMsg result = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes()); ProvisionDeviceResponseMsg result = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish());
Assert.assertNotNull(result); Assert.assertNotNull(result);
Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), result.getStatus().toString()); Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), result.getStatus().name());
} }
protected void processTestProvisioningCreateNewDeviceWithoutCredentials() throws Exception { protected void processTestProvisioningCreateNewDeviceWithoutCredentials() throws Exception {
@ -118,7 +117,7 @@ public class MqttProvisionProtoDeviceTest extends AbstractMqttIntegrationTest {
.provisionSecret("testProvisionSecret") .provisionSecret("testProvisionSecret")
.build(); .build();
processBeforeTest(configProperties); processBeforeTest(configProperties);
ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes()); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish());
Device createdDevice = deviceService.findDeviceByTenantIdAndName(tenantId, "Test Provision device"); Device createdDevice = deviceService.findDeviceByTenantIdAndName(tenantId, "Test Provision device");
@ -126,8 +125,8 @@ public class MqttProvisionProtoDeviceTest extends AbstractMqttIntegrationTest {
DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, createdDevice.getId()); DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, createdDevice.getId());
Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getCredentialsType().toString()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getCredentialsType().name());
Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getStatus().toString()); Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getStatus().name());
} }
protected void processTestProvisioningCreateNewDeviceWithAccessToken() throws Exception { protected void processTestProvisioningCreateNewDeviceWithAccessToken() throws Exception {
@ -139,9 +138,12 @@ public class MqttProvisionProtoDeviceTest extends AbstractMqttIntegrationTest {
.provisionSecret("testProvisionSecret") .provisionSecret("testProvisionSecret")
.build(); .build();
processBeforeTest(configProperties); processBeforeTest(configProperties);
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)));
Device createdDevice = deviceService.findDeviceByTenantIdAndName(tenantId, "Test Provision device"); Device createdDevice = deviceService.findDeviceByTenantIdAndName(tenantId, "Test Provision device");
@ -164,9 +166,13 @@ public class MqttProvisionProtoDeviceTest extends AbstractMqttIntegrationTest {
.provisionSecret("testProvisionSecret") .provisionSecret("testProvisionSecret")
.build(); .build();
processBeforeTest(configProperties); processBeforeTest(configProperties);
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)));
Device createdDevice = deviceService.findDeviceByTenantIdAndName(tenantId, "Test Provision device"); Device createdDevice = deviceService.findDeviceByTenantIdAndName(tenantId, "Test Provision device");
@ -203,7 +209,8 @@ public class MqttProvisionProtoDeviceTest extends AbstractMqttIntegrationTest {
.build() .build()
).build(); ).build();
ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish(createTestsProvisionMessage(CredentialsType.MQTT_BASIC, requestCredentials)).getPayloadBytes()); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(
createMqttClientAndPublish(createTestsProvisionMessage(CredentialsType.MQTT_BASIC, requestCredentials)));
Device createdDevice = deviceService.findDeviceByTenantIdAndName(tenantId, "Test Provision device"); Device createdDevice = deviceService.findDeviceByTenantIdAndName(tenantId, "Test Provision device");
@ -233,12 +240,12 @@ public class MqttProvisionProtoDeviceTest extends AbstractMqttIntegrationTest {
.provisionSecret("testProvisionSecret") .provisionSecret("testProvisionSecret")
.build(); .build();
processBeforeTest(configProperties); processBeforeTest(configProperties);
ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes()); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish());
DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, savedDevice.getId()); DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, savedDevice.getId());
Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getCredentialsType().toString()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getCredentialsType().name());
Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getStatus().toString()); Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getStatus().name());
} }
protected void processTestProvisioningWithBadKeyDevice() throws Exception { protected void processTestProvisioningWithBadKeyDevice() throws Exception {
@ -250,70 +257,25 @@ public class MqttProvisionProtoDeviceTest extends AbstractMqttIntegrationTest {
.provisionSecret("testProvisionSecret") .provisionSecret("testProvisionSecret")
.build(); .build();
processBeforeTest(configProperties); processBeforeTest(configProperties);
ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes()); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish());
Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.getStatus().toString()); Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.getStatus().name());
} }
protected TestMqttCallback createMqttClientAndPublish() throws Exception { protected byte[] createMqttClientAndPublish() throws Exception {
byte[] provisionRequestMsg = createTestProvisionMessage(); byte[] provisionRequestMsg = createTestProvisionMessage();
return createMqttClientAndPublish(provisionRequestMsg); return createMqttClientAndPublish(provisionRequestMsg);
} }
protected TestMqttCallback createMqttClientAndPublish(byte[] provisionRequestMsg) throws Exception { protected byte[] createMqttClientAndPublish(byte[] provisionRequestMsg) throws Exception {
MqttAsyncClient client = getMqttAsyncClient("provision"); MqttTestClient client = new MqttTestClient();
TestMqttCallback onProvisionCallback = getTestMqttCallback(); client.connectAndWait("provision");
MqttTestCallback onProvisionCallback = new MqttTestCallback(DEVICE_PROVISION_RESPONSE_TOPIC);
client.setCallback(onProvisionCallback); client.setCallback(onProvisionCallback);
client.subscribe(MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC, MqttQoS.AT_MOST_ONCE.value()); client.subscribe(DEVICE_PROVISION_RESPONSE_TOPIC, MqttQoS.AT_MOST_ONCE);
Thread.sleep(2000); client.publishAndWait(DEVICE_PROVISION_REQUEST_TOPIC, provisionRequestMsg);
client.publish(MqttTopics.DEVICE_PROVISION_REQUEST_TOPIC, new MqttMessage(provisionRequestMsg)); onProvisionCallback.getSubscribeLatch().await(3, TimeUnit.SECONDS);
onProvisionCallback.getLatch().await(3, TimeUnit.SECONDS); client.disconnect();
return onProvisionCallback; return onProvisionCallback.getPayloadBytes();
}
protected TestMqttCallback getTestMqttCallback() {
CountDownLatch latch = new CountDownLatch(1);
return new TestMqttCallback(latch);
}
protected static class TestMqttCallback implements MqttCallback {
private final CountDownLatch latch;
private Integer qoS;
private byte[] payloadBytes;
TestMqttCallback(CountDownLatch latch) {
this.latch = latch;
}
public int getQoS() {
return qoS;
}
public byte[] getPayloadBytes() {
return payloadBytes;
}
public CountDownLatch getLatch() {
return latch;
}
@Override
public void connectionLost(Throwable throwable) {
}
@Override
public void messageArrived(String requestTopic, MqttMessage mqttMessage) throws Exception {
qoS = mqttMessage.getQos();
payloadBytes = mqttMessage.getPayload();
latch.countDown();
}
@Override
public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
}
} }
protected byte[] createTestsProvisionMessage(CredentialsType credentialsType, CredentialsDataProto credentialsData) throws Exception { protected byte[] createTestsProvisionMessage(CredentialsType credentialsType, CredentialsDataProto credentialsData) throws Exception {

396
application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java

@ -26,33 +26,34 @@ import com.squareup.wire.schema.internal.parser.ProtoFileElement;
import io.netty.handler.codec.mqtt.MqttQoS; import io.netty.handler.codec.mqtt.MqttQoS;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage; import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.junit.Assert;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.TransportPayloadType;
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.MqttDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.MqttTopics;
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;
import org.thingsboard.server.gen.transport.TransportApiProtos; import org.thingsboard.server.gen.transport.TransportApiProtos;
import org.thingsboard.server.transport.mqtt.AbstractMqttIntegrationTest; import org.thingsboard.server.transport.mqtt.AbstractMqttIntegrationTest;
import org.thingsboard.server.transport.mqtt.MqttTestCallback;
import org.thingsboard.server.transport.mqtt.MqttTestClient;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.BASE_DEVICE_API_TOPIC;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.BASE_DEVICE_API_TOPIC_V2;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_RPC_REQUESTS_SUB_TOPIC;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.GATEWAY_CONNECT_TOPIC;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.GATEWAY_RPC_TOPIC;
@Slf4j @Slf4j
public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractMqttIntegrationTest { public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractMqttIntegrationTest {
@ -76,81 +77,93 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractM
protected static final Long asyncContextTimeoutToUseRpcPlugin = 10000L; protected static final Long asyncContextTimeoutToUseRpcPlugin = 10000L;
protected void processOneWayRpcTest(String rpcSubTopic) throws Exception { protected void processOneWayRpcTest(String rpcSubTopic) throws Exception {
MqttAsyncClient client = getMqttAsyncClient(accessToken); MqttTestClient client = new MqttTestClient();
client.connectAndWait(accessToken);
CountDownLatch latch = new CountDownLatch(1); MqttTestCallback callback = new MqttTestCallback(rpcSubTopic.replace("+", "0"));
TestOneWayMqttCallback callback = new TestOneWayMqttCallback(client, latch);
client.setCallback(callback); client.setCallback(callback);
client.subscribeAndWait(rpcSubTopic, MqttQoS.AT_MOST_ONCE);
client.subscribe(rpcSubTopic, MqttQoS.AT_MOST_ONCE.value());
Thread.sleep(1000);
String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"23\",\"value\": 1}}"; String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"23\",\"value\": 1}}";
String deviceId = savedDevice.getId().getId().toString(); String result = doPostAsync("/api/rpc/oneway/" + savedDevice.getId(), setGpioRequest, String.class, status().isOk());
String result = doPostAsync("/api/rpc/oneway/" + deviceId, setGpioRequest, String.class, status().isOk()); assertTrue(StringUtils.isEmpty(result));
Assert.assertTrue(StringUtils.isEmpty(result)); callback.getSubscribeLatch().await(3, TimeUnit.SECONDS);
latch.await(3, TimeUnit.SECONDS); DeviceTransportType deviceTransportType = deviceProfile.getTransportType();
if (deviceTransportType.equals(DeviceTransportType.MQTT)) {
DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration();
assertTrue(transportConfiguration instanceof MqttDeviceProfileTransportConfiguration);
MqttDeviceProfileTransportConfiguration configuration = (MqttDeviceProfileTransportConfiguration) transportConfiguration;
TransportPayloadType transportPayloadType = configuration.getTransportPayloadTypeConfiguration().getTransportPayloadType();
if (transportPayloadType.equals(TransportPayloadType.PROTOBUF)) {
// TODO: add correct validation of proto requests to device
assertTrue(callback.getPayloadBytes().length > 0);
} else {
assertEquals(JacksonUtil.toJsonNode(setGpioRequest), JacksonUtil.fromBytes(callback.getPayloadBytes()));
}
} else {
assertEquals(JacksonUtil.toJsonNode(setGpioRequest), JacksonUtil.fromBytes(callback.getPayloadBytes()));
}
assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS()); assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS());
client.disconnect();
} }
protected void processJsonOneWayRpcTestGateway(String deviceName) throws Exception { protected void processJsonOneWayRpcTestGateway(String deviceName) throws Exception {
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken); MqttTestClient client = new MqttTestClient();
client.connectAndWait(gatewayAccessToken);
String payload = "{\"device\":\"" + deviceName + "\"}"; String payload = "{\"device\":\"" + deviceName + "\"}";
byte[] payloadBytes = payload.getBytes(); byte[] payloadBytes = payload.getBytes();
validateOneWayRpcGatewayResponse(deviceName, client, payloadBytes); validateOneWayRpcGatewayResponse(deviceName, client, payloadBytes);
client.disconnect();
} }
protected void processJsonTwoWayRpcTest(String rpcSubTopic) throws Exception { protected void processJsonTwoWayRpcTest(String rpcSubTopic) throws Exception {
MqttAsyncClient client = getMqttAsyncClient(accessToken); MqttTestClient client = new MqttTestClient();
client.subscribe(rpcSubTopic, 1); client.connectAndWait(accessToken);
client.subscribeAndWait(rpcSubTopic, MqttQoS.AT_LEAST_ONCE);
CountDownLatch latch = new CountDownLatch(1); MqttTestRpcJsonCallback callback = new MqttTestRpcJsonCallback(client, rpcSubTopic.replace("+", "0"));
TestJsonMqttCallback callback = new TestJsonMqttCallback(client, latch);
client.setCallback(callback); client.setCallback(callback);
Thread.sleep(1000);
String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"26\",\"value\": 1}}"; String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"26\",\"value\": 1}}";
String deviceId = savedDevice.getId().getId().toString(); String actualRpcResponse = doPostAsync("/api/rpc/twoway/" + savedDevice.getId(), setGpioRequest, String.class, status().isOk());
callback.getSubscribeLatch().await(3, TimeUnit.SECONDS);
String result = doPostAsync("/api/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk()); assertEquals(JacksonUtil.toJsonNode(setGpioRequest), JacksonUtil.fromBytes(callback.getPayloadBytes()));
String expected = "{\"value1\":\"A\",\"value2\":\"B\"}"; assertEquals("{\"value1\":\"A\",\"value2\":\"B\"}", actualRpcResponse);
latch.await(3, TimeUnit.SECONDS); client.disconnect();
Assert.assertEquals(expected, result);
} }
protected void processProtoTwoWayRpcTest(String rpcSubTopic) throws Exception { protected void processProtoTwoWayRpcTest(String rpcSubTopic) throws Exception {
MqttAsyncClient client = getMqttAsyncClient(accessToken); MqttTestClient client = new MqttTestClient();
client.subscribe(rpcSubTopic, 1); client.connectAndWait(accessToken);
client.subscribeAndWait(rpcSubTopic, MqttQoS.AT_LEAST_ONCE);
CountDownLatch latch = new CountDownLatch(1); MqttTestRpcProtoCallback callback = new MqttTestRpcProtoCallback(client, rpcSubTopic.replace("+", "0"));
TestProtoMqttCallback callback = new TestProtoMqttCallback(client, latch);
client.setCallback(callback); client.setCallback(callback);
Thread.sleep(1000);
String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"26\",\"value\": 1}}"; String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"26\",\"value\": 1}}";
String deviceId = savedDevice.getId().getId().toString(); String deviceId = savedDevice.getId().getId().toString();
String result = doPostAsync("/api/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk()); String actualRpcResponse = doPostAsync("/api/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk());
String expected = "{\"payload\":\"{\\\"value1\\\":\\\"A\\\",\\\"value2\\\":\\\"B\\\"}\"}"; callback.getSubscribeLatch().await(3, TimeUnit.SECONDS);
latch.await(3, TimeUnit.SECONDS); // TODO: add correct validation of proto requests to device
Assert.assertEquals(expected, result); assertTrue(callback.getPayloadBytes().length > 0);
assertEquals("{\"payload\":\"{\\\"value1\\\":\\\"A\\\",\\\"value2\\\":\\\"B\\\"}\"}", actualRpcResponse);
client.disconnect();
} }
protected void processProtoTwoWayRpcTestGateway(String deviceName) throws Exception { protected void processProtoTwoWayRpcTestGateway(String deviceName) throws Exception {
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken); MqttTestClient client = new MqttTestClient();
client.connectAndWait(gatewayAccessToken);
TransportApiProtos.ConnectMsg connectMsgProto = getConnectProto(deviceName); TransportApiProtos.ConnectMsg connectMsgProto = getConnectProto(deviceName);
byte[] payloadBytes = connectMsgProto.toByteArray(); byte[] payloadBytes = connectMsgProto.toByteArray();
validateProtoTwoWayRpcGatewayResponse(deviceName, client, payloadBytes); validateProtoTwoWayRpcGatewayResponse(deviceName, client, payloadBytes);
client.disconnect();
} }
protected void processProtoOneWayRpcTestGateway(String deviceName) throws Exception { protected void processProtoOneWayRpcTestGateway(String deviceName) throws Exception {
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken); MqttTestClient client = new MqttTestClient();
client.connectAndWait(gatewayAccessToken);
TransportApiProtos.ConnectMsg connectMsgProto = getConnectProto(deviceName); TransportApiProtos.ConnectMsg connectMsgProto = getConnectProto(deviceName);
byte[] payloadBytes = connectMsgProto.toByteArray(); byte[] payloadBytes = connectMsgProto.toByteArray();
validateOneWayRpcGatewayResponse(deviceName, client, payloadBytes); validateOneWayRpcGatewayResponse(deviceName, client, payloadBytes);
client.disconnect();
} }
private TransportApiProtos.ConnectMsg getConnectProto(String deviceName) { private TransportApiProtos.ConnectMsg getConnectProto(String deviceName) {
@ -175,29 +188,30 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractM
doPostAsync("/api/rpc/twoway/" + deviceId, JacksonUtil.toString(request), String.class, status().isOk()); doPostAsync("/api/rpc/twoway/" + deviceId, JacksonUtil.toString(request), String.class, status().isOk());
} }
MqttAsyncClient client = getMqttAsyncClient(accessToken); MqttTestClient client = new MqttTestClient();
client.setManualAcks(true); client.connectAndWait(accessToken);
CountDownLatch latch = new CountDownLatch(10); client.enableManualAcks();
TestSequenceMqttCallback callback = new TestSequenceMqttCallback(client, latch, result); MqttTestSequenceCallback callback = new MqttTestSequenceCallback(client, 10, result);
client.setCallback(callback); client.setCallback(callback);
client.subscribe(MqttTopics.DEVICE_RPC_REQUESTS_SUB_TOPIC, 1); client.subscribeAndWait(DEVICE_RPC_REQUESTS_SUB_TOPIC, MqttQoS.AT_LEAST_ONCE);
latch.await(10, TimeUnit.SECONDS); callback.getSubscribeLatch().await(10, TimeUnit.SECONDS);
Assert.assertEquals(expected, result); assertEquals(expected, result);
} }
protected void processJsonTwoWayRpcTestGateway(String deviceName) throws Exception { protected void processJsonTwoWayRpcTestGateway(String deviceName) throws Exception {
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken); MqttTestClient client = new MqttTestClient();
client.connectAndWait(gatewayAccessToken);
String payload = "{\"device\":\"" + deviceName + "\"}"; String payload = "{\"device\":\"" + deviceName + "\"}";
byte[] payloadBytes = payload.getBytes(); byte[] payloadBytes = payload.getBytes();
validateJsonTwoWayRpcGatewayResponse(deviceName, client, payloadBytes); validateJsonTwoWayRpcGatewayResponse(deviceName, client, payloadBytes);
client.disconnect();
} }
protected void validateOneWayRpcGatewayResponse(String deviceName, MqttAsyncClient client, byte[] payloadBytes) throws Exception { protected void validateOneWayRpcGatewayResponse(String deviceName, MqttTestClient client, byte[] connectPayloadBytes) throws Exception {
publishMqttMsg(client, payloadBytes, MqttTopics.GATEWAY_CONNECT_TOPIC); client.publish(GATEWAY_CONNECT_TOPIC, connectPayloadBytes);
Device savedDevice = doExecuteWithRetriesAndInterval( Device savedDevice = doExecuteWithRetriesAndInterval(
() -> getDeviceByName(deviceName), () -> getDeviceByName(deviceName),
20, 20,
@ -205,24 +219,45 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractM
); );
assertNotNull(savedDevice); assertNotNull(savedDevice);
CountDownLatch latch = new CountDownLatch(1); MqttTestCallback callback = new MqttTestCallback(GATEWAY_RPC_TOPIC);
TestOneWayMqttCallback callback = new TestOneWayMqttCallback(client, latch);
client.setCallback(callback); client.setCallback(callback);
client.subscribeAndWait(GATEWAY_RPC_TOPIC, MqttQoS.AT_MOST_ONCE);
client.subscribe(MqttTopics.GATEWAY_RPC_TOPIC, MqttQoS.AT_MOST_ONCE.value());
Thread.sleep(1000);
String setGpioRequest = "{\"method\": \"toggle_gpio\", \"params\": {\"pin\":1}}"; String setGpioRequest = "{\"method\": \"toggle_gpio\", \"params\": {\"pin\":1}}";
String deviceId = savedDevice.getId().getId().toString(); String deviceId = savedDevice.getId().getId().toString();
String result = doPostAsync("/api/rpc/oneway/" + deviceId, setGpioRequest, String.class, status().isOk()); String result = doPostAsync("/api/rpc/oneway/" + deviceId, setGpioRequest, String.class, status().isOk());
Assert.assertTrue(StringUtils.isEmpty(result)); assertTrue(StringUtils.isEmpty(result));
latch.await(3, TimeUnit.SECONDS); callback.getSubscribeLatch().await(3, TimeUnit.SECONDS);
DeviceTransportType deviceTransportType = deviceProfile.getTransportType();
if (deviceTransportType.equals(DeviceTransportType.MQTT)) {
DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration();
assertTrue(transportConfiguration instanceof MqttDeviceProfileTransportConfiguration);
MqttDeviceProfileTransportConfiguration configuration = (MqttDeviceProfileTransportConfiguration) transportConfiguration;
TransportPayloadType transportPayloadType = configuration.getTransportPayloadTypeConfiguration().getTransportPayloadType();
if (transportPayloadType.equals(TransportPayloadType.PROTOBUF)) {
// TODO: add correct validation of proto requests to device
assertTrue(callback.getPayloadBytes().length > 0);
} else {
JsonNode expectedJsonRequestData = getExpectedGatewayJsonRequestData(deviceName, setGpioRequest);
assertEquals(expectedJsonRequestData, JacksonUtil.fromBytes(callback.getPayloadBytes()));
}
} else {
JsonNode expectedJsonRequestData = getExpectedGatewayJsonRequestData(deviceName, setGpioRequest);
assertEquals(expectedJsonRequestData, JacksonUtil.fromBytes(callback.getPayloadBytes()));
}
assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS()); assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS());
} }
protected void validateJsonTwoWayRpcGatewayResponse(String deviceName, MqttAsyncClient client, byte[] payloadBytes) throws Exception { private JsonNode getExpectedGatewayJsonRequestData(String deviceName, String requestStr) {
publishMqttMsg(client, payloadBytes, MqttTopics.GATEWAY_CONNECT_TOPIC); ObjectNode deviceData = (ObjectNode) JacksonUtil.toJsonNode(requestStr);
deviceData.put("id", 0);
ObjectNode expectedRequest = JacksonUtil.newObjectNode();
expectedRequest.put("device", deviceName);
expectedRequest.set("data", deviceData);
return expectedRequest;
}
protected void validateJsonTwoWayRpcGatewayResponse(String deviceName, MqttTestClient client, byte[] connectPayloadBytes) throws Exception {
client.publish(GATEWAY_CONNECT_TOPIC, connectPayloadBytes);
Device savedDevice = doExecuteWithRetriesAndInterval( Device savedDevice = doExecuteWithRetriesAndInterval(
() -> getDeviceByName(deviceName), () -> getDeviceByName(deviceName),
@ -231,25 +266,21 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractM
); );
assertNotNull(savedDevice); assertNotNull(savedDevice);
CountDownLatch latch = new CountDownLatch(1); MqttTestRpcJsonCallback callback = new MqttTestRpcJsonCallback(client, GATEWAY_RPC_TOPIC);
TestJsonMqttCallback callback = new TestJsonMqttCallback(client, latch);
client.setCallback(callback); client.setCallback(callback);
client.subscribeAndWait(GATEWAY_RPC_TOPIC, MqttQoS.AT_MOST_ONCE);
client.subscribe(MqttTopics.GATEWAY_RPC_TOPIC, MqttQoS.AT_MOST_ONCE.value());
Thread.sleep(1000);
String setGpioRequest = "{\"method\": \"toggle_gpio\", \"params\": {\"pin\":1}}"; String setGpioRequest = "{\"method\": \"toggle_gpio\", \"params\": {\"pin\":1}}";
String deviceId = savedDevice.getId().getId().toString(); String deviceId = savedDevice.getId().getId().toString();
String result = doPostAsync("/api/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk()); String actualRpcResponse = doPostAsync("/api/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk());
latch.await(3, TimeUnit.SECONDS); callback.getSubscribeLatch().await(3, TimeUnit.SECONDS);
String expected = "{\"success\":true}"; log.warn("request payload: {}", JacksonUtil.fromBytes(callback.getPayloadBytes()));
assertEquals(expected, result); assertEquals("{\"success\":true}", actualRpcResponse);
assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS()); assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS());
} }
protected void validateProtoTwoWayRpcGatewayResponse(String deviceName, MqttAsyncClient client, byte[] payloadBytes) throws Exception { protected void validateProtoTwoWayRpcGatewayResponse(String deviceName, MqttTestClient client, byte[] connectPayloadBytes) throws Exception {
publishMqttMsg(client, payloadBytes, MqttTopics.GATEWAY_CONNECT_TOPIC); client.publish(GATEWAY_CONNECT_TOPIC, connectPayloadBytes);
Device savedDevice = doExecuteWithRetriesAndInterval( Device savedDevice = doExecuteWithRetriesAndInterval(
() -> getDeviceByName(deviceName), () -> getDeviceByName(deviceName),
@ -258,20 +289,15 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractM
); );
assertNotNull(savedDevice); assertNotNull(savedDevice);
CountDownLatch latch = new CountDownLatch(1); MqttTestRpcProtoCallback callback = new MqttTestRpcProtoCallback(client, GATEWAY_RPC_TOPIC);
TestProtoMqttCallback callback = new TestProtoMqttCallback(client, latch);
client.setCallback(callback); client.setCallback(callback);
client.subscribeAndWait(GATEWAY_RPC_TOPIC, MqttQoS.AT_MOST_ONCE);
client.subscribe(MqttTopics.GATEWAY_RPC_TOPIC, MqttQoS.AT_MOST_ONCE.value());
Thread.sleep(1000);
String setGpioRequest = "{\"method\": \"toggle_gpio\", \"params\": {\"pin\":1}}"; String setGpioRequest = "{\"method\": \"toggle_gpio\", \"params\": {\"pin\":1}}";
String deviceId = savedDevice.getId().getId().toString(); String deviceId = savedDevice.getId().getId().toString();
String result = doPostAsync("/api/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk()); String actualRpcResponse = doPostAsync("/api/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk());
latch.await(3, TimeUnit.SECONDS); callback.getSubscribeLatch().await(3, TimeUnit.SECONDS);
String expected = "{\"success\":true}"; assertEquals("{\"success\":true}", actualRpcResponse);
assertEquals(expected, result);
assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS()); assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS());
} }
@ -279,132 +305,82 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractM
return doGet("/api/tenant/devices?deviceName=" + deviceName, Device.class); return doGet("/api/tenant/devices?deviceName=" + deviceName, Device.class);
} }
protected MqttMessage processJsonMessageArrived(String requestTopic, MqttMessage mqttMessage) throws MqttException, InvalidProtocolBufferException { protected byte[] processJsonMessageArrived(String requestTopic, MqttMessage mqttMessage) {
MqttMessage message = new MqttMessage(); if (requestTopic.startsWith(BASE_DEVICE_API_TOPIC) || requestTopic.startsWith(BASE_DEVICE_API_TOPIC_V2)) {
if (requestTopic.startsWith(MqttTopics.BASE_DEVICE_API_TOPIC) || requestTopic.startsWith(MqttTopics.BASE_DEVICE_API_TOPIC_V2)) { return DEVICE_RESPONSE.getBytes(StandardCharset.UTF_8);
message.setPayload(DEVICE_RESPONSE.getBytes(StandardCharset.UTF_8));
} else { } else {
JsonNode requestMsgNode = JacksonUtil.toJsonNode(new String(mqttMessage.getPayload(), StandardCharset.UTF_8)); JsonNode requestMsgNode = JacksonUtil.toJsonNode(new String(mqttMessage.getPayload(), StandardCharset.UTF_8));
String deviceName = requestMsgNode.get("device").asText(); String deviceName = requestMsgNode.get("device").asText();
int requestId = requestMsgNode.get("data").get("id").asInt(); int requestId = requestMsgNode.get("data").get("id").asInt();
message.setPayload(("{\"device\": \"" + deviceName + "\", \"id\": " + requestId + ", \"data\": {\"success\": true}}").getBytes(StandardCharset.UTF_8)); String response = "{\"device\": \"" + deviceName + "\", \"id\": " + requestId + ", \"data\": {\"success\": true}}";
} return response.getBytes(StandardCharset.UTF_8);
return message;
}
protected class TestOneWayMqttCallback implements MqttCallback {
private final MqttAsyncClient client;
private final CountDownLatch latch;
private Integer qoS;
TestOneWayMqttCallback(MqttAsyncClient client, CountDownLatch latch) {
this.client = client;
this.latch = latch;
}
int getQoS() {
return qoS;
}
@Override
public void connectionLost(Throwable throwable) {
}
@Override
public void messageArrived(String requestTopic, MqttMessage mqttMessage) throws Exception {
log.info("Message Arrived: " + Arrays.toString(mqttMessage.getPayload()));
qoS = mqttMessage.getQos();
latch.countDown();
}
@Override
public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
} }
} }
protected class TestJsonMqttCallback implements MqttCallback { protected class MqttTestRpcJsonCallback extends MqttTestCallback {
private final MqttAsyncClient client; private final MqttTestClient client;
private final CountDownLatch latch;
private Integer qoS;
TestJsonMqttCallback(MqttAsyncClient client, CountDownLatch latch) { public MqttTestRpcJsonCallback(MqttTestClient client, String awaitSubTopic) {
super(awaitSubTopic);
this.client = client; this.client = client;
this.latch = latch;
}
int getQoS() {
return qoS;
}
@Override
public void connectionLost(Throwable throwable) {
} }
@Override @Override
public void messageArrived(String requestTopic, MqttMessage mqttMessage) throws Exception { protected void messageArrivedOnAwaitSubTopic(String requestTopic, MqttMessage mqttMessage) {
log.info("Message Arrived: " + Arrays.toString(mqttMessage.getPayload())); log.warn("messageArrived on topic: {}, awaitSubTopic: {}", requestTopic, awaitSubTopic);
String responseTopic; if (awaitSubTopic.equals(requestTopic)) {
if (requestTopic.startsWith(MqttTopics.BASE_DEVICE_API_TOPIC_V2)) { qoS = mqttMessage.getQos();
responseTopic = requestTopic.replace("req", "res"); payloadBytes = mqttMessage.getPayload();
} else { String responseTopic;
responseTopic = requestTopic.replace("request", "response"); if (requestTopic.startsWith(BASE_DEVICE_API_TOPIC_V2)) {
responseTopic = requestTopic.replace("req", "res");
} else {
responseTopic = requestTopic.replace("request", "response");
}
try {
client.publish(responseTopic, processJsonMessageArrived(requestTopic, mqttMessage));
} catch (MqttException e) {
log.warn("Failed to publish response on topic: {} due to: ", responseTopic, e);
}
subscribeLatch.countDown();
} }
qoS = mqttMessage.getQos();
client.publish(responseTopic, processJsonMessageArrived(requestTopic, mqttMessage));
latch.countDown();
}
@Override
public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
} }
} }
protected class TestProtoMqttCallback implements MqttCallback { protected class MqttTestRpcProtoCallback extends MqttTestCallback {
private final MqttAsyncClient client; private final MqttTestClient client;
private final CountDownLatch latch;
private Integer qoS;
TestProtoMqttCallback(MqttAsyncClient client, CountDownLatch latch) { public MqttTestRpcProtoCallback(MqttTestClient client, String awaitSubTopic) {
super(awaitSubTopic);
this.client = client; this.client = client;
this.latch = latch;
}
int getQoS() {
return qoS;
} }
@Override @Override
public void connectionLost(Throwable throwable) { protected void messageArrivedOnAwaitSubTopic(String requestTopic, MqttMessage mqttMessage) {
} log.warn("messageArrived on topic: {}, awaitSubTopic: {}", requestTopic, awaitSubTopic);
if (awaitSubTopic.equals(requestTopic)) {
@Override qoS = mqttMessage.getQos();
public void messageArrived(String requestTopic, MqttMessage mqttMessage) throws Exception { payloadBytes = mqttMessage.getPayload();
log.info("Message Arrived: " + Arrays.toString(mqttMessage.getPayload())); String responseTopic;
String responseTopic; if (requestTopic.startsWith(BASE_DEVICE_API_TOPIC_V2)) {
if (requestTopic.startsWith(MqttTopics.BASE_DEVICE_API_TOPIC_V2)) { responseTopic = requestTopic.replace("req", "res");
responseTopic = requestTopic.replace("req", "res"); } else {
} else { responseTopic = requestTopic.replace("request", "response");
responseTopic = requestTopic.replace("request", "response"); }
try {
client.publish(responseTopic, processProtoMessageArrived(requestTopic, mqttMessage));
} catch (Exception e) {
log.warn("Failed to publish response on topic: {} due to: ", responseTopic, e);
}
subscribeLatch.countDown();
} }
qoS = mqttMessage.getQos();
client.publish(responseTopic, processProtoMessageArrived(requestTopic, mqttMessage));
latch.countDown();
}
@Override
public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
} }
} }
protected MqttMessage processProtoMessageArrived(String requestTopic, MqttMessage mqttMessage) throws MqttException, InvalidProtocolBufferException { protected byte[] processProtoMessageArrived(String requestTopic, MqttMessage mqttMessage) throws MqttException, InvalidProtocolBufferException {
MqttMessage message = new MqttMessage(); if (requestTopic.startsWith(BASE_DEVICE_API_TOPIC) || requestTopic.startsWith(BASE_DEVICE_API_TOPIC_V2)) {
if (requestTopic.startsWith(MqttTopics.BASE_DEVICE_API_TOPIC) || requestTopic.startsWith(MqttTopics.BASE_DEVICE_API_TOPIC_V2)) {
ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = getProtoTransportPayloadConfiguration(); ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = getProtoTransportPayloadConfiguration();
ProtoFileElement rpcRequestProtoSchemaFile = protoTransportPayloadConfiguration.getTransportProtoSchema(RPC_REQUEST_PROTO_SCHEMA); ProtoFileElement rpcRequestProtoSchemaFile = protoTransportPayloadConfiguration.getTransportProtoSchema(RPC_REQUEST_PROTO_SCHEMA);
DynamicSchema rpcRequestProtoSchema = protoTransportPayloadConfiguration.getDynamicSchema(rpcRequestProtoSchemaFile, ProtoTransportPayloadConfiguration.RPC_REQUEST_PROTO_SCHEMA); DynamicSchema rpcRequestProtoSchema = protoTransportPayloadConfiguration.getDynamicSchema(rpcRequestProtoSchemaFile, ProtoTransportPayloadConfiguration.RPC_REQUEST_PROTO_SCHEMA);
@ -428,9 +404,9 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractM
DynamicMessage rpcResponseMsg = rpcResponseBuilder DynamicMessage rpcResponseMsg = rpcResponseBuilder
.setField(rpcResponseMsgDescriptor.findFieldByName("payload"), DEVICE_RESPONSE) .setField(rpcResponseMsgDescriptor.findFieldByName("payload"), DEVICE_RESPONSE)
.build(); .build();
message.setPayload(rpcResponseMsg.toByteArray()); return rpcResponseMsg.toByteArray();
} catch (InvalidProtocolBufferException e) { } catch (InvalidProtocolBufferException e) {
log.warn("Command Response Ack Error, Invalid response received: ", e); throw new RuntimeException("Command Response Ack Error, Invalid response received: ", e);
} }
} else { } else {
TransportApiProtos.GatewayDeviceRpcRequestMsg msg = TransportApiProtos.GatewayDeviceRpcRequestMsg.parseFrom(mqttMessage.getPayload()); TransportApiProtos.GatewayDeviceRpcRequestMsg msg = TransportApiProtos.GatewayDeviceRpcRequestMsg.parseFrom(mqttMessage.getPayload());
@ -441,9 +417,8 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractM
.setId(requestId) .setId(requestId)
.setData("{\"success\": true}") .setData("{\"success\": true}")
.build(); .build();
message.setPayload(gatewayRpcResponseMsg.toByteArray()); return gatewayRpcResponseMsg.toByteArray();
} }
return message;
} }
private ProtoTransportPayloadConfiguration getProtoTransportPayloadConfiguration() { private ProtoTransportPayloadConfiguration getProtoTransportPayloadConfiguration() {
@ -455,37 +430,30 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractM
return (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; return (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration;
} }
protected class TestSequenceMqttCallback implements MqttCallback { protected class MqttTestSequenceCallback extends MqttTestCallback {
private final MqttAsyncClient client; private final MqttTestClient client;
private final CountDownLatch latch;
private final List<String> expected; private final List<String> expected;
TestSequenceMqttCallback(MqttAsyncClient client, CountDownLatch latch, List<String> expected) { MqttTestSequenceCallback(MqttTestClient client, int subscribeCount, List<String> expected) {
super(subscribeCount);
this.client = client; this.client = client;
this.latch = latch;
this.expected = expected; this.expected = expected;
} }
@Override @Override
public void connectionLost(Throwable throwable) { public void messageArrived(String requestTopic, MqttMessage mqttMessage) {
} log.warn("messageArrived on topic: {}, awaitSubTopic: {}", requestTopic, awaitSubTopic);
@Override
public void messageArrived(String requestTopic, MqttMessage mqttMessage) throws Exception {
log.info("Message Arrived: " + Arrays.toString(mqttMessage.getPayload()));
expected.add(new String(mqttMessage.getPayload())); expected.add(new String(mqttMessage.getPayload()));
String responseTopic = requestTopic.replace("request", "response"); String responseTopic = requestTopic.replace("request", "response");
var qoS = mqttMessage.getQos(); qoS = mqttMessage.getQos();
try {
client.messageArrivedComplete(mqttMessage.getId(), qoS); client.messageArrivedComplete(mqttMessage);
client.publish(responseTopic, processJsonMessageArrived(requestTopic, mqttMessage)); client.publish(responseTopic, processJsonMessageArrived(requestTopic, mqttMessage));
latch.countDown(); } catch (MqttException e) {
} log.warn("Failed to publish response on topic: {} due to: ", responseTopic, e);
}
@Override subscribeLatch.countDown();
public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
} }
} }
} }

23
application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/MqttServerSideRpcBackwardCompatibilityIntegrationTest.java

@ -16,13 +16,16 @@
package org.thingsboard.server.transport.mqtt.rpc; package org.thingsboard.server.transport.mqtt.rpc;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.junit.After;
import org.junit.Test; import org.junit.Test;
import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.TransportPayloadType;
import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties; import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_JSON_TOPIC;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_PROTO_TOPIC;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_TOPIC;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_RPC_REQUESTS_SUB_TOPIC;
@Slf4j @Slf4j
@DaoSqlTest @DaoSqlTest
public class MqttServerSideRpcBackwardCompatibilityIntegrationTest extends AbstractMqttServerSideRpcIntegrationTest { public class MqttServerSideRpcBackwardCompatibilityIntegrationTest extends AbstractMqttServerSideRpcIntegrationTest {
@ -37,7 +40,7 @@ public class MqttServerSideRpcBackwardCompatibilityIntegrationTest extends Abstr
.useJsonPayloadFormatForDefaultDownlinkTopics(true) .useJsonPayloadFormatForDefaultDownlinkTopics(true)
.build(); .build();
processBeforeTest(configProperties); processBeforeTest(configProperties);
processOneWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_TOPIC); processOneWayRpcTest(DEVICE_RPC_REQUESTS_SUB_TOPIC);
} }
@Test @Test
@ -50,7 +53,7 @@ public class MqttServerSideRpcBackwardCompatibilityIntegrationTest extends Abstr
.useJsonPayloadFormatForDefaultDownlinkTopics(true) .useJsonPayloadFormatForDefaultDownlinkTopics(true)
.build(); .build();
super.processBeforeTest(configProperties); super.processBeforeTest(configProperties);
processOneWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_TOPIC); processOneWayRpcTest(DEVICE_RPC_REQUESTS_SUB_SHORT_TOPIC);
} }
@Test @Test
@ -63,7 +66,7 @@ public class MqttServerSideRpcBackwardCompatibilityIntegrationTest extends Abstr
.useJsonPayloadFormatForDefaultDownlinkTopics(true) .useJsonPayloadFormatForDefaultDownlinkTopics(true)
.build(); .build();
super.processBeforeTest(configProperties); super.processBeforeTest(configProperties);
processOneWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_PROTO_TOPIC); processOneWayRpcTest(DEVICE_RPC_REQUESTS_SUB_SHORT_PROTO_TOPIC);
} }
@Test @Test
@ -75,7 +78,7 @@ public class MqttServerSideRpcBackwardCompatibilityIntegrationTest extends Abstr
.enableCompatibilityWithJsonPayloadFormat(true) .enableCompatibilityWithJsonPayloadFormat(true)
.build(); .build();
super.processBeforeTest(configProperties); super.processBeforeTest(configProperties);
processProtoTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_TOPIC); processProtoTwoWayRpcTest(DEVICE_RPC_REQUESTS_SUB_TOPIC);
} }
@Test @Test
@ -88,7 +91,7 @@ public class MqttServerSideRpcBackwardCompatibilityIntegrationTest extends Abstr
.useJsonPayloadFormatForDefaultDownlinkTopics(true) .useJsonPayloadFormatForDefaultDownlinkTopics(true)
.build(); .build();
super.processBeforeTest(configProperties); super.processBeforeTest(configProperties);
processJsonTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_TOPIC); processJsonTwoWayRpcTest(DEVICE_RPC_REQUESTS_SUB_TOPIC);
} }
@Test @Test
@ -99,7 +102,7 @@ public class MqttServerSideRpcBackwardCompatibilityIntegrationTest extends Abstr
.rpcRequestProtoSchema(RPC_REQUEST_PROTO_SCHEMA) .rpcRequestProtoSchema(RPC_REQUEST_PROTO_SCHEMA)
.build(); .build();
super.processBeforeTest(configProperties); super.processBeforeTest(configProperties);
processProtoTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_TOPIC); processProtoTwoWayRpcTest(DEVICE_RPC_REQUESTS_SUB_SHORT_TOPIC);
} }
@Test @Test
@ -112,7 +115,7 @@ public class MqttServerSideRpcBackwardCompatibilityIntegrationTest extends Abstr
.useJsonPayloadFormatForDefaultDownlinkTopics(true) .useJsonPayloadFormatForDefaultDownlinkTopics(true)
.build(); .build();
super.processBeforeTest(configProperties); super.processBeforeTest(configProperties);
processProtoTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_PROTO_TOPIC); processProtoTwoWayRpcTest(DEVICE_RPC_REQUESTS_SUB_SHORT_PROTO_TOPIC);
} }
@Test @Test
@ -125,7 +128,7 @@ public class MqttServerSideRpcBackwardCompatibilityIntegrationTest extends Abstr
.useJsonPayloadFormatForDefaultDownlinkTopics(true) .useJsonPayloadFormatForDefaultDownlinkTopics(true)
.build(); .build();
super.processBeforeTest(configProperties); super.processBeforeTest(configProperties);
processJsonTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_JSON_TOPIC); processJsonTwoWayRpcTest(DEVICE_RPC_REQUESTS_SUB_SHORT_JSON_TOPIC);
} }
@Test @Test

16
application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/MqttServerSideRpcDefaultIntegrationTest.java

@ -20,12 +20,14 @@ import lombok.extern.slf4j.Slf4j;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.service.security.AccessValidator; import org.thingsboard.server.service.security.AccessValidator;
import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties; import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_JSON_TOPIC;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_TOPIC;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_RPC_REQUESTS_SUB_TOPIC;
@Slf4j @Slf4j
@DaoSqlTest @DaoSqlTest
@ -80,32 +82,32 @@ public class MqttServerSideRpcDefaultIntegrationTest extends AbstractMqttServerS
@Test @Test
public void testServerMqttOneWayRpc() throws Exception { public void testServerMqttOneWayRpc() throws Exception {
processOneWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_TOPIC); processOneWayRpcTest(DEVICE_RPC_REQUESTS_SUB_TOPIC);
} }
@Test @Test
public void testServerMqttOneWayRpcOnShortTopic() throws Exception { public void testServerMqttOneWayRpcOnShortTopic() throws Exception {
processOneWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_TOPIC); processOneWayRpcTest(DEVICE_RPC_REQUESTS_SUB_SHORT_TOPIC);
} }
@Test @Test
public void testServerMqttOneWayRpcOnShortJsonTopic() throws Exception { public void testServerMqttOneWayRpcOnShortJsonTopic() throws Exception {
processOneWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_JSON_TOPIC); processOneWayRpcTest(DEVICE_RPC_REQUESTS_SUB_SHORT_JSON_TOPIC);
} }
@Test @Test
public void testServerMqttTwoWayRpc() throws Exception { public void testServerMqttTwoWayRpc() throws Exception {
processJsonTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_TOPIC); processJsonTwoWayRpcTest(DEVICE_RPC_REQUESTS_SUB_TOPIC);
} }
@Test @Test
public void testServerMqttTwoWayRpcOnShortTopic() throws Exception { public void testServerMqttTwoWayRpcOnShortTopic() throws Exception {
processJsonTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_TOPIC); processJsonTwoWayRpcTest(DEVICE_RPC_REQUESTS_SUB_SHORT_TOPIC);
} }
@Test @Test
public void testServerMqttTwoWayRpcOnShortJsonTopic() throws Exception { public void testServerMqttTwoWayRpcOnShortJsonTopic() throws Exception {
processJsonTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_JSON_TOPIC); processJsonTwoWayRpcTest(DEVICE_RPC_REQUESTS_SUB_SHORT_JSON_TOPIC);
} }
@Test @Test

23
application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/MqttServerSideRpcJsonIntegrationTest.java

@ -16,14 +16,17 @@
package org.thingsboard.server.transport.mqtt.rpc; package org.thingsboard.server.transport.mqtt.rpc;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.TransportPayloadType;
import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.transport.mqtt.MqttTestClient;
import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties; import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_JSON_TOPIC;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_TOPIC;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_RPC_REQUESTS_SUB_TOPIC;
@Slf4j @Slf4j
@DaoSqlTest @DaoSqlTest
public class MqttServerSideRpcJsonIntegrationTest extends AbstractMqttServerSideRpcIntegrationTest { public class MqttServerSideRpcJsonIntegrationTest extends AbstractMqttServerSideRpcIntegrationTest {
@ -40,32 +43,32 @@ public class MqttServerSideRpcJsonIntegrationTest extends AbstractMqttServerSide
@Test @Test
public void testServerMqttOneWayRpc() throws Exception { public void testServerMqttOneWayRpc() throws Exception {
processOneWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_TOPIC); processOneWayRpcTest(DEVICE_RPC_REQUESTS_SUB_TOPIC);
} }
@Test @Test
public void testServerMqttOneWayRpcOnShortTopic() throws Exception { public void testServerMqttOneWayRpcOnShortTopic() throws Exception {
processOneWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_TOPIC); processOneWayRpcTest(DEVICE_RPC_REQUESTS_SUB_SHORT_TOPIC);
} }
@Test @Test
public void testServerMqttOneWayRpcOnShortJsonTopic() throws Exception { public void testServerMqttOneWayRpcOnShortJsonTopic() throws Exception {
processOneWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_JSON_TOPIC); processOneWayRpcTest(DEVICE_RPC_REQUESTS_SUB_SHORT_JSON_TOPIC);
} }
@Test @Test
public void testServerMqttTwoWayRpc() throws Exception { public void testServerMqttTwoWayRpc() throws Exception {
processJsonTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_TOPIC); processJsonTwoWayRpcTest(DEVICE_RPC_REQUESTS_SUB_TOPIC);
} }
@Test @Test
public void testServerMqttTwoWayRpcOnShortTopic() throws Exception { public void testServerMqttTwoWayRpcOnShortTopic() throws Exception {
processJsonTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_TOPIC); processJsonTwoWayRpcTest(DEVICE_RPC_REQUESTS_SUB_SHORT_TOPIC);
} }
@Test @Test
public void testServerMqttTwoWayRpcOnShortJsonTopic() throws Exception { public void testServerMqttTwoWayRpcOnShortJsonTopic() throws Exception {
processJsonTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_JSON_TOPIC); processJsonTwoWayRpcTest(DEVICE_RPC_REQUESTS_SUB_SHORT_JSON_TOPIC);
} }
@Test @Test
@ -79,10 +82,12 @@ public class MqttServerSideRpcJsonIntegrationTest extends AbstractMqttServerSide
} }
protected void processJsonOneWayRpcTestGateway(String deviceName) throws Exception { protected void processJsonOneWayRpcTestGateway(String deviceName) throws Exception {
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken); MqttTestClient client = new MqttTestClient();
client.connectAndWait(gatewayAccessToken);
String payload = "{\"device\": \"" + deviceName + "\", \"type\": \"" + TransportPayloadType.JSON.name() + "\"}"; String payload = "{\"device\": \"" + deviceName + "\", \"type\": \"" + TransportPayloadType.JSON.name() + "\"}";
byte[] payloadBytes = payload.getBytes(); byte[] payloadBytes = payload.getBytes();
validateOneWayRpcGatewayResponse(deviceName, client, payloadBytes); validateOneWayRpcGatewayResponse(deviceName, client, payloadBytes);
client.disconnect();
} }
} }

17
application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/MqttServerSideRpcProtoIntegrationTest.java

@ -19,10 +19,13 @@ import lombok.extern.slf4j.Slf4j;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.TransportPayloadType;
import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties; import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_PROTO_TOPIC;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_TOPIC;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_RPC_REQUESTS_SUB_TOPIC;
@Slf4j @Slf4j
@DaoSqlTest @DaoSqlTest
public class MqttServerSideRpcProtoIntegrationTest extends AbstractMqttServerSideRpcIntegrationTest { public class MqttServerSideRpcProtoIntegrationTest extends AbstractMqttServerSideRpcIntegrationTest {
@ -40,32 +43,32 @@ public class MqttServerSideRpcProtoIntegrationTest extends AbstractMqttServerSid
@Test @Test
public void testServerMqttOneWayRpc() throws Exception { public void testServerMqttOneWayRpc() throws Exception {
processOneWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_TOPIC); processOneWayRpcTest(DEVICE_RPC_REQUESTS_SUB_TOPIC);
} }
@Test @Test
public void testServerMqttOneWayRpcOnShortTopic() throws Exception { public void testServerMqttOneWayRpcOnShortTopic() throws Exception {
processOneWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_TOPIC); processOneWayRpcTest(DEVICE_RPC_REQUESTS_SUB_SHORT_TOPIC);
} }
@Test @Test
public void testServerMqttOneWayRpcOnShortProtoTopic() throws Exception { public void testServerMqttOneWayRpcOnShortProtoTopic() throws Exception {
processOneWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_PROTO_TOPIC); processOneWayRpcTest(DEVICE_RPC_REQUESTS_SUB_SHORT_PROTO_TOPIC);
} }
@Test @Test
public void testServerMqttTwoWayRpc() throws Exception { public void testServerMqttTwoWayRpc() throws Exception {
processProtoTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_TOPIC); processProtoTwoWayRpcTest(DEVICE_RPC_REQUESTS_SUB_TOPIC);
} }
@Test @Test
public void testServerMqttTwoWayRpcOnShortTopic() throws Exception { public void testServerMqttTwoWayRpcOnShortTopic() throws Exception {
processProtoTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_TOPIC); processProtoTwoWayRpcTest(DEVICE_RPC_REQUESTS_SUB_SHORT_TOPIC);
} }
@Test @Test
public void testServerMqttTwoWayRpcOnShortProtoTopic() throws Exception { public void testServerMqttTwoWayRpcOnShortProtoTopic() throws Exception {
processProtoTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_PROTO_TOPIC); processProtoTwoWayRpcTest(DEVICE_RPC_REQUESTS_SUB_SHORT_PROTO_TOPIC);
} }
@Test @Test

48
application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/MqttAttributesIntegrationTest.java

@ -18,14 +18,13 @@ package org.thingsboard.server.transport.mqtt.telemetry.attributes;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
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.Device;
import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.transport.mqtt.AbstractMqttIntegrationTest; import org.thingsboard.server.transport.mqtt.AbstractMqttIntegrationTest;
import org.thingsboard.server.transport.mqtt.MqttTestClient;
import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties; import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties;
import java.util.Arrays; import java.util.Arrays;
@ -38,6 +37,10 @@ import java.util.Set;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_ATTRIBUTES_SHORT_JSON_TOPIC;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_ATTRIBUTES_SHORT_TOPIC;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_ATTRIBUTES_TOPIC;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.GATEWAY_ATTRIBUTES_TOPIC;
@Slf4j @Slf4j
@DaoSqlTest @DaoSqlTest
@ -58,19 +61,19 @@ public class MqttAttributesIntegrationTest extends AbstractMqttIntegrationTest {
@Test @Test
public void testPushAttributes() throws Exception { public void testPushAttributes() throws Exception {
List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5"); List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5");
processJsonPayloadAttributesTest(MqttTopics.DEVICE_ATTRIBUTES_TOPIC, expectedKeys, PAYLOAD_VALUES_STR.getBytes()); processJsonPayloadAttributesTest(DEVICE_ATTRIBUTES_TOPIC, expectedKeys, PAYLOAD_VALUES_STR.getBytes());
} }
@Test @Test
public void testPushAttributesOnShortTopic() throws Exception { public void testPushAttributesOnShortTopic() throws Exception {
List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5"); List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5");
processJsonPayloadAttributesTest(MqttTopics.DEVICE_ATTRIBUTES_SHORT_TOPIC, expectedKeys, PAYLOAD_VALUES_STR.getBytes()); processJsonPayloadAttributesTest(DEVICE_ATTRIBUTES_SHORT_TOPIC, expectedKeys, PAYLOAD_VALUES_STR.getBytes());
} }
@Test @Test
public void testPushAttributesOnShortJsonTopic() throws Exception { public void testPushAttributesOnShortJsonTopic() throws Exception {
List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5"); List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5");
processJsonPayloadAttributesTest(MqttTopics.DEVICE_ATTRIBUTES_SHORT_JSON_TOPIC, expectedKeys, PAYLOAD_VALUES_STR.getBytes()); processJsonPayloadAttributesTest(DEVICE_ATTRIBUTES_SHORT_JSON_TOPIC, expectedKeys, PAYLOAD_VALUES_STR.getBytes());
} }
@Test @Test
@ -87,9 +90,11 @@ public class MqttAttributesIntegrationTest extends AbstractMqttIntegrationTest {
} }
protected void processAttributesTest(String topic, List<String> expectedKeys, byte[] payload, boolean presenceFieldsTest) throws Exception { protected void processAttributesTest(String topic, List<String> expectedKeys, byte[] payload, boolean presenceFieldsTest) throws Exception {
MqttAsyncClient client = getMqttAsyncClient(accessToken); MqttTestClient client = new MqttTestClient();
client.connectAndWait(accessToken);
publishMqttMsg(client, payload, topic); client.publishAndWait(topic, payload);
client.disconnect();
DeviceId deviceId = savedDevice.getId(); DeviceId deviceId = savedDevice.getId();
@ -125,9 +130,11 @@ public class MqttAttributesIntegrationTest extends AbstractMqttIntegrationTest {
} }
protected void processGatewayAttributesTest(List<String> expectedKeys, byte[] payload, String firstDeviceName, String secondDeviceName) throws Exception { protected void processGatewayAttributesTest(List<String> expectedKeys, byte[] payload, String firstDeviceName, String secondDeviceName) throws Exception {
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken); MqttTestClient client = new MqttTestClient();
client.connectAndWait(gatewayAccessToken);
publishMqttMsg(client, payload, MqttTopics.GATEWAY_ATTRIBUTES_TOPIC); client.publishAndWait(GATEWAY_ATTRIBUTES_TOPIC, payload);
client.disconnect();
Device firstDevice = doExecuteWithRetriesAndInterval(() -> doGet("/api/tenant/devices?deviceName=" + firstDeviceName, Device.class), Device firstDevice = doExecuteWithRetriesAndInterval(() -> doGet("/api/tenant/devices?deviceName=" + firstDeviceName, Device.class),
20, 20,
@ -141,12 +148,12 @@ public class MqttAttributesIntegrationTest extends AbstractMqttIntegrationTest {
assertNotNull(secondDevice); assertNotNull(secondDevice);
Thread.sleep(2000); List<String> firstDeviceActualKeys = getActualKeysList(firstDevice.getId(), expectedKeys);
assertNotNull(firstDeviceActualKeys);
List<String> firstDeviceActualKeys = doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + firstDevice.getId() + "/keys/attributes/CLIENT_SCOPE", new TypeReference<>() {});
Set<String> firstDeviceActualKeySet = new HashSet<>(firstDeviceActualKeys); Set<String> firstDeviceActualKeySet = new HashSet<>(firstDeviceActualKeys);
List<String> secondDeviceActualKeys = doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + secondDevice.getId() + "/keys/attributes/CLIENT_SCOPE", new TypeReference<>() {}); List<String> secondDeviceActualKeys = getActualKeysList(secondDevice.getId(), expectedKeys);
assertNotNull(secondDeviceActualKeys);
Set<String> secondDeviceActualKeySet = new HashSet<>(secondDeviceActualKeys); Set<String> secondDeviceActualKeySet = new HashSet<>(secondDeviceActualKeys);
Set<String> expectedKeySet = new HashSet<>(expectedKeys); Set<String> expectedKeySet = new HashSet<>(expectedKeys);
@ -165,6 +172,21 @@ public class MqttAttributesIntegrationTest extends AbstractMqttIntegrationTest {
} }
private List<String> getActualKeysList(DeviceId deviceId, List<String> expectedKeys) throws Exception {
long start = System.currentTimeMillis();
long end = System.currentTimeMillis() + 3000;
List<String> firstDeviceActualKeys = null;
while (start <= end) {
firstDeviceActualKeys = doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + deviceId + "/keys/attributes/CLIENT_SCOPE", new TypeReference<>() {});
if (firstDeviceActualKeys.size() == expectedKeys.size()) {
break;
}
Thread.sleep(100);
start += 100;
}
return firstDeviceActualKeys;
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
protected void assertAttributesValues(List<Map<String, Object>> deviceValues, Set<String> keySet) throws JsonProcessingException { protected void assertAttributesValues(List<Map<String, Object>> deviceValues, Set<String> keySet) throws JsonProcessingException {
for (Map<String, Object> map : deviceValues) { for (Map<String, Object> map : deviceValues) {

10
application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/MqttAttributesProtoIntegrationTest.java

@ -25,7 +25,6 @@ import org.junit.Test;
import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.TransportPayloadType;
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.MqttDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.MqttTopics;
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;
import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.dao.service.DaoSqlTest;
@ -38,6 +37,9 @@ import java.util.List;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_ATTRIBUTES_SHORT_JSON_TOPIC;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_ATTRIBUTES_SHORT_PROTO_TOPIC;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_ATTRIBUTES_SHORT_TOPIC;
@Slf4j @Slf4j
@DaoSqlTest @DaoSqlTest
@ -122,7 +124,7 @@ public class MqttAttributesProtoIntegrationTest extends MqttAttributesIntegratio
.build(); .build();
processBeforeTest(configProperties); processBeforeTest(configProperties);
DynamicMessage postAttributesMsg = getDefaultDynamicMessage(); DynamicMessage postAttributesMsg = getDefaultDynamicMessage();
processAttributesTest(MqttTopics.DEVICE_ATTRIBUTES_SHORT_TOPIC, Arrays.asList("key1", "key2", "key3", "key4", "key5"), postAttributesMsg.toByteArray(), false); processAttributesTest(DEVICE_ATTRIBUTES_SHORT_TOPIC, Arrays.asList("key1", "key2", "key3", "key4", "key5"), postAttributesMsg.toByteArray(), false);
} }
@Test @Test
@ -133,7 +135,7 @@ public class MqttAttributesProtoIntegrationTest extends MqttAttributesIntegratio
.attributesTopicFilter(POST_DATA_ATTRIBUTES_TOPIC) .attributesTopicFilter(POST_DATA_ATTRIBUTES_TOPIC)
.build(); .build();
processBeforeTest(configProperties); processBeforeTest(configProperties);
processJsonPayloadAttributesTest(MqttTopics.DEVICE_ATTRIBUTES_SHORT_JSON_TOPIC, Arrays.asList("key1", "key2", "key3", "key4", "key5"), PAYLOAD_VALUES_STR.getBytes()); processJsonPayloadAttributesTest(DEVICE_ATTRIBUTES_SHORT_JSON_TOPIC, Arrays.asList("key1", "key2", "key3", "key4", "key5"), PAYLOAD_VALUES_STR.getBytes());
} }
@Test @Test
@ -145,7 +147,7 @@ public class MqttAttributesProtoIntegrationTest extends MqttAttributesIntegratio
.build(); .build();
processBeforeTest(configProperties); processBeforeTest(configProperties);
DynamicMessage postAttributesMsg = getDefaultDynamicMessage(); DynamicMessage postAttributesMsg = getDefaultDynamicMessage();
processAttributesTest(MqttTopics.DEVICE_ATTRIBUTES_SHORT_PROTO_TOPIC, Arrays.asList("key1", "key2", "key3", "key4", "key5"), postAttributesMsg.toByteArray(), false); processAttributesTest(DEVICE_ATTRIBUTES_SHORT_PROTO_TOPIC, Arrays.asList("key1", "key2", "key3", "key4", "key5"), postAttributesMsg.toByteArray(), false);
} }
@Test @Test

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

@ -18,19 +18,13 @@ package org.thingsboard.server.transport.mqtt.telemetry.timeseries;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
import io.netty.handler.codec.mqtt.MqttQoS; import io.netty.handler.codec.mqtt.MqttQoS;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
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.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.Device;
import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.transport.mqtt.AbstractMqttIntegrationTest; import org.thingsboard.server.transport.mqtt.AbstractMqttIntegrationTest;
import org.thingsboard.server.transport.mqtt.MqttTestCallback;
import org.thingsboard.server.transport.mqtt.MqttTestClient;
import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties; import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties;
import java.util.Arrays; import java.util.Arrays;
@ -38,12 +32,17 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_ATTRIBUTES_TOPIC;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_TELEMETRY_SHORT_JSON_TOPIC;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_TELEMETRY_SHORT_TOPIC;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_TELEMETRY_TOPIC;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.GATEWAY_CONNECT_TOPIC;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.GATEWAY_TELEMETRY_TOPIC;
@Slf4j @Slf4j
public abstract class AbstractMqttTimeseriesIntegrationTest extends AbstractMqttIntegrationTest { public abstract class AbstractMqttTimeseriesIntegrationTest extends AbstractMqttIntegrationTest {
@ -66,26 +65,26 @@ public abstract class AbstractMqttTimeseriesIntegrationTest extends AbstractMqtt
@Test @Test
public void testPushTelemetry() throws Exception { public void testPushTelemetry() throws Exception {
List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5"); List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5");
processJsonPayloadTelemetryTest(MqttTopics.DEVICE_TELEMETRY_TOPIC, expectedKeys, PAYLOAD_VALUES_STR.getBytes(), false); processJsonPayloadTelemetryTest(DEVICE_TELEMETRY_TOPIC, expectedKeys, PAYLOAD_VALUES_STR.getBytes(), false);
} }
@Test @Test
public void testPushTelemetryWithTs() throws Exception { public void testPushTelemetryWithTs() throws Exception {
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(MqttTopics.DEVICE_TELEMETRY_TOPIC, expectedKeys, payloadStr.getBytes(), true); processJsonPayloadTelemetryTest(DEVICE_TELEMETRY_TOPIC, expectedKeys, payloadStr.getBytes(), true);
} }
@Test @Test
public void testPushTelemetryOnShortTopic() throws Exception { public void testPushTelemetryOnShortTopic() throws Exception {
List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5"); List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5");
processJsonPayloadTelemetryTest(MqttTopics.DEVICE_TELEMETRY_SHORT_TOPIC, expectedKeys, PAYLOAD_VALUES_STR.getBytes(), false); processJsonPayloadTelemetryTest(DEVICE_TELEMETRY_SHORT_TOPIC, expectedKeys, PAYLOAD_VALUES_STR.getBytes(), false);
} }
@Test @Test
public void testPushTelemetryOnShortJsonTopic() throws Exception { public void testPushTelemetryOnShortJsonTopic() throws Exception {
List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5"); List<String> expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5");
processJsonPayloadTelemetryTest(MqttTopics.DEVICE_TELEMETRY_SHORT_JSON_TOPIC, expectedKeys, PAYLOAD_VALUES_STR.getBytes(), false); processJsonPayloadTelemetryTest(DEVICE_TELEMETRY_SHORT_JSON_TOPIC, expectedKeys, PAYLOAD_VALUES_STR.getBytes(), false);
} }
@Test @Test
@ -94,14 +93,15 @@ public abstract class AbstractMqttTimeseriesIntegrationTest extends AbstractMqtt
String deviceName1 = "Device A"; String deviceName1 = "Device A";
String deviceName2 = "Device B"; String deviceName2 = "Device B";
String payload = getGatewayTelemetryJsonPayload(deviceName1, deviceName2, "10000", "20000"); String payload = getGatewayTelemetryJsonPayload(deviceName1, deviceName2, "10000", "20000");
processGatewayTelemetryTest(MqttTopics.GATEWAY_TELEMETRY_TOPIC, expectedKeys, payload.getBytes(), deviceName1, deviceName2); processGatewayTelemetryTest(GATEWAY_TELEMETRY_TOPIC, expectedKeys, payload.getBytes(), deviceName1, deviceName2);
} }
@Test @Test
public void testGatewayConnect() throws Exception { public void testGatewayConnect() throws Exception {
String payload = "{\"device\":\"Device A\"}"; String payload = "{\"device\":\"Device A\"}";
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken); MqttTestClient client = new MqttTestClient();
publishMqttMsg(client, payload.getBytes(), MqttTopics.GATEWAY_CONNECT_TOPIC); client.connectAndWait(gatewayAccessToken);
client.publish(GATEWAY_CONNECT_TOPIC, payload.getBytes());
String deviceName = "Device A"; String deviceName = "Device A";
@ -110,6 +110,7 @@ public abstract class AbstractMqttTimeseriesIntegrationTest extends AbstractMqtt
100); 100);
assertNotNull(device); assertNotNull(device);
client.disconnect();
} }
protected void processJsonPayloadTelemetryTest(String topic, List<String> expectedKeys, byte[] payload, boolean withTs) throws Exception { protected void processJsonPayloadTelemetryTest(String topic, List<String> expectedKeys, byte[] payload, boolean withTs) throws Exception {
@ -117,23 +118,14 @@ public abstract class AbstractMqttTimeseriesIntegrationTest extends AbstractMqtt
} }
protected void processTelemetryTest(String topic, List<String> expectedKeys, byte[] payload, boolean withTs, boolean presenceFieldsTest) throws Exception { protected void processTelemetryTest(String topic, List<String> expectedKeys, byte[] payload, boolean withTs, boolean presenceFieldsTest) throws Exception {
MqttAsyncClient client = getMqttAsyncClient(accessToken); MqttTestClient client = new MqttTestClient();
publishMqttMsg(client, payload, topic); client.connectAndWait(accessToken);
client.publishAndWait(topic, payload);
client.disconnect();
String deviceId = savedDevice.getId().getId().toString(); DeviceId deviceId = savedDevice.getId();
long start = System.currentTimeMillis(); List<String> actualKeys = getActualKeysList(deviceId, expectedKeys);
long end = System.currentTimeMillis() + 5000;
List<String> actualKeys = null;
while (start <= end) {
actualKeys = doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + deviceId + "/keys/timeseries", new TypeReference<>() {});
if (actualKeys.size() == expectedKeys.size()) {
break;
}
Thread.sleep(100);
start += 100;
}
assertNotNull(actualKeys); assertNotNull(actualKeys);
Set<String> actualKeySet = new HashSet<>(actualKeys); Set<String> actualKeySet = new HashSet<>(actualKeys);
@ -147,8 +139,8 @@ public abstract class AbstractMqttTimeseriesIntegrationTest extends AbstractMqtt
} else { } else {
getTelemetryValuesUrl = "/api/plugins/telemetry/DEVICE/" + deviceId + "/values/timeseries?keys=" + String.join(",", actualKeySet); getTelemetryValuesUrl = "/api/plugins/telemetry/DEVICE/" + deviceId + "/values/timeseries?keys=" + String.join(",", actualKeySet);
} }
start = System.currentTimeMillis(); long start = System.currentTimeMillis();
end = System.currentTimeMillis() + 5000; long end = System.currentTimeMillis() + 5000;
Map<String, List<Map<String, Object>>> values = null; Map<String, List<Map<String, Object>>> values = null;
while (start <= end) { while (start <= end) {
values = doGetAsyncTyped(getTelemetryValuesUrl, new TypeReference<>() {}); values = doGetAsyncTyped(getTelemetryValuesUrl, new TypeReference<>() {});
@ -187,9 +179,10 @@ public abstract class AbstractMqttTimeseriesIntegrationTest extends AbstractMqtt
} }
protected void processGatewayTelemetryTest(String topic, List<String> expectedKeys, byte[] payload, String firstDeviceName, String secondDeviceName) throws Exception { protected void processGatewayTelemetryTest(String topic, List<String> expectedKeys, byte[] payload, String firstDeviceName, String secondDeviceName) throws Exception {
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken); MqttTestClient client = new MqttTestClient();
client.connectAndWait(gatewayAccessToken);
publishMqttMsg(client, payload, topic); client.publishAndWait(topic, payload);
client.disconnect();
Device firstDevice = doExecuteWithRetriesAndInterval(() -> doGet("/api/tenant/devices?deviceName=" + firstDeviceName, Device.class), Device firstDevice = doExecuteWithRetriesAndInterval(() -> doGet("/api/tenant/devices?deviceName=" + firstDeviceName, Device.class),
20, 20,
@ -203,12 +196,10 @@ public abstract class AbstractMqttTimeseriesIntegrationTest extends AbstractMqtt
assertNotNull(secondDevice); assertNotNull(secondDevice);
Thread.sleep(2000); List<String> firstDeviceActualKeys = getActualKeysList(firstDevice.getId(), expectedKeys);
List<String> firstDeviceActualKeys = doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + firstDevice.getId() + "/keys/timeseries", new TypeReference<>() {});
Set<String> firstDeviceActualKeySet = new HashSet<>(firstDeviceActualKeys); Set<String> firstDeviceActualKeySet = new HashSet<>(firstDeviceActualKeys);
List<String> secondDeviceActualKeys = doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + secondDevice.getId() + "/keys/timeseries", new TypeReference<>() {}); List<String> secondDeviceActualKeys = getActualKeysList(secondDevice.getId(), expectedKeys);
Set<String> secondDeviceActualKeySet = new HashSet<>(secondDeviceActualKeys); Set<String> secondDeviceActualKeySet = new HashSet<>(secondDeviceActualKeys);
Set<String> expectedKeySet = new HashSet<>(expectedKeys); Set<String> expectedKeySet = new HashSet<>(expectedKeys);
@ -226,6 +217,22 @@ public abstract class AbstractMqttTimeseriesIntegrationTest extends AbstractMqtt
assertGatewayDeviceData(secondDeviceValues, expectedKeys); assertGatewayDeviceData(secondDeviceValues, expectedKeys);
} }
private List<String> getActualKeysList(DeviceId deviceId, List<String> expectedKeys) throws Exception {
long start = System.currentTimeMillis();
long end = System.currentTimeMillis() + 3000;
List<String> actualKeys = null;
while (start <= end) {
actualKeys = doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + deviceId + "/keys/timeseries", new TypeReference<>() {});
if (actualKeys.size() == expectedKeys.size()) {
break;
}
Thread.sleep(100);
start += 100;
}
return actualKeys;
}
protected String getGatewayTelemetryJsonPayload(String deviceA, String deviceB, String firstTsValue, String secondTsValue) { protected String getGatewayTelemetryJsonPayload(String deviceA, String deviceB, String firstTsValue, String secondTsValue) {
String payload = "[{\"ts\": " + firstTsValue + ", \"values\": " + PAYLOAD_VALUES_STR + "}, " + String payload = "[{\"ts\": " + firstTsValue + ", \"values\": " + PAYLOAD_VALUES_STR + "}, " +
"{\"ts\": " + secondTsValue + ", \"values\": " + PAYLOAD_VALUES_STR + "}]"; "{\"ts\": " + secondTsValue + ", \"values\": " + PAYLOAD_VALUES_STR + "}]";
@ -312,93 +319,20 @@ public abstract class AbstractMqttTimeseriesIntegrationTest extends AbstractMqtt
// @Test - Unstable // @Test - Unstable
public void testMqttQoSLevel() throws Exception { public void testMqttQoSLevel() throws Exception {
String clientId = MqttAsyncClient.generateClientId(); MqttTestClient client = new MqttTestClient();
MqttAsyncClient client = new MqttAsyncClient(MQTT_URL, clientId, new MemoryPersistence()); MqttTestCallback callback = new MqttTestCallback();
MqttConnectOptions options = new MqttConnectOptions();
options.setUserName(accessToken);
CountDownLatch latch = new CountDownLatch(1);
TestMqttCallback callback = new TestMqttCallback(client, latch);
client.setCallback(callback); client.setCallback(callback);
client.connect(options).waitForCompletion(5000); client.connectAndWait(accessToken);
client.subscribe("v1/devices/me/attributes", MqttQoS.AT_MOST_ONCE.value()); client.subscribe(DEVICE_ATTRIBUTES_TOPIC, MqttQoS.AT_MOST_ONCE);
String payload = "{\"key\":\"uniqueValue\"}"; String payload = "{\"key\":\"uniqueValue\"}";
// TODO 3.1: we need to acknowledge subscription only after it is processed by device actor and not when the message is pushed to queue. // TODO 3.1: we need to acknowledge subscription only after it is processed by device actor and not when the message is pushed to queue.
// MqttClient -> SUB REQUEST -> Transport -> Kafka -> Device Actor (subscribed) // MqttClient -> SUB REQUEST -> Transport -> Kafka -> Device Actor (subscribed)
// MqttClient <- SUB_ACK <- Transport // MqttClient <- SUB_ACK <- Transport
Thread.sleep(5000); Thread.sleep(5000);
doPostAsync("/api/plugins/telemetry/" + savedDevice.getId() + "/SHARED_SCOPE", payload, String.class, status().isOk()); doPostAsync("/api/plugins/telemetry/" + savedDevice.getId() + "/SHARED_SCOPE", payload, String.class, status().isOk());
latch.await(10, TimeUnit.SECONDS); callback.getSubscribeLatch().await(10, TimeUnit.SECONDS);
assertEquals(payload, callback.getPayload()); assertEquals(payload.getBytes(), callback.getPayloadBytes());
assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS()); assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS());
} }
private static class TestMqttCallback implements MqttCallback {
private final MqttAsyncClient client;
private final CountDownLatch latch;
private volatile Integer qoS;
private volatile String payload;
String getPayload() {
return payload;
}
TestMqttCallback(MqttAsyncClient client, CountDownLatch latch) {
this.client = client;
this.latch = latch;
}
int getQoS() {
return qoS;
}
@Override
public void connectionLost(Throwable throwable) {
log.error("Client connection lost", throwable);
}
@Override
public void messageArrived(String requestTopic, MqttMessage mqttMessage) {
payload = new String(mqttMessage.getPayload());
qoS = mqttMessage.getQos();
latch.countDown();
}
@Override
public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
}
}
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();
}
}
} }

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

@ -16,15 +16,15 @@
package org.thingsboard.server.transport.mqtt.telemetry.timeseries; package org.thingsboard.server.transport.mqtt.telemetry.timeseries;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.TransportPayloadType;
import org.thingsboard.server.transport.mqtt.MqttTestCallback;
import org.thingsboard.server.transport.mqtt.MqttTestClient;
import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties; import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties;
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 java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
@ -121,13 +121,14 @@ public abstract class AbstractMqttTimeseriesJsonIntegrationTest extends Abstract
.sendAckOnValidationException(true) .sendAckOnValidationException(true)
.build(); .build();
processBeforeTest(configProperties); processBeforeTest(configProperties);
CountDownLatch latch = new CountDownLatch(1); MqttTestClient client = new MqttTestClient();
MqttAsyncClient client = getMqttAsyncClient(accessToken); client.connectAndWait(accessToken);
TestMqttPublishCallback callback = new TestMqttPublishCallback(latch); MqttTestCallback callback = new MqttTestCallback();
client.setCallback(callback); client.setCallback(callback);
publishMqttMsg(client, MALFORMED_JSON_PAYLOAD.getBytes(), POST_DATA_TELEMETRY_TOPIC); client.publish(POST_DATA_TELEMETRY_TOPIC, MALFORMED_JSON_PAYLOAD.getBytes());
latch.await(3, TimeUnit.SECONDS); callback.getDeliveryLatch().await(3, TimeUnit.SECONDS);
assertTrue(callback.isPubAckReceived()); assertTrue(callback.isPubAckReceived());
client.disconnect();
} }
@Test @Test
@ -138,12 +139,12 @@ public abstract class AbstractMqttTimeseriesJsonIntegrationTest extends Abstract
.telemetryTopicFilter(POST_DATA_TELEMETRY_TOPIC) .telemetryTopicFilter(POST_DATA_TELEMETRY_TOPIC)
.build(); .build();
processBeforeTest(configProperties); processBeforeTest(configProperties);
CountDownLatch latch = new CountDownLatch(1); MqttTestClient client = new MqttTestClient();
MqttAsyncClient client = getMqttAsyncClient(accessToken); client.connectAndWait(accessToken);
TestMqttPublishCallback callback = new TestMqttPublishCallback(latch); MqttTestCallback callback = new MqttTestCallback();
client.setCallback(callback); client.setCallback(callback);
publishMqttMsg(client, MALFORMED_JSON_PAYLOAD.getBytes(), POST_DATA_TELEMETRY_TOPIC); client.publish(POST_DATA_TELEMETRY_TOPIC, MALFORMED_JSON_PAYLOAD.getBytes());
latch.await(3, TimeUnit.SECONDS); callback.getDeliveryLatch().await(3, TimeUnit.SECONDS);
assertFalse(callback.isPubAckReceived()); assertFalse(callback.isPubAckReceived());
} }
@ -157,13 +158,14 @@ public abstract class AbstractMqttTimeseriesJsonIntegrationTest extends Abstract
.sendAckOnValidationException(true) .sendAckOnValidationException(true)
.build(); .build();
processBeforeTest(configProperties); processBeforeTest(configProperties);
CountDownLatch latch = new CountDownLatch(1); MqttTestClient client = new MqttTestClient();
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken); client.connectAndWait(gatewayAccessToken);
TestMqttPublishCallback callback = new TestMqttPublishCallback(latch); MqttTestCallback callback = new MqttTestCallback();
client.setCallback(callback); client.setCallback(callback);
publishMqttMsg(client, MALFORMED_JSON_PAYLOAD.getBytes(), POST_DATA_TELEMETRY_TOPIC); client.publish(POST_DATA_TELEMETRY_TOPIC, MALFORMED_JSON_PAYLOAD.getBytes());
latch.await(3, TimeUnit.SECONDS); callback.getDeliveryLatch().await(3, TimeUnit.SECONDS);
assertTrue(callback.isPubAckReceived()); assertTrue(callback.isPubAckReceived());
client.disconnect();
} }
@Test @Test
@ -175,12 +177,12 @@ public abstract class AbstractMqttTimeseriesJsonIntegrationTest extends Abstract
.telemetryTopicFilter(POST_DATA_TELEMETRY_TOPIC) .telemetryTopicFilter(POST_DATA_TELEMETRY_TOPIC)
.build(); .build();
processBeforeTest(configProperties); processBeforeTest(configProperties);
CountDownLatch latch = new CountDownLatch(1); MqttTestClient client = new MqttTestClient();
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken); client.connectAndWait(gatewayAccessToken);
TestMqttPublishCallback callback = new TestMqttPublishCallback(latch); MqttTestCallback callback = new MqttTestCallback();
client.setCallback(callback); client.setCallback(callback);
publishMqttMsg(client, MALFORMED_JSON_PAYLOAD.getBytes(), POST_DATA_TELEMETRY_TOPIC); client.publish(POST_DATA_TELEMETRY_TOPIC, MALFORMED_JSON_PAYLOAD.getBytes());
latch.await(3, TimeUnit.SECONDS); callback.getDeliveryLatch().await(3, TimeUnit.SECONDS);
assertFalse(callback.isPubAckReceived()); assertFalse(callback.isPubAckReceived());
} }

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

@ -20,28 +20,32 @@ import com.google.protobuf.Descriptors;
import com.google.protobuf.DynamicMessage; import com.google.protobuf.DynamicMessage;
import com.squareup.wire.schema.internal.parser.ProtoFileElement; import com.squareup.wire.schema.internal.parser.ProtoFileElement;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
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.Device;
import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.TransportPayloadType;
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.MqttDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.MqttTopics;
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;
import org.thingsboard.server.gen.transport.TransportApiProtos; import org.thingsboard.server.gen.transport.TransportApiProtos;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.transport.mqtt.MqttTestCallback;
import org.thingsboard.server.transport.mqtt.MqttTestClient;
import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties; import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties;
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 java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertFalse; 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;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_TELEMETRY_SHORT_JSON_TOPIC;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_TELEMETRY_SHORT_PROTO_TOPIC;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_TELEMETRY_SHORT_TOPIC;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.GATEWAY_CONNECT_TOPIC;
import static org.thingsboard.server.common.data.device.profile.MqttTopics.GATEWAY_TELEMETRY_TOPIC;
@Slf4j @Slf4j
public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends AbstractMqttTimeseriesIntegrationTest { public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends AbstractMqttTimeseriesIntegrationTest {
@ -272,7 +276,7 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac
.build(); .build();
processBeforeTest(configProperties); processBeforeTest(configProperties);
DynamicMessage postTelemetryMsg = getDefaultDynamicMessage(); DynamicMessage postTelemetryMsg = getDefaultDynamicMessage();
processTelemetryTest(MqttTopics.DEVICE_TELEMETRY_SHORT_TOPIC, Arrays.asList("key1", "key2", "key3", "key4", "key5"), postTelemetryMsg.toByteArray(), false, false); processTelemetryTest(DEVICE_TELEMETRY_SHORT_TOPIC, Arrays.asList("key1", "key2", "key3", "key4", "key5"), postTelemetryMsg.toByteArray(), false, false);
} }
@Test @Test
@ -283,7 +287,7 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac
.telemetryTopicFilter(POST_DATA_TELEMETRY_TOPIC) .telemetryTopicFilter(POST_DATA_TELEMETRY_TOPIC)
.build(); .build();
processBeforeTest(configProperties); processBeforeTest(configProperties);
processJsonPayloadTelemetryTest(MqttTopics.DEVICE_TELEMETRY_SHORT_JSON_TOPIC, Arrays.asList("key1", "key2", "key3", "key4", "key5"), PAYLOAD_VALUES_STR.getBytes(), false); processJsonPayloadTelemetryTest(DEVICE_TELEMETRY_SHORT_JSON_TOPIC, Arrays.asList("key1", "key2", "key3", "key4", "key5"), PAYLOAD_VALUES_STR.getBytes(), false);
} }
@Test @Test
@ -295,7 +299,7 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac
.build(); .build();
processBeforeTest(configProperties); processBeforeTest(configProperties);
DynamicMessage postTelemetryMsg = getDefaultDynamicMessage(); DynamicMessage postTelemetryMsg = getDefaultDynamicMessage();
processTelemetryTest(MqttTopics.DEVICE_TELEMETRY_SHORT_PROTO_TOPIC, Arrays.asList("key1", "key2", "key3", "key4", "key5"), postTelemetryMsg.toByteArray(), false, false); processTelemetryTest(DEVICE_TELEMETRY_SHORT_PROTO_TOPIC, Arrays.asList("key1", "key2", "key3", "key4", "key5"), postTelemetryMsg.toByteArray(), false, false);
} }
@Test @Test
@ -315,7 +319,7 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac
TransportApiProtos.TelemetryMsg deviceBTelemetryMsgProto = getDeviceTelemetryMsgProto(deviceName2, expectedKeys, 10000, 20000); TransportApiProtos.TelemetryMsg deviceBTelemetryMsgProto = getDeviceTelemetryMsgProto(deviceName2, expectedKeys, 10000, 20000);
gatewayTelemetryMsgProtoBuilder.addAllMsg(Arrays.asList(deviceATelemetryMsgProto, deviceBTelemetryMsgProto)); gatewayTelemetryMsgProtoBuilder.addAllMsg(Arrays.asList(deviceATelemetryMsgProto, deviceBTelemetryMsgProto));
TransportApiProtos.GatewayTelemetryMsg gatewayTelemetryMsg = gatewayTelemetryMsgProtoBuilder.build(); TransportApiProtos.GatewayTelemetryMsg gatewayTelemetryMsg = gatewayTelemetryMsgProtoBuilder.build();
processGatewayTelemetryTest(MqttTopics.GATEWAY_TELEMETRY_TOPIC, expectedKeys, gatewayTelemetryMsg.toByteArray(), deviceName1, deviceName2); processGatewayTelemetryTest(GATEWAY_TELEMETRY_TOPIC, expectedKeys, gatewayTelemetryMsg.toByteArray(), deviceName1, deviceName2);
} }
@Test @Test
@ -329,14 +333,16 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac
processBeforeTest(configProperties); processBeforeTest(configProperties);
String deviceName = "Device A"; String deviceName = "Device A";
TransportApiProtos.ConnectMsg connectMsgProto = getConnectProto(deviceName); TransportApiProtos.ConnectMsg connectMsgProto = getConnectProto(deviceName);
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken); MqttTestClient client = new MqttTestClient();
publishMqttMsg(client, connectMsgProto.toByteArray(), MqttTopics.GATEWAY_CONNECT_TOPIC); client.connectAndWait(gatewayAccessToken);
client.publish(GATEWAY_CONNECT_TOPIC, connectMsgProto.toByteArray());
Device device = doExecuteWithRetriesAndInterval(() -> doGet("/api/tenant/devices?deviceName=" + deviceName, Device.class), Device device = doExecuteWithRetriesAndInterval(() -> doGet("/api/tenant/devices?deviceName=" + deviceName, Device.class),
20, 20,
100); 100);
assertNotNull(device); assertNotNull(device);
client.disconnect();
} }
@ -349,13 +355,14 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac
.sendAckOnValidationException(true) .sendAckOnValidationException(true)
.build(); .build();
processBeforeTest(configProperties); processBeforeTest(configProperties);
CountDownLatch latch = new CountDownLatch(1); MqttTestClient client = new MqttTestClient();
MqttAsyncClient client = getMqttAsyncClient(accessToken); client.connectAndWait(accessToken);
TestMqttPublishCallback callback = new TestMqttPublishCallback(latch); MqttTestCallback callback = new MqttTestCallback();
client.setCallback(callback); client.setCallback(callback);
publishMqttMsg(client, MALFORMED_PROTO_PAYLOAD.getBytes(), POST_DATA_TELEMETRY_TOPIC); client.publish(POST_DATA_TELEMETRY_TOPIC, MALFORMED_PROTO_PAYLOAD.getBytes());
latch.await(3, TimeUnit.SECONDS); callback.getDeliveryLatch().await(3, TimeUnit.SECONDS);
assertTrue(callback.isPubAckReceived()); assertTrue(callback.isPubAckReceived());
client.disconnect();
} }
@Test @Test
@ -366,12 +373,12 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac
.telemetryTopicFilter(POST_DATA_TELEMETRY_TOPIC) .telemetryTopicFilter(POST_DATA_TELEMETRY_TOPIC)
.build(); .build();
processBeforeTest(configProperties); processBeforeTest(configProperties);
CountDownLatch latch = new CountDownLatch(1); MqttTestClient client = new MqttTestClient();
MqttAsyncClient client = getMqttAsyncClient(accessToken); client.connectAndWait(accessToken);
TestMqttPublishCallback callback = new TestMqttPublishCallback(latch); MqttTestCallback callback = new MqttTestCallback();
client.setCallback(callback); client.setCallback(callback);
publishMqttMsg(client, MALFORMED_PROTO_PAYLOAD.getBytes(), POST_DATA_TELEMETRY_TOPIC); client.publish(POST_DATA_TELEMETRY_TOPIC, MALFORMED_PROTO_PAYLOAD.getBytes());
latch.await(3, TimeUnit.SECONDS); callback.getDeliveryLatch().await(3, TimeUnit.SECONDS);
assertFalse(callback.isPubAckReceived()); assertFalse(callback.isPubAckReceived());
} }
@ -385,13 +392,14 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac
.sendAckOnValidationException(true) .sendAckOnValidationException(true)
.build(); .build();
processBeforeTest(configProperties); processBeforeTest(configProperties);
CountDownLatch latch = new CountDownLatch(1); MqttTestClient client = new MqttTestClient();
MqttAsyncClient client = getMqttAsyncClient(accessToken); client.connectAndWait(accessToken);
TestMqttPublishCallback callback = new TestMqttPublishCallback(latch); MqttTestCallback callback = new MqttTestCallback();
client.setCallback(callback); client.setCallback(callback);
publishMqttMsg(client, MALFORMED_JSON_PAYLOAD.getBytes(), POST_DATA_TELEMETRY_TOPIC); client.publish(POST_DATA_TELEMETRY_TOPIC, MALFORMED_JSON_PAYLOAD.getBytes());
latch.await(3, TimeUnit.SECONDS); callback.getDeliveryLatch().await(3, TimeUnit.SECONDS);
assertTrue(callback.isPubAckReceived()); assertTrue(callback.isPubAckReceived());
client.disconnect();
} }
@Test @Test
@ -403,12 +411,12 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac
.enableCompatibilityWithJsonPayloadFormat(true) .enableCompatibilityWithJsonPayloadFormat(true)
.build(); .build();
processBeforeTest(configProperties); processBeforeTest(configProperties);
CountDownLatch latch = new CountDownLatch(1); MqttTestClient client = new MqttTestClient();
MqttAsyncClient client = getMqttAsyncClient(accessToken); client.connectAndWait(accessToken);
TestMqttPublishCallback callback = new TestMqttPublishCallback(latch); MqttTestCallback callback = new MqttTestCallback();
client.setCallback(callback); client.setCallback(callback);
publishMqttMsg(client, MALFORMED_JSON_PAYLOAD.getBytes(), POST_DATA_TELEMETRY_TOPIC); client.publish(POST_DATA_TELEMETRY_TOPIC, MALFORMED_JSON_PAYLOAD.getBytes());
latch.await(3, TimeUnit.SECONDS); callback.getDeliveryLatch().await(3, TimeUnit.SECONDS);
assertFalse(callback.isPubAckReceived()); assertFalse(callback.isPubAckReceived());
} }
@ -422,13 +430,14 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac
.sendAckOnValidationException(true) .sendAckOnValidationException(true)
.build(); .build();
processBeforeTest(configProperties); processBeforeTest(configProperties);
CountDownLatch latch = new CountDownLatch(1); MqttTestClient client = new MqttTestClient();
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken); client.connectAndWait(gatewayAccessToken);
TestMqttPublishCallback callback = new TestMqttPublishCallback(latch); MqttTestCallback callback = new MqttTestCallback();
client.setCallback(callback); client.setCallback(callback);
publishMqttMsg(client, MALFORMED_PROTO_PAYLOAD.getBytes(), POST_DATA_TELEMETRY_TOPIC); client.publish(POST_DATA_TELEMETRY_TOPIC, MALFORMED_PROTO_PAYLOAD.getBytes());
latch.await(3, TimeUnit.SECONDS); callback.getDeliveryLatch().await(3, TimeUnit.SECONDS);
assertTrue(callback.isPubAckReceived()); assertTrue(callback.isPubAckReceived());
client.disconnect();
} }
@Test @Test
@ -440,12 +449,12 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac
.telemetryTopicFilter(POST_DATA_TELEMETRY_TOPIC) .telemetryTopicFilter(POST_DATA_TELEMETRY_TOPIC)
.build(); .build();
processBeforeTest(configProperties); processBeforeTest(configProperties);
CountDownLatch latch = new CountDownLatch(1); MqttTestClient client = new MqttTestClient();
MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken); client.connectAndWait(gatewayAccessToken);
TestMqttPublishCallback callback = new TestMqttPublishCallback(latch); MqttTestCallback callback = new MqttTestCallback();
client.setCallback(callback); client.setCallback(callback);
publishMqttMsg(client, MALFORMED_PROTO_PAYLOAD.getBytes(), POST_DATA_TELEMETRY_TOPIC); client.publish(POST_DATA_TELEMETRY_TOPIC, MALFORMED_PROTO_PAYLOAD.getBytes());
latch.await(3, TimeUnit.SECONDS); callback.getDeliveryLatch().await(3, TimeUnit.SECONDS);
assertFalse(callback.isPubAckReceived()); assertFalse(callback.isPubAckReceived());
} }

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

@ -141,6 +141,7 @@ public class CachedAttributesService implements AttributesService {
.filter(Objects::nonNull) .filter(Objects::nonNull)
.collect(Collectors.toList()); .collect(Collectors.toList());
if (wrappedCachedAttributes.size() == attributeKeys.size()) { if (wrappedCachedAttributes.size() == attributeKeys.size()) {
log.trace("[{}][{}] Found all attributes from cache: {}", entityId, scope, attributeKeys);
return Futures.immediateFuture(cachedAttributes); return Futures.immediateFuture(cachedAttributes);
} }
@ -152,6 +153,7 @@ public class CachedAttributesService implements AttributesService {
return cacheExecutor.submit(() -> { return cacheExecutor.submit(() -> {
var cacheTransaction = cache.newTransactionForKeys(notFoundKeys); var cacheTransaction = cache.newTransactionForKeys(notFoundKeys);
try { try {
log.trace("[{}][{}] Lookup attributes from db: {}", entityId, scope, notFoundAttributeKeys);
List<AttributeKvEntry> result = attributesDao.find(tenantId, entityId, scope, notFoundAttributeKeys); List<AttributeKvEntry> result = attributesDao.find(tenantId, entityId, scope, notFoundAttributeKeys);
for (AttributeKvEntry foundInDbAttribute : result) { for (AttributeKvEntry foundInDbAttribute : result) {
AttributeCacheKey attributeCacheKey = new AttributeCacheKey(scope, entityId, foundInDbAttribute.getKey()); AttributeCacheKey attributeCacheKey = new AttributeCacheKey(scope, entityId, foundInDbAttribute.getKey());
@ -164,6 +166,7 @@ public class CachedAttributesService implements AttributesService {
List<AttributeKvEntry> mergedAttributes = new ArrayList<>(cachedAttributes); List<AttributeKvEntry> mergedAttributes = new ArrayList<>(cachedAttributes);
mergedAttributes.addAll(result); mergedAttributes.addAll(result);
cacheTransaction.commit(); cacheTransaction.commit();
log.trace("[{}][{}] Commit cache transaction: {}", entityId, scope, notFoundAttributeKeys);
return mergedAttributes; return mergedAttributes;
} catch (Throwable e) { } catch (Throwable e) {
cacheTransaction.rollback(); cacheTransaction.rollback();
@ -211,7 +214,9 @@ public class CachedAttributesService implements AttributesService {
for (var attribute : attributes) { for (var attribute : attributes) {
ListenableFuture<String> future = attributesDao.save(tenantId, entityId, scope, attribute); ListenableFuture<String> future = attributesDao.save(tenantId, entityId, scope, attribute);
futures.add(Futures.transform(future, key -> { futures.add(Futures.transform(future, key -> {
log.trace("[{}][{}][{}] Before cache evict: {}", entityId, scope, key, attribute);
cache.evictOrPut(new AttributeCacheKey(scope, entityId, key), attribute); cache.evictOrPut(new AttributeCacheKey(scope, entityId, key), attribute);
log.trace("[{}][{}][{}] after cache evict.", entityId, scope, key);
return key; return key;
}, cacheExecutor)); }, cacheExecutor));
} }

3
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttGatewayClientTest.java

@ -408,9 +408,8 @@ public class MqttGatewayClientTest extends AbstractContainerTest {
private Device createDeviceThroughGateway(MqttClient mqttClient, Device gatewayDevice) throws Exception { private Device createDeviceThroughGateway(MqttClient mqttClient, Device gatewayDevice) throws Exception {
String deviceName = "mqtt_device"; String deviceName = "mqtt_device";
mqttClient.publish("v1/gateway/connect", Unpooled.wrappedBuffer(createGatewayConnectPayload(deviceName).toString().getBytes())).get(); mqttClient.publish("v1/gateway/connect", Unpooled.wrappedBuffer(createGatewayConnectPayload(deviceName).toString().getBytes()), MqttQoS.AT_LEAST_ONCE).get();
TimeUnit.SECONDS.sleep(3);
List<EntityRelation> relations = restClient.findByFrom(gatewayDevice.getId(), RelationTypeGroup.COMMON); List<EntityRelation> relations = restClient.findByFrom(gatewayDevice.getId(), RelationTypeGroup.COMMON);
Assert.assertEquals(1, relations.size()); Assert.assertEquals(1, relations.size());

10
ui-ngx/src/app/core/api/alias-controller.ts

@ -17,14 +17,15 @@
import { AliasInfo, IAliasController, StateControllerHolder, StateEntityInfo } from '@core/api/widget-api.models'; import { AliasInfo, IAliasController, StateControllerHolder, StateEntityInfo } from '@core/api/widget-api.models';
import { forkJoin, Observable, of, ReplaySubject, Subject } from 'rxjs'; import { forkJoin, Observable, of, ReplaySubject, Subject } from 'rxjs';
import { Datasource, DatasourceType, datasourceTypeTranslationMap } from '@app/shared/models/widget.models'; import { Datasource, DatasourceType, datasourceTypeTranslationMap } from '@app/shared/models/widget.models';
import { deepClone, isEqual } from '@core/utils'; import { deepClone, isDefinedAndNotNull, isEqual } from '@core/utils';
import { EntityService } from '@core/http/entity.service'; import { EntityService } from '@core/http/entity.service';
import { UtilsService } from '@core/services/utils.service'; import { UtilsService } from '@core/services/utils.service';
import { AliasFilterType, EntityAliases, SingleEntityFilter } from '@shared/models/alias.models'; import { AliasFilterType, EntityAliases, SingleEntityFilter } from '@shared/models/alias.models';
import { EntityInfo } from '@shared/models/entity.models'; import { EntityInfo } from '@shared/models/entity.models';
import { map, mergeMap } from 'rxjs/operators'; import { map, mergeMap } from 'rxjs/operators';
import { import {
defaultEntityDataPageLink, Filter, FilterInfo, filterInfoToKeyFilters, Filters, KeyFilter, singleEntityDataPageLink, createDefaultEntityDataPageLink,
Filter, FilterInfo, filterInfoToKeyFilters, Filters, KeyFilter, singleEntityDataPageLink,
updateDatasourceFromEntityInfo updateDatasourceFromEntityInfo
} from '@shared/models/query/query.models'; } from '@shared/models/query/query.models';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
@ -322,7 +323,7 @@ export class AliasController implements IAliasController {
); );
} }
resolveDatasources(datasources: Array<Datasource>, singleEntity?: boolean): Observable<Array<Datasource>> { resolveDatasources(datasources: Array<Datasource>, singleEntity?: boolean, pageSize = 1024): Observable<Array<Datasource>> {
if (!datasources || !datasources.length) { if (!datasources || !datasources.length) {
return of([]); return of([]);
} }
@ -360,7 +361,8 @@ export class AliasController implements IAliasController {
if (singleEntity) { if (singleEntity) {
datasource.pageLink = deepClone(singleEntityDataPageLink); datasource.pageLink = deepClone(singleEntityDataPageLink);
} else if (!datasource.pageLink) { } else if (!datasource.pageLink) {
datasource.pageLink = deepClone(defaultEntityDataPageLink); pageSize = isDefinedAndNotNull(pageSize) && pageSize > 0 ? pageSize : 1024;
datasource.pageLink = createDefaultEntityDataPageLink(pageSize);
} }
} }
}); });

4
ui-ngx/src/app/core/api/widget-api.models.ts

@ -119,7 +119,7 @@ export interface IAliasController {
getEntityAliasId(aliasName: string): string; getEntityAliasId(aliasName: string): string;
getInstantAliasInfo(aliasId: string): AliasInfo; getInstantAliasInfo(aliasId: string): AliasInfo;
resolveSingleEntityInfo(aliasId: string): Observable<EntityInfo>; resolveSingleEntityInfo(aliasId: string): Observable<EntityInfo>;
resolveDatasources(datasources: Array<Datasource>, singleEntity?: boolean): Observable<Array<Datasource>>; resolveDatasources(datasources: Array<Datasource>, singleEntity?: boolean, pageSize?: number): Observable<Array<Datasource>>;
resolveAlarmSource(alarmSource: Datasource): Observable<Datasource>; resolveAlarmSource(alarmSource: Datasource): Observable<Datasource>;
getEntityAliases(): EntityAliases; getEntityAliases(): EntityAliases;
getFilters(): Filters; getFilters(): Filters;
@ -184,6 +184,7 @@ export interface SubscriptionInfo {
deviceName?: string; deviceName?: string;
deviceNamePrefix?: string; deviceNamePrefix?: string;
deviceIds?: Array<string>; deviceIds?: Array<string>;
pageSize?: number;
} }
export class WidgetSubscriptionContext { export class WidgetSubscriptionContext {
@ -242,6 +243,7 @@ export interface WidgetSubscriptionOptions {
datasourcesOptional?: boolean; datasourcesOptional?: boolean;
hasDataPageLink?: boolean; hasDataPageLink?: boolean;
singleEntity?: boolean; singleEntity?: boolean;
pageSize?: number;
warnOnPageDataOverflow?: boolean; warnOnPageDataOverflow?: boolean;
ignoreDataUpdateOnIntervalTick?: boolean; ignoreDataUpdateOnIntervalTick?: boolean;
targetDeviceAliasIds?: Array<string>; targetDeviceAliasIds?: Array<string>;

8
ui-ngx/src/app/core/api/widget-subscription.ts

@ -97,6 +97,7 @@ export class WidgetSubscription implements IWidgetSubscription {
hasDataPageLink: boolean; hasDataPageLink: boolean;
singleEntity: boolean; singleEntity: boolean;
pageSize: number;
warnOnPageDataOverflow: boolean; warnOnPageDataOverflow: boolean;
ignoreDataUpdateOnIntervalTick: boolean; ignoreDataUpdateOnIntervalTick: boolean;
@ -229,6 +230,7 @@ export class WidgetSubscription implements IWidgetSubscription {
this.entityDataListeners = []; this.entityDataListeners = [];
this.hasDataPageLink = options.hasDataPageLink; this.hasDataPageLink = options.hasDataPageLink;
this.singleEntity = options.singleEntity; this.singleEntity = options.singleEntity;
this.pageSize = options.pageSize;
this.warnOnPageDataOverflow = options.warnOnPageDataOverflow; this.warnOnPageDataOverflow = options.warnOnPageDataOverflow;
this.ignoreDataUpdateOnIntervalTick = options.ignoreDataUpdateOnIntervalTick; this.ignoreDataUpdateOnIntervalTick = options.ignoreDataUpdateOnIntervalTick;
this.datasourcePages = []; this.datasourcePages = [];
@ -387,7 +389,7 @@ export class WidgetSubscription implements IWidgetSubscription {
} }
); );
} else { } else {
this.ctx.aliasController.resolveDatasources(this.configuredDatasources, this.singleEntity).subscribe( this.ctx.aliasController.resolveDatasources(this.configuredDatasources, this.singleEntity, this.pageSize).subscribe(
(datasources) => { (datasources) => {
this.configuredDatasources = datasources; this.configuredDatasources = datasources;
this.prepareDataSubscriptions().subscribe( this.prepareDataSubscriptions().subscribe(
@ -1132,7 +1134,7 @@ export class WidgetSubscription implements IWidgetSubscription {
} }
); );
} else { } else {
this.ctx.aliasController.resolveDatasources(this.configuredDatasources, this.singleEntity).subscribe( this.ctx.aliasController.resolveDatasources(this.configuredDatasources, this.singleEntity, this.pageSize).subscribe(
(datasources) => { (datasources) => {
this.configuredDatasources = datasources; this.configuredDatasources = datasources;
this.prepareDataSubscriptions().subscribe( this.prepareDataSubscriptions().subscribe(
@ -1271,7 +1273,7 @@ export class WidgetSubscription implements IWidgetSubscription {
totalPages: pageData.totalPages totalPages: pageData.totalPages
}; };
if (datasource.type === DatasourceType.entity && if (datasource.type === DatasourceType.entity &&
pageData.hasNext && pageLink.pageSize > 1) { pageData.hasNext && !this.singleEntity) {
if (this.warnOnPageDataOverflow) { if (this.warnOnPageDataOverflow) {
const message = this.ctx.translate.instant('widget.data-overflow', const message = this.ctx.translate.instant('widget.data-overflow',
{count: pageData.data.length, total: pageData.totalElements}); {count: pageData.data.length, total: pageData.totalElements});

6
ui-ngx/src/app/core/http/entity.service.ts

@ -1319,7 +1319,8 @@ export class EntityService {
pageLink = deepClone(singleEntityDataPageLink); pageLink = deepClone(singleEntityDataPageLink);
} else { } else {
nameFilter = subscriptionInfo.entityNamePrefix; nameFilter = subscriptionInfo.entityNamePrefix;
pageLink = deepClone(defaultEntityDataPageLink); const pageSize = isDefinedAndNotNull(subscriptionInfo.pageSize) && subscriptionInfo.pageSize > 0 ? subscriptionInfo.pageSize : 1024;
pageLink = createDefaultEntityDataPageLink(pageSize);
} }
datasource.entityFilter = { datasource.entityFilter = {
type: AliasFilterType.entityName, type: AliasFilterType.entityName,
@ -1333,7 +1334,8 @@ export class EntityService {
entityType: subscriptionInfo.entityType, entityType: subscriptionInfo.entityType,
entityList: subscriptionInfo.entityIds entityList: subscriptionInfo.entityIds
}; };
datasource.pageLink = deepClone(defaultEntityDataPageLink); const pageSize = isDefinedAndNotNull(subscriptionInfo.pageSize) && subscriptionInfo.pageSize > 0 ? subscriptionInfo.pageSize : 1024;
datasource.pageLink = createDefaultEntityDataPageLink(pageSize);
} }
} }

87
ui-ngx/src/app/core/schema-utils.ts

@ -1,87 +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.
///
import { JsonSettingsSchema } from '@shared/models/widget.models';
export function initSchema(): JsonSettingsSchema {
return {
schema: {
type: 'object',
properties: {},
required: []
},
form: [],
groupInfoes: []
};
}
export function addGroupInfo(schema: JsonSettingsSchema, title: string) {
schema.groupInfoes.push({
formIndex: schema.groupInfoes?.length || 0,
GroupTitle: title
});
}
export function addToSchema(schema: JsonSettingsSchema, newSchema: JsonSettingsSchema) {
Object.assign(schema.schema.properties, newSchema.schema.properties);
schema.schema.required = schema.schema.required.concat(newSchema.schema.required);
schema.form.push(newSchema.form);
}
export function mergeSchemes(schemes: JsonSettingsSchema[]): JsonSettingsSchema {
return schemes.reduce((finalSchema: JsonSettingsSchema, schema: JsonSettingsSchema) => {
return {
schema: {
properties: {
...finalSchema.schema.properties,
...schema.schema.properties
},
required: [
...finalSchema.schema.required,
...schema.schema.required
]
},
form: [
...finalSchema.form,
...schema.form
]
} as JsonSettingsSchema;
}, initSchema());
}
export function addCondition(schema: JsonSettingsSchema, condition: string, exclude: string[] = []): JsonSettingsSchema {
schema.form = schema.form.map(element => {
if (!exclude.includes(element) && !exclude.includes(element.key)) {
if (typeof element === 'string') {
return {
key: element,
condition
};
}
if (typeof element === 'object') {
if (element.condition) {
element.condition += ' && ' + condition;
}
else {
element.condition = condition;
}
}
}
return element;
});
return schema;
}

19
ui-ngx/src/app/modules/home/components/widget/lib/maps/common-maps-utils.ts

@ -28,27 +28,8 @@ import {
import { Observable, Observer, of } from 'rxjs'; import { Observable, Observer, of } from 'rxjs';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { FormattedData } from '@shared/models/widget.models'; import { FormattedData } from '@shared/models/widget.models';
import _ from 'lodash';
import { mapProviderSchema, providerSets } from '@home/components/widget/lib/maps/schemes';
import { addCondition, mergeSchemes } from '@core/schema-utils';
import L from 'leaflet'; import L from 'leaflet';
export function getProviderSchema(mapProvider: MapProviders, ignoreImageMap = false) {
const providerSchema = _.cloneDeep(mapProviderSchema);
if (mapProvider) {
providerSchema.schema.properties.provider.default = mapProvider;
}
if (ignoreImageMap) {
providerSchema.form[0].items = providerSchema.form[0]?.items.filter(item => item.value !== 'image-map');
}
return mergeSchemes([providerSchema,
...Object.keys(providerSets)?.map(
(key: string) => {
const setting = providerSets[key];
return addCondition(setting?.schema, `model.provider === '${setting.name}'`);
})]);
}
export function getRatio(firsMoment: number, secondMoment: number, intermediateMoment: number): number { export function getRatio(firsMoment: number, secondMoment: number, intermediateMoment: number): number {
return (intermediateMoment - firsMoment) / (secondMoment - firsMoment); return (intermediateMoment - firsMoment) / (secondMoment - firsMoment);
} }

5
ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget.interface.ts

@ -14,8 +14,6 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { JsonSettingsSchema } from '@shared/models/widget.models';
import { MapProviders } from '@home/components/widget/lib/maps/map-models';
import LeafletMap from '@home/components/widget/lib/maps/leaflet-map'; import LeafletMap from '@home/components/widget/lib/maps/leaflet-map';
export interface MapWidgetInterface { export interface MapWidgetInterface {
@ -26,8 +24,5 @@ export interface MapWidgetInterface {
} }
export interface MapWidgetStaticInterface { export interface MapWidgetStaticInterface {
settingsSchema(mapProvider?: MapProviders, drawRoutes?: boolean): JsonSettingsSchema;
getProvidersSchema(mapProvider?: MapProviders, ignoreImageMap?: boolean): JsonSettingsSchema;
dataKeySettingsSchema(): object;
actionSources(): object; actionSources(): object;
} }

60
ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts

@ -14,26 +14,11 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { import { defaultMapSettings, MapProviders, UnitedMapSettings, WidgetUnitedMapSettings } from './map-models';
defaultMapSettings,
MapProviders,
UnitedMapSettings,
WidgetUnitedMapSettings
} from './map-models';
import LeafletMap from './leaflet-map'; import LeafletMap from './leaflet-map';
import {
commonMapSettingsSchema,
editorSettingSchema,
mapCircleSchema,
mapPolygonSchema,
markerClusteringSettingsSchema,
markerClusteringSettingsSchemaLeaflet,
routeMapSettingsSchema
} from './schemes';
import { MapWidgetInterface, MapWidgetStaticInterface } from './map-widget.interface'; import { MapWidgetInterface, MapWidgetStaticInterface } from './map-widget.interface';
import { addCondition, addGroupInfo, addToSchema, initSchema, mergeSchemes } from '@core/schema-utils';
import { WidgetContext } from '@app/modules/home/models/widget-component.models'; import { WidgetContext } from '@app/modules/home/models/widget-component.models';
import { getDefCenterPosition, getProviderSchema, parseWithTranslation } from './common-maps-utils'; import { getDefCenterPosition, parseWithTranslation } from './common-maps-utils';
import { import {
Datasource, Datasource,
DatasourceData, DatasourceData,
@ -45,7 +30,7 @@ import { TranslateService } from '@ngx-translate/core';
import { UtilsService } from '@core/services/utils.service'; import { UtilsService } from '@core/services/utils.service';
import { EntityDataPageLink } from '@shared/models/query/query.models'; import { EntityDataPageLink } from '@shared/models/query/query.models';
import { providerClass } from '@home/components/widget/lib/maps/providers'; import { providerClass } from '@home/components/widget/lib/maps/providers';
import { isDefined, parseFunction } from '@core/utils'; import { isDefined, isDefinedAndNotNull, parseFunction } from '@core/utils';
import L from 'leaflet'; import L from 'leaflet';
import { forkJoin, Observable, of } from 'rxjs'; import { forkJoin, Observable, of } from 'rxjs';
import { AttributeService } from '@core/http/attribute.service'; import { AttributeService } from '@core/http/attribute.service';
@ -87,9 +72,13 @@ export class MapWidgetController implements MapWidgetInterface {
this.map.saveMarkerLocation = this.setMarkerLocation.bind(this); this.map.saveMarkerLocation = this.setMarkerLocation.bind(this);
this.map.savePolygonLocation = this.savePolygonLocation.bind(this); this.map.savePolygonLocation = this.savePolygonLocation.bind(this);
this.map.saveLocation = this.saveLocation.bind(this); this.map.saveLocation = this.saveLocation.bind(this);
let pageSize = this.settings.mapPageSize;
if (isDefinedAndNotNull(this.ctx.widgetConfig.pageSize)) {
pageSize = Math.max(pageSize, this.ctx.widgetConfig.pageSize);
}
this.pageLink = { this.pageLink = {
page: 0, page: 0,
pageSize: this.settings.mapPageSize, pageSize,
textSearch: null, textSearch: null,
dynamic: true dynamic: true
}; };
@ -104,39 +93,6 @@ export class MapWidgetController implements MapWidgetInterface {
settings: WidgetUnitedMapSettings; settings: WidgetUnitedMapSettings;
pageLink: EntityDataPageLink; pageLink: EntityDataPageLink;
public static dataKeySettingsSchema(): object {
return {};
}
public static getProvidersSchema(mapProvider: MapProviders, ignoreImageMap = false) {
return getProviderSchema(mapProvider, ignoreImageMap);
}
public static settingsSchema(mapProvider: MapProviders, drawRoutes: boolean): JsonSettingsSchema {
const schema = initSchema();
addToSchema(schema, this.getProvidersSchema(mapProvider));
addGroupInfo(schema, 'Map Provider Settings');
addToSchema(schema, commonMapSettingsSchema);
addGroupInfo(schema, 'Common Map Settings');
addToSchema(schema, addCondition(mapPolygonSchema, 'model.showPolygon === true', ['showPolygon']));
addGroupInfo(schema, 'Polygon Settings');
addToSchema(schema, addCondition(mapCircleSchema, 'model.showCircle === true', ['showCircle']));
addGroupInfo(schema, 'Circle Settings');
if (drawRoutes) {
addToSchema(schema, routeMapSettingsSchema);
addGroupInfo(schema, 'Route Map Settings');
} else {
const clusteringSchema = mergeSchemes([markerClusteringSettingsSchema,
addCondition(markerClusteringSettingsSchemaLeaflet,
`model.useClusterMarkers === true && model.provider !== "image-map"`)]);
addToSchema(schema, clusteringSchema);
addGroupInfo(schema, 'Markers Clustering Settings');
addToSchema(schema, addCondition(editorSettingSchema, '(model.editablePolygon === true || model.draggableMarker === true)'));
addGroupInfo(schema, 'Editor settings');
}
return schema;
}
public static actionSources(): object { public static actionSources(): object {
return { return {
markerClick: { markerClick: {

1
ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts

@ -88,6 +88,7 @@ export class ImageMap extends LeafletMap {
const imageUrlSubscriptionOptions: WidgetSubscriptionOptions = { const imageUrlSubscriptionOptions: WidgetSubscriptionOptions = {
datasources, datasources,
hasDataPageLink: true, hasDataPageLink: true,
singleEntity: true,
useDashboardTimewindow: false, useDashboardTimewindow: false,
type: widgetType.latest, type: widgetType.latest,
callbacks: { callbacks: {

1603
ui-ngx/src/app/modules/home/components/widget/lib/maps/schemes.ts

File diff suppressed because it is too large

6
ui-ngx/src/app/modules/home/components/widget/lib/markdown-widget.component.ts

@ -26,7 +26,7 @@ import {
fillDataPattern, fillDataPattern,
flatFormattedData, flatFormattedData,
formattedDataFormDatasourceData, formattedDataFormDatasourceData,
hashCode, hashCode, isDefinedAndNotNull,
isNotEmptyStr, isNotEmptyStr,
parseFunction, processDataPattern, parseFunction, processDataPattern,
safeExecute safeExecute
@ -83,9 +83,11 @@ export class MarkdownWidgetComponent extends PageComponent implements OnInit {
cssParser.cssPreviewNamespace = this.markdownClass; cssParser.cssPreviewNamespace = this.markdownClass;
cssParser.createStyleElement(this.markdownClass, cssString); cssParser.createStyleElement(this.markdownClass, cssString);
} }
const pageSize = isDefinedAndNotNull(this.ctx.widgetConfig.pageSize) &&
this.ctx.widgetConfig.pageSize > 0 ? this.ctx.widgetConfig.pageSize : 16384;
const pageLink: EntityDataPageLink = { const pageLink: EntityDataPageLink = {
page: 0, page: 0,
pageSize: 16384, pageSize,
textSearch: null, textSearch: null,
dynamic: true dynamic: true
}; };

45
ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.ts

@ -32,30 +32,22 @@ import {
MapProviders, MapProviders,
WidgetUnitedTripAnimationSettings WidgetUnitedTripAnimationSettings
} from '@home/components/widget/lib/maps/map-models'; } from '@home/components/widget/lib/maps/map-models';
import { addCondition, addGroupInfo, addToSchema, initSchema } from '@app/core/schema-utils';
import {
mapCircleSchema,
mapPolygonSchema,
pathSchema,
pointSchema,
tripAnimationSchema
} from '@home/components/widget/lib/maps/schemes';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
import { WidgetContext } from '@app/modules/home/models/widget-component.models'; import { WidgetContext } from '@app/modules/home/models/widget-component.models';
import { import {
findAngle, findAngle,
getProviderSchema,
getRatio, getRatio,
interpolateOnLineSegment, interpolateOnLineSegment,
parseWithTranslation parseWithTranslation
} from '@home/components/widget/lib/maps/common-maps-utils'; } from '@home/components/widget/lib/maps/common-maps-utils';
import { FormattedData, JsonSettingsSchema, WidgetConfig } from '@shared/models/widget.models'; import { FormattedData, WidgetConfig } from '@shared/models/widget.models';
import moment from 'moment'; import moment from 'moment';
import { import {
deepClone, formattedDataArrayFromDatasourceData,
formattedDataArrayFromDatasourceData, formattedDataFormDatasourceData, formattedDataFormDatasourceData,
isDefined, isDefined,
isUndefined, mergeFormattedData, isUndefined,
mergeFormattedData,
parseFunction, parseFunction,
safeExecute safeExecute
} from '@core/utils'; } from '@core/utils';
@ -101,23 +93,6 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy
useAnchors: boolean; useAnchors: boolean;
currentTime: number; currentTime: number;
static getSettingsSchema(): JsonSettingsSchema {
const schema = initSchema();
addToSchema(schema, getProviderSchema(null, true));
addGroupInfo(schema, 'Map Provider Settings');
addToSchema(schema, tripAnimationSchema);
addGroupInfo(schema, 'Trip Animation Settings');
addToSchema(schema, pathSchema);
addGroupInfo(schema, 'Path Settings');
addToSchema(schema, addCondition(pointSchema, 'model.showPoints === true', ['showPoints']));
addGroupInfo(schema, 'Path Points Settings');
addToSchema(schema, addCondition(mapPolygonSchema, 'model.showPolygon === true', ['showPolygon']));
addGroupInfo(schema, 'Polygon Settings');
addToSchema(schema, addCondition(mapCircleSchema, 'model.showCircle === true', ['showCircle']));
addGroupInfo(schema, 'Circle Settings');
return schema;
}
ngOnInit(): void { ngOnInit(): void {
this.widgetConfig = this.ctx.widgetConfig; this.widgetConfig = this.ctx.widgetConfig;
this.settings = { this.settings = {
@ -286,10 +261,12 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy
} }
calcLabel(points: FormattedData[]) { calcLabel(points: FormattedData[]) {
const data = points[this.activeTrip.dsIndex]; if (this.activeTrip) {
const labelText: string = this.settings.useLabelFunction ? const data = points[this.activeTrip.dsIndex];
safeExecute(this.settings.parsedLabelFunction, [data, points, data.dsIndex]) : this.settings.label; const labelText: string = this.settings.useLabelFunction ?
this.label = this.sanitizer.bypassSecurityTrustHtml(parseWithTranslation.parseTemplate(labelText, data, true)); safeExecute(this.settings.parsedLabelFunction, [data, points, data.dsIndex]) : this.settings.label;
this.label = this.sanitizer.bypassSecurityTrustHtml(parseWithTranslation.parseTemplate(labelText, data, true));
}
} }
private interpolateArray(originData: FormattedData[]): {[time: number]: FormattedData} { private interpolateArray(originData: FormattedData[]): {[time: number]: FormattedData} {

8
ui-ngx/src/app/modules/home/components/widget/widget-config.component.html

@ -317,6 +317,14 @@
<mat-panel-title translate>widget-config.data-settings</mat-panel-title> <mat-panel-title translate>widget-config.data-settings</mat-panel-title>
</mat-expansion-panel-header> </mat-expansion-panel-header>
<ng-template matExpansionPanelContent> <ng-template matExpansionPanelContent>
<div fxLayout="row" *ngIf="widgetType !== widgetTypes.rpc &&
widgetType !== widgetTypes.alarm &&
modelValue?.isDataEnabled && !modelValue?.typeParameters?.singleEntity">
<mat-form-field fxFlex>
<mat-label translate>widget-config.data-page-size</mat-label>
<input matInput formControlName="pageSize" type="number" min="1" step="1">
</mat-form-field>
</div>
<div fxLayout.xs="column" fxLayout="row" fxLayoutGap="8px"> <div fxLayout.xs="column" fxLayout="row" fxLayoutGap="8px">
<mat-form-field fxFlex> <mat-form-field fxFlex>
<mat-label translate>widget-config.units</mat-label> <mat-label translate>widget-config.units</mat-label>

2
ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts

@ -213,6 +213,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
widgetStyle: [null, []], widgetStyle: [null, []],
widgetCss: [null, []], widgetCss: [null, []],
titleStyle: [null, []], titleStyle: [null, []],
pageSize: [1024, [Validators.min(1), Validators.pattern(/^\d*$/)]],
units: [null, []], units: [null, []],
decimals: [null, [Validators.min(0), Validators.max(15), Validators.pattern(/^\d*$/)]], decimals: [null, [Validators.min(0), Validators.max(15), Validators.pattern(/^\d*$/)]],
noDataDisplayMessage: [null, []], noDataDisplayMessage: [null, []],
@ -420,6 +421,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
fontSize: '16px', fontSize: '16px',
fontWeight: 400 fontWeight: 400
}, },
pageSize: isDefined(config.pageSize) ? config.pageSize : 1024,
units: config.units, units: config.units,
decimals: config.decimals, decimals: config.decimals,
noDataDisplayMessage: isDefined(config.noDataDisplayMessage) ? config.noDataDisplayMessage : '', noDataDisplayMessage: isDefined(config.noDataDisplayMessage) ? config.noDataDisplayMessage : '',

3
ui-ngx/src/app/modules/home/components/widget/widget.component.ts

@ -977,7 +977,8 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
ignoreDataUpdateOnIntervalTick: this.typeParameters.ignoreDataUpdateOnIntervalTick, ignoreDataUpdateOnIntervalTick: this.typeParameters.ignoreDataUpdateOnIntervalTick,
comparisonEnabled: comparisonSettings.comparisonEnabled, comparisonEnabled: comparisonSettings.comparisonEnabled,
timeForComparison: comparisonSettings.timeForComparison, timeForComparison: comparisonSettings.timeForComparison,
comparisonCustomIntervalValue: comparisonSettings.comparisonCustomIntervalValue comparisonCustomIntervalValue: comparisonSettings.comparisonCustomIntervalValue,
pageSize: this.widget.config.pageSize
}; };
if (this.widget.type === widgetType.alarm) { if (this.widget.type === widgetType.alarm) {
options.alarmSource = deepClone(this.widget.config.alarmSource); options.alarmSource = deepClone(this.widget.config.alarmSource);

1
ui-ngx/src/app/shared/models/widget.models.ts

@ -554,6 +554,7 @@ export interface WidgetConfig {
units?: string; units?: string;
decimals?: number; decimals?: number;
noDataDisplayMessage?: string; noDataDisplayMessage?: string;
pageSize?: number;
actions?: {[actionSourceId: string]: Array<WidgetActionDescriptor>}; actions?: {[actionSourceId: string]: Array<WidgetActionDescriptor>};
settings?: WidgetSettings; settings?: WidgetSettings;
alarmSource?: Datasource; alarmSource?: Datasource;

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

@ -3305,6 +3305,7 @@
"advanced-settings": "Advanced settings", "advanced-settings": "Advanced settings",
"data-settings": "Data settings", "data-settings": "Data settings",
"no-data-display-message": "\"No data to display\" alternative message", "no-data-display-message": "\"No data to display\" alternative message",
"data-page-size": "Maximum entities per datasource",
"settings-component-not-found": "Settings form component not found for selector '{{selector}}'" "settings-component-not-found": "Settings form component not found for selector '{{selector}}'"
}, },
"widget-type": { "widget-type": {

Loading…
Cancel
Save