Browse Source

Merge with master

pull/4631/head
Andrii Shvaika 5 years ago
parent
commit
15d725570d
  1. 4
      application/pom.xml
  2. 22
      application/src/main/data/upgrade/3.2.2/schema_update.sql
  3. 34
      application/src/main/java/org/thingsboard/server/controller/AlarmController.java
  4. 5
      application/src/main/java/org/thingsboard/server/service/apiusage/BaseApiUsageState.java
  5. 7
      application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java
  6. 117
      application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java
  7. 16
      application/src/main/java/org/thingsboard/server/service/install/update/PaginatedUpdater.java
  8. 4
      application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java
  9. 16
      application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java
  10. 21
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java
  11. 2
      application/src/main/resources/logback.xml
  12. 2
      application/src/main/resources/thingsboard.yml
  13. 3
      application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java
  14. 3
      application/src/test/java/org/thingsboard/server/service/resource/BaseTbResourceServiceTest.java
  15. 2
      application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java
  16. 10
      common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmOperationResult.java
  17. 5
      common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java
  18. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityService.java
  19. 3
      common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java
  20. 7
      common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java
  21. 24
      common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageState.java
  22. 10
      common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java
  23. 20
      common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java
  24. 5
      common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java
  25. 11
      common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileInfo.java
  26. 6
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java
  27. 2
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java
  28. 2
      common/data/src/main/java/org/thingsboard/server/common/data/firmware/FirmwareUpdateStatus.java
  29. 3
      common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java
  30. 4
      common/transport/lwm2m/pom.xml
  31. 5
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java
  32. 8
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java
  33. 358
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MTransportMsgHandler.java
  34. 4
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java
  35. 22
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java
  36. 5
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportMsgHandler.java
  37. 445
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java
  38. 39
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelper.java
  39. 371
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportUtil.java
  40. 2
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mVersionedModelProvider.java
  41. 1
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/adaptors/LwM2MJsonAdaptor.java
  42. 109
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java
  43. 2
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContext.java
  44. 25
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java
  45. 27
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mFirmwareUpdate.java
  46. 355
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mFwSwUpdate.java
  47. 12
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/Lwm2mClientRpcRequest.java
  48. 373
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java
  49. 64
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisSecurityStore.java
  50. 4
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mSecurityStore.java
  51. 6
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mStoreFactory.java
  52. 8
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java
  53. 2
      dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java
  54. 56
      dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java
  55. 24
      dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java
  56. 12
      dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java
  57. 26
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java
  58. 15
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java
  59. 19
      dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java
  60. 46
      dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java
  61. 5
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  62. 12
      dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmEntity.java
  63. 5
      dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmEntity.java
  64. 5
      dao/src/main/java/org/thingsboard/server/dao/model/sql/ApiUsageStateEntity.java
  65. 7
      dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardEntity.java
  66. 5
      dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardInfoEntity.java
  67. 13
      dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java
  68. 136
      dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java
  69. 9
      dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java
  70. 52
      dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java
  71. 41
      dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java
  72. 8
      dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceProfileRepository.java
  73. 4
      dao/src/main/java/org/thingsboard/server/dao/sql/query/AlarmDataAdapter.java
  74. 1
      dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java
  75. 6
      dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java
  76. 3
      dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleChainRepository.java
  77. 2
      dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java
  78. 3
      dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java
  79. 8
      dao/src/main/resources/sql/schema-entities-hsql.sql
  80. 8
      dao/src/main/resources/sql/schema-entities.sql
  81. 21
      dao/src/test/java/org/thingsboard/server/dao/service/BaseAssetServiceTest.java
  82. 3
      dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceProfileServiceTest.java
  83. 21
      dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceServiceTest.java
  84. 19
      dao/src/test/java/org/thingsboard/server/dao/service/BaseEdgeServiceTest.java
  85. 2
      dao/src/test/resources/sql/hsql/drop-all-tables.sql
  86. 2
      dao/src/test/resources/sql/psql/drop-all-tables.sql
  87. 2
      dao/src/test/resources/sql/timescale/drop-all-tables.sql
  88. 5
      pom.xml
  89. 2
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineAlarmService.java
  90. 63
      transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml
  91. 2
      transport/snmp/src/main/java/org/thingsboard/server/snmp/ThingsboardSnmpTransportApplication.java
  92. 1
      ui-ngx/package.json
  93. 5
      ui-ngx/src/app/core/auth/auth.service.ts
  94. 2
      ui-ngx/src/app/core/http/alarm.service.ts
  95. 19
      ui-ngx/src/app/core/services/utils.service.ts
  96. 2
      ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts
  97. 3
      ui-ngx/src/app/modules/home/components/alias/aliases-entity-select.component.ts
  98. 88
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-image-dialog.component.html
  99. 59
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-image-dialog.component.scss
  100. 174
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-image-dialog.component.ts

4
application/pom.xml

@ -145,6 +145,10 @@
<version>${project.version}</version> <version>${project.version}</version>
<scope>runtime</scope> <scope>runtime</scope>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-redis</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId> <artifactId>spring-boot-starter-security</artifactId>

22
application/src/main/data/upgrade/3.2.2/schema_update.sql

@ -78,14 +78,24 @@ CREATE TABLE IF NOT EXISTS firmware (
CONSTRAINT firmware_tenant_title_version_unq_key UNIQUE (tenant_id, title, version) CONSTRAINT firmware_tenant_title_version_unq_key UNIQUE (tenant_id, title, version)
); );
ALTER TABLE dashboard
ADD COLUMN IF NOT EXISTS image varchar(1000000);
ALTER TABLE device_profile ALTER TABLE device_profile
ADD COLUMN IF NOT EXISTS image varchar(1000000),
ADD COLUMN IF NOT EXISTS firmware_id uuid, ADD COLUMN IF NOT EXISTS firmware_id uuid,
ADD COLUMN IF NOT EXISTS software_id uuid; ADD COLUMN IF NOT EXISTS software_id uuid,
ADD COLUMN IF NOT EXISTS default_dashboard_id uuid;
ALTER TABLE device ALTER TABLE device
ADD COLUMN IF NOT EXISTS firmware_id uuid, ADD COLUMN IF NOT EXISTS firmware_id uuid,
ADD COLUMN IF NOT EXISTS software_id uuid; ADD COLUMN IF NOT EXISTS software_id uuid;
ALTER TABLE alarm
ADD COLUMN IF NOT EXISTS customer_id uuid;
DELETE FROM relation WHERE from_type = 'TENANT' AND relation_type_group = 'RULE_CHAIN';
DO $$ DO $$
BEGIN BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_firmware_device_profile') THEN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_firmware_device_profile') THEN
@ -100,6 +110,12 @@ DO $$
FOREIGN KEY (firmware_id) REFERENCES firmware(id); FOREIGN KEY (firmware_id) REFERENCES firmware(id);
END IF; END IF;
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_default_dashboard_device_profile') THEN
ALTER TABLE device_profile
ADD CONSTRAINT fk_default_dashboard_device_profile
FOREIGN KEY (default_dashboard_id) REFERENCES dashboard(id);
END IF;
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_firmware_device') THEN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_firmware_device') THEN
ALTER TABLE device ALTER TABLE device
ADD CONSTRAINT fk_firmware_device ADD CONSTRAINT fk_firmware_device
@ -114,3 +130,7 @@ DO $$
END; END;
$$; $$;
ALTER TABLE api_usage_state
ADD COLUMN IF NOT EXISTS alarm_exec VARCHAR(32);
UPDATE api_usage_state SET alarm_exec = 'ENABLED' WHERE alarm_exec IS NULL;

34
application/src/main/java/org/thingsboard/server/controller/AlarmController.java

@ -196,6 +196,40 @@ public class AlarmController extends BaseController {
} }
} }
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/alarms", method = RequestMethod.GET)
@ResponseBody
public PageData<AlarmInfo> getAllAlarms(
@RequestParam(required = false) String searchStatus,
@RequestParam(required = false) String status,
@RequestParam int pageSize,
@RequestParam int page,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String sortProperty,
@RequestParam(required = false) String sortOrder,
@RequestParam(required = false) Long startTime,
@RequestParam(required = false) Long endTime,
@RequestParam(required = false) Boolean fetchOriginator
) throws ThingsboardException {
AlarmSearchStatus alarmSearchStatus = StringUtils.isEmpty(searchStatus) ? null : AlarmSearchStatus.valueOf(searchStatus);
AlarmStatus alarmStatus = StringUtils.isEmpty(status) ? null : AlarmStatus.valueOf(status);
if (alarmSearchStatus != null && alarmStatus != null) {
throw new ThingsboardException("Invalid alarms search query: Both parameters 'searchStatus' " +
"and 'status' can't be specified at the same time!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
}
TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime);
try {
if (getCurrentUser().isCustomerUser()) {
return checkNotNull(alarmService.findCustomerAlarms(getCurrentUser().getTenantId(), getCurrentUser().getCustomerId(), new AlarmQuery(null, pageLink, alarmSearchStatus, alarmStatus, fetchOriginator)).get());
} else {
return checkNotNull(alarmService.findAlarms(getCurrentUser().getTenantId(), new AlarmQuery(null, pageLink, alarmSearchStatus, alarmStatus, fetchOriginator)).get());
}
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/alarm/highestSeverity/{entityType}/{entityId}", method = RequestMethod.GET) @RequestMapping(value = "/alarm/highestSeverity/{entityType}/{entityId}", method = RequestMethod.GET)
@ResponseBody @ResponseBody

5
application/src/main/java/org/thingsboard/server/service/apiusage/BaseApiUsageState.java

@ -106,6 +106,8 @@ public abstract class BaseApiUsageState {
return apiUsageState.getEmailExecState(); return apiUsageState.getEmailExecState();
case SMS: case SMS:
return apiUsageState.getSmsExecState(); return apiUsageState.getSmsExecState();
case ALARM:
return apiUsageState.getAlarmExecState();
default: default:
return ApiUsageStateValue.ENABLED; return ApiUsageStateValue.ENABLED;
} }
@ -132,6 +134,9 @@ public abstract class BaseApiUsageState {
case SMS: case SMS:
apiUsageState.setSmsExecState(value); apiUsageState.setSmsExecState(value);
break; break;
case ALARM:
apiUsageState.setAlarmExecState(value);
break;
} }
return !currentValue.equals(value); return !currentValue.equals(value);
} }

7
application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java

@ -24,8 +24,9 @@ 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.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.FirmwareInfo; import org.thingsboard.server.common.data.FirmwareInfo;
import org.thingsboard.server.common.data.firmware.FirmwareUtil;
import org.thingsboard.server.common.data.firmware.FirmwareType; import org.thingsboard.server.common.data.firmware.FirmwareType;
import org.thingsboard.server.common.data.firmware.FirmwareUpdateStatus;
import org.thingsboard.server.common.data.firmware.FirmwareUtil;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.FirmwareId; import org.thingsboard.server.common.data.id.FirmwareId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
@ -66,11 +67,11 @@ import static org.thingsboard.server.common.data.firmware.FirmwareKey.STATE;
import static org.thingsboard.server.common.data.firmware.FirmwareKey.TITLE; import static org.thingsboard.server.common.data.firmware.FirmwareKey.TITLE;
import static org.thingsboard.server.common.data.firmware.FirmwareKey.TS; import static org.thingsboard.server.common.data.firmware.FirmwareKey.TS;
import static org.thingsboard.server.common.data.firmware.FirmwareKey.VERSION; import static org.thingsboard.server.common.data.firmware.FirmwareKey.VERSION;
import static org.thingsboard.server.common.data.firmware.FirmwareType.FIRMWARE;
import static org.thingsboard.server.common.data.firmware.FirmwareType.SOFTWARE;
import static org.thingsboard.server.common.data.firmware.FirmwareUtil.getAttributeKey; import static org.thingsboard.server.common.data.firmware.FirmwareUtil.getAttributeKey;
import static org.thingsboard.server.common.data.firmware.FirmwareUtil.getTargetTelemetryKey; import static org.thingsboard.server.common.data.firmware.FirmwareUtil.getTargetTelemetryKey;
import static org.thingsboard.server.common.data.firmware.FirmwareUtil.getTelemetryKey; import static org.thingsboard.server.common.data.firmware.FirmwareUtil.getTelemetryKey;
import static org.thingsboard.server.common.data.firmware.FirmwareType.FIRMWARE;
import static org.thingsboard.server.common.data.firmware.FirmwareType.SOFTWARE;
@Slf4j @Slf4j
@Service @Service

117
application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java

@ -27,6 +27,9 @@ import org.thingsboard.rule.engine.profile.TbDeviceProfileNode;
import org.thingsboard.rule.engine.profile.TbDeviceProfileNodeConfiguration; import org.thingsboard.rule.engine.profile.TbDeviceProfileNodeConfiguration;
import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmQuery;
import org.thingsboard.server.common.data.id.EntityViewId; import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery; import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
@ -34,9 +37,13 @@ import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry;
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.page.TimePageLink;
import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.dao.alarm.AlarmDao;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.tenant.TenantService;
@ -72,6 +79,15 @@ public class DefaultDataUpdateService implements DataUpdateService {
@Autowired @Autowired
private TimeseriesService tsService; private TimeseriesService tsService;
@Autowired
private AlarmService alarmService;
@Autowired
private EntityService entityService;
@Autowired
private AlarmDao alarmDao;
@Override @Override
public void updateData(String fromVersion) throws Exception { public void updateData(String fromVersion) throws Exception {
switch (fromVersion) { switch (fromVersion) {
@ -90,14 +106,25 @@ public class DefaultDataUpdateService implements DataUpdateService {
case "3.2.2": case "3.2.2":
log.info("Updating data from version 3.2.2 to 3.3.0 ..."); log.info("Updating data from version 3.2.2 to 3.3.0 ...");
tenantsDefaultEdgeRuleChainUpdater.updateEntities(null); tenantsDefaultEdgeRuleChainUpdater.updateEntities(null);
tenantsAlarmsCustomerUpdater.updateEntities(null);
break; break;
default: default:
throw new RuntimeException("Unable to update data, unsupported fromVersion: " + fromVersion); throw new RuntimeException("Unable to update data, unsupported fromVersion: " + fromVersion);
} }
} }
private PaginatedUpdater<String, Tenant> tenantsDefaultRuleChainUpdater = private final PaginatedUpdater<String, Tenant> tenantsDefaultRuleChainUpdater =
new PaginatedUpdater<String, Tenant>() { new PaginatedUpdater<>() {
@Override
protected String getName() {
return "Tenants default rule chain updater";
}
@Override
protected boolean forceReportTotal() {
return true;
}
@Override @Override
protected PageData<Tenant> findEntities(String region, PageLink pageLink) { protected PageData<Tenant> findEntities(String region, PageLink pageLink) {
@ -117,8 +144,18 @@ public class DefaultDataUpdateService implements DataUpdateService {
} }
}; };
private PaginatedUpdater<String, Tenant> tenantsDefaultEdgeRuleChainUpdater = private final PaginatedUpdater<String, Tenant> tenantsDefaultEdgeRuleChainUpdater =
new PaginatedUpdater<String, Tenant>() { new PaginatedUpdater<>() {
@Override
protected String getName() {
return "Tenants default edge rule chain updater";
}
@Override
protected boolean forceReportTotal() {
return true;
}
@Override @Override
protected PageData<Tenant> findEntities(String region, PageLink pageLink) { protected PageData<Tenant> findEntities(String region, PageLink pageLink) {
@ -138,8 +175,18 @@ public class DefaultDataUpdateService implements DataUpdateService {
} }
}; };
private PaginatedUpdater<String, Tenant> tenantsRootRuleChainUpdater = private final PaginatedUpdater<String, Tenant> tenantsRootRuleChainUpdater =
new PaginatedUpdater<String, Tenant>() { new PaginatedUpdater<>() {
@Override
protected String getName() {
return "Tenants root rule chain updater";
}
@Override
protected boolean forceReportTotal() {
return true;
}
@Override @Override
protected PageData<Tenant> findEntities(String region, PageLink pageLink) { protected PageData<Tenant> findEntities(String region, PageLink pageLink) {
@ -192,8 +239,18 @@ public class DefaultDataUpdateService implements DataUpdateService {
} }
}; };
private PaginatedUpdater<String, Tenant> tenantsEntityViewsUpdater = private final PaginatedUpdater<String, Tenant> tenantsEntityViewsUpdater =
new PaginatedUpdater<String, Tenant>() { new PaginatedUpdater<>() {
@Override
protected String getName() {
return "Tenants entity views updater";
}
@Override
protected boolean forceReportTotal() {
return true;
}
@Override @Override
protected PageData<Tenant> findEntities(String region, PageLink pageLink) { protected PageData<Tenant> findEntities(String region, PageLink pageLink) {
@ -261,4 +318,48 @@ public class DefaultDataUpdateService implements DataUpdateService {
}, MoreExecutors.directExecutor()); }, MoreExecutors.directExecutor());
} }
private final PaginatedUpdater<String, Tenant> tenantsAlarmsCustomerUpdater =
new PaginatedUpdater<>() {
@Override
protected String getName() {
return "Tenants alarms customer updater";
}
@Override
protected boolean forceReportTotal() {
return true;
}
@Override
protected PageData<Tenant> findEntities(String region, PageLink pageLink) {
return tenantService.findTenants(pageLink);
}
@Override
protected void updateEntity(Tenant tenant) {
updateTenantAlarmsCustomer(tenant.getId());
}
};
private void updateTenantAlarmsCustomer(TenantId tenantId) {
AlarmQuery alarmQuery = new AlarmQuery(null, new TimePageLink(100), null, null, false);
PageData<AlarmInfo> alarms = alarmDao.findAlarms(tenantId, alarmQuery);
boolean hasNext = true;
while (hasNext) {
for (Alarm alarm : alarms.getData()) {
if (alarm.getCustomerId() == null && alarm.getOriginator() != null) {
alarm.setCustomerId(entityService.fetchEntityCustomerId(tenantId, alarm.getOriginator()));
alarmDao.save(tenantId, alarm);
}
}
if (alarms.hasNext()) {
alarmQuery.setPageLink(alarmQuery.getPageLink().nextPageLink());
alarms = alarmDao.findAlarms(tenantId, alarmQuery);
} else {
hasNext = false;
}
}
}
} }

16
application/src/main/java/org/thingsboard/server/service/install/update/PaginatedUpdater.java

@ -15,16 +15,20 @@
*/ */
package org.thingsboard.server.service.install.update; package org.thingsboard.server.service.install.update;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.SearchTextBased; import org.thingsboard.server.common.data.SearchTextBased;
import org.thingsboard.server.common.data.id.UUIDBased; import org.thingsboard.server.common.data.id.UUIDBased;
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;
@Slf4j
public abstract class PaginatedUpdater<I, D extends SearchTextBased<? extends UUIDBased>> { public abstract class PaginatedUpdater<I, D extends SearchTextBased<? extends UUIDBased>> {
private static final int DEFAULT_LIMIT = 100; private static final int DEFAULT_LIMIT = 100;
private int updated = 0;
public void updateEntities(I id) { public void updateEntities(I id) {
updated = 0;
PageLink pageLink = new PageLink(DEFAULT_LIMIT); PageLink pageLink = new PageLink(DEFAULT_LIMIT);
boolean hasNext = true; boolean hasNext = true;
while (hasNext) { while (hasNext) {
@ -32,13 +36,25 @@ public abstract class PaginatedUpdater<I, D extends SearchTextBased<? extends UU
for (D entity : entities.getData()) { for (D entity : entities.getData()) {
updateEntity(entity); updateEntity(entity);
} }
updated += entities.getData().size();
hasNext = entities.hasNext(); hasNext = entities.hasNext();
if (hasNext) { if (hasNext) {
log.info("{}: {} entities updated so far...", getName(), updated);
pageLink = pageLink.nextPageLink(); pageLink = pageLink.nextPageLink();
} else {
if (updated > DEFAULT_LIMIT || forceReportTotal()) {
log.info("{}: {} total entities updated.", getName(), updated);
}
} }
} }
} }
protected boolean forceReportTotal() {
return false;
}
protected abstract String getName();
protected abstract PageData<D> findEntities(I id, PageLink pageLink); protected abstract PageData<D> findEntities(I id, PageLink pageLink);
protected abstract void updateEntity(D entity); protected abstract void updateEntity(D entity);

4
application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java

@ -309,6 +309,8 @@ public class DefaultMailService implements MailService {
case EMAIL: case EMAIL:
case SMS: case SMS:
return "send"; return "send";
case ALARM:
return "create";
default: default:
throw new RuntimeException("Not implemented!"); throw new RuntimeException("Not implemented!");
} }
@ -327,6 +329,8 @@ public class DefaultMailService implements MailService {
case EMAIL: case EMAIL:
case SMS: case SMS:
return "sent"; return "sent";
case ALARM:
return "created";
default: default:
throw new RuntimeException("Not implemented!"); throw new RuntimeException("Not implemented!");
} }

16
application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java

@ -44,6 +44,7 @@ import java.util.Base64;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_KEY; import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_KEY;
import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_SEARCH_TEXT; import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_SEARCH_TEXT;
@ -130,7 +131,7 @@ public class DefaultTbResourceService implements TbResourceService {
List<TbResource> resources = resourceService.findTenantResourcesByResourceTypeAndObjectIds(tenantId, ResourceType.LWM2M_MODEL, List<TbResource> resources = resourceService.findTenantResourcesByResourceTypeAndObjectIds(tenantId, ResourceType.LWM2M_MODEL,
objectIds); objectIds);
return resources.stream() return resources.stream()
.map(this::toLwM2mObject) .flatMap(s -> Stream.ofNullable(toLwM2mObject(s)))
.sorted(getComparator(sortProperty, sortOrder)) .sorted(getComparator(sortProperty, sortOrder))
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
@ -141,7 +142,7 @@ public class DefaultTbResourceService implements TbResourceService {
validateId(tenantId, INCORRECT_TENANT_ID + tenantId); validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
PageData<TbResource> resourcePageData = resourceService.findTenantResourcesByResourceTypeAndPageLink(tenantId, ResourceType.LWM2M_MODEL, pageLink); PageData<TbResource> resourcePageData = resourceService.findTenantResourcesByResourceTypeAndPageLink(tenantId, ResourceType.LWM2M_MODEL, pageLink);
return resourcePageData.getData().stream() return resourcePageData.getData().stream()
.map(this::toLwM2mObject) .flatMap(s -> Stream.ofNullable(toLwM2mObject(s)))
.sorted(getComparator(sortProperty, sortOrder)) .sorted(getComparator(sortProperty, sortOrder))
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
@ -190,9 +191,14 @@ public class DefaultTbResourceService implements TbResourceService {
resources.add(lwM2MResourceObserve); resources.add(lwM2MResourceObserve);
} }
}); });
instance.setResources(resources.toArray(LwM2mResourceObserve[]::new)); if (resources.size() > 0) {
lwM2mObject.setInstances(new LwM2mInstance[]{instance}); instance.setResources(resources.toArray(LwM2mResourceObserve[]::new));
return lwM2mObject; lwM2mObject.setInstances(new LwM2mInstance[]{instance});
return lwM2mObject;
}
else {
return null;
}
} }
} catch (IOException | InvalidDDFFileException e) { } catch (IOException | InvalidDDFFileException e) {
log.error("Could not parse the XML of objectModel with name [{}]", resource.getSearchText(), e); log.error("Could not parse the XML of objectModel with name [{}]", resource.getSearchText(), e);

21
application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java

@ -23,6 +23,7 @@ import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.ApiUsageRecordKey;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmQuery; import org.thingsboard.server.common.data.alarm.AlarmQuery;
@ -43,6 +44,8 @@ import org.thingsboard.server.dao.alarm.AlarmOperationResult;
import org.thingsboard.server.dao.alarm.AlarmService; import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.usagestats.TbApiUsageClient;
import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.service.queue.TbClusterService;
import org.thingsboard.server.service.subscription.SubscriptionManagerService; import org.thingsboard.server.service.subscription.SubscriptionManagerService;
import org.thingsboard.server.service.subscription.TbSubscriptionUtils; import org.thingsboard.server.service.subscription.TbSubscriptionUtils;
@ -58,12 +61,18 @@ import java.util.Optional;
public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService implements AlarmSubscriptionService { public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService implements AlarmSubscriptionService {
private final AlarmService alarmService; private final AlarmService alarmService;
private final TbApiUsageClient apiUsageClient;
private final TbApiUsageStateService apiUsageStateService;
public DefaultAlarmSubscriptionService(TbClusterService clusterService, public DefaultAlarmSubscriptionService(TbClusterService clusterService,
PartitionService partitionService, PartitionService partitionService,
AlarmService alarmService) { AlarmService alarmService,
TbApiUsageClient apiUsageClient,
TbApiUsageStateService apiUsageStateService) {
super(clusterService, partitionService); super(clusterService, partitionService);
this.alarmService = alarmService; this.alarmService = alarmService;
this.apiUsageClient = apiUsageClient;
this.apiUsageStateService = apiUsageStateService;
} }
@Autowired(required = false) @Autowired(required = false)
@ -78,10 +87,13 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
@Override @Override
public Alarm createOrUpdateAlarm(Alarm alarm) { public Alarm createOrUpdateAlarm(Alarm alarm) {
AlarmOperationResult result = alarmService.createOrUpdateAlarm(alarm); AlarmOperationResult result = alarmService.createOrUpdateAlarm(alarm, apiUsageStateService.getApiUsageState(alarm.getTenantId()).isAlarmCreationEnabled());
if (result.isSuccessful()) { if (result.isSuccessful()) {
onAlarmUpdated(result); onAlarmUpdated(result);
} }
if (result.isCreated()) {
apiUsageClient.report(alarm.getTenantId(), null, ApiUsageRecordKey.CREATED_ALARMS_COUNT);
}
return result.getAlarm(); return result.getAlarm();
} }
@ -127,6 +139,11 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
return alarmService.findAlarms(tenantId, query); return alarmService.findAlarms(tenantId, query);
} }
@Override
public ListenableFuture<PageData<AlarmInfo>> findCustomerAlarms(TenantId tenantId, CustomerId customerId, AlarmQuery query) {
return alarmService.findCustomerAlarms(tenantId, customerId, query);
}
@Override @Override
public AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus, AlarmStatus alarmStatus) { public AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus, AlarmStatus alarmStatus) {
return alarmService.findHighestAlarmSeverity(tenantId, entityId, alarmSearchStatus, alarmStatus); return alarmService.findHighestAlarmSeverity(tenantId, entityId, alarmSearchStatus, alarmStatus);

2
application/src/main/resources/logback.xml

@ -26,7 +26,7 @@
</appender> </appender>
<logger name="org.thingsboard.server" level="INFO" /> <logger name="org.thingsboard.server" level="INFO" />
<logger name="org.thingsboard.server.transport.snmp" level="TRACE" /> <logger name="org.thingsboard.server.transport.snmp" level="DEBUG" />
<!-- <logger name="org.thingsboard.server.service.queue" level="TRACE" />--> <!-- <logger name="org.thingsboard.server.service.queue" level="TRACE" />-->
<!-- <logger name="org.thingsboard.server.service.transport" level="TRACE" />--> <!-- <logger name="org.thingsboard.server.service.transport" level="TRACE" />-->

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

@ -673,6 +673,8 @@ transport:
recommended_supported_groups: "${LWM2M_RECOMMENDED_SUPPORTED_GROUPS:true}" recommended_supported_groups: "${LWM2M_RECOMMENDED_SUPPORTED_GROUPS:true}"
response_pool_size: "${LWM2M_RESPONSE_POOL_SIZE:100}" response_pool_size: "${LWM2M_RESPONSE_POOL_SIZE:100}"
registered_pool_size: "${LWM2M_REGISTERED_POOL_SIZE:10}" registered_pool_size: "${LWM2M_REGISTERED_POOL_SIZE:10}"
registration_store_pool_size: "${LWM2M_REGISTRATION_STORE_POOL_SIZE:100}"
clean_period_in_sec: "${LWM2M_CLEAN_PERIOD_IN_SEC:2}"
update_registered_pool_size: "${LWM2M_UPDATE_REGISTERED_POOL_SIZE:10}" update_registered_pool_size: "${LWM2M_UPDATE_REGISTERED_POOL_SIZE:10}"
un_registered_pool_size: "${LWM2M_UN_REGISTERED_POOL_SIZE:10}" un_registered_pool_size: "${LWM2M_UN_REGISTERED_POOL_SIZE:10}"
log_max_length: "${LWM2M_LOG_MAX_LENGTH:100}" log_max_length: "${LWM2M_LOG_MAX_LENGTH:100}"

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

@ -313,7 +313,8 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController
Collections.sort(loadedDeviceProfileInfos, deviceProfileInfoIdComparator); Collections.sort(loadedDeviceProfileInfos, deviceProfileInfoIdComparator);
List<DeviceProfileInfo> deviceProfileInfos = deviceProfiles.stream().map(deviceProfile -> new DeviceProfileInfo(deviceProfile.getId(), List<DeviceProfileInfo> deviceProfileInfos = deviceProfiles.stream().map(deviceProfile -> new DeviceProfileInfo(deviceProfile.getId(),
deviceProfile.getName(), deviceProfile.getType(), deviceProfile.getTransportType())).collect(Collectors.toList()); deviceProfile.getName(), deviceProfile.getImage(), deviceProfile.getDefaultDashboardId(),
deviceProfile.getType(), deviceProfile.getTransportType())).collect(Collectors.toList());
Assert.assertEquals(deviceProfileInfos, loadedDeviceProfileInfos); Assert.assertEquals(deviceProfileInfos, loadedDeviceProfileInfos);

3
application/src/test/java/org/thingsboard/server/service/resource/BaseTbResourceServiceTest.java

@ -32,7 +32,6 @@ import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.controller.AbstractControllerTest; import org.thingsboard.server.controller.AbstractControllerTest;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.AbstractServiceTest;
import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.dao.service.DaoSqlTest;
import java.util.ArrayList; import java.util.ArrayList;
@ -57,7 +56,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
"<Resources>\n" + "<Resources>\n" +
"<Item ID=\"0\">\n" + "<Item ID=\"0\">\n" +
"<Name>LWM2M</Name>\n" + "<Name>LWM2M</Name>\n" +
"<Operations></Operations>\n" + "<Operations>RW</Operations>\n" +
"<MultipleInstances>Single</MultipleInstances>\n" + "<MultipleInstances>Single</MultipleInstances>\n" +
"<Mandatory>Mandatory</Mandatory>\n" + "<Mandatory>Mandatory</Mandatory>\n" +
"<Type>String</Type>\n" + "<Type>String</Type>\n" +

2
application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java

@ -177,7 +177,7 @@ public class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest {
executor = Executors.newScheduledThreadPool(10); executor = Executors.newScheduledThreadPool(10);
loginTenantAdmin(); loginTenantAdmin();
String[] resources = new String[]{"0.xml", "1.xml", "2.xml", "3.xml"}; String[] resources = new String[]{"1.xml", "2.xml", "3.xml"};
for (String resourceName : resources) { for (String resourceName : resources) {
TbResource lwModel = new TbResource(); TbResource lwModel = new TbResource();
lwModel.setResourceType(ResourceType.LWM2M_MODEL); lwModel.setResourceType(ResourceType.LWM2M_MODEL);

10
common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmOperationResult.java

@ -26,17 +26,21 @@ import java.util.List;
public class AlarmOperationResult { public class AlarmOperationResult {
private final Alarm alarm; private final Alarm alarm;
private final boolean successful; private final boolean successful;
private final boolean created;
private final List<EntityId> propagatedEntitiesList; private final List<EntityId> propagatedEntitiesList;
public AlarmOperationResult(Alarm alarm, boolean successful) { public AlarmOperationResult(Alarm alarm, boolean successful) {
this.alarm = alarm; this(alarm, successful, Collections.emptyList());
this.successful = successful;
this.propagatedEntitiesList = Collections.emptyList();
} }
public AlarmOperationResult(Alarm alarm, boolean successful, List<EntityId> propagatedEntitiesList) { public AlarmOperationResult(Alarm alarm, boolean successful, List<EntityId> propagatedEntitiesList) {
this(alarm, successful, false, propagatedEntitiesList);
}
public AlarmOperationResult(Alarm alarm, boolean successful, boolean created, List<EntityId> propagatedEntitiesList) {
this.alarm = alarm; this.alarm = alarm;
this.successful = successful; this.successful = successful;
this.created = created;
this.propagatedEntitiesList = propagatedEntitiesList; this.propagatedEntitiesList = propagatedEntitiesList;
} }
} }

5
common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java

@ -29,7 +29,6 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.AlarmData; import org.thingsboard.server.common.data.query.AlarmData;
import org.thingsboard.server.common.data.query.AlarmDataPageLink;
import org.thingsboard.server.common.data.query.AlarmDataQuery; import org.thingsboard.server.common.data.query.AlarmDataQuery;
import java.util.Collection; import java.util.Collection;
@ -41,6 +40,8 @@ public interface AlarmService {
AlarmOperationResult createOrUpdateAlarm(Alarm alarm); AlarmOperationResult createOrUpdateAlarm(Alarm alarm);
AlarmOperationResult createOrUpdateAlarm(Alarm alarm, boolean alarmCreationEnabled);
AlarmOperationResult deleteAlarm(TenantId tenantId, AlarmId alarmId); AlarmOperationResult deleteAlarm(TenantId tenantId, AlarmId alarmId);
ListenableFuture<AlarmOperationResult> ackAlarm(TenantId tenantId, AlarmId alarmId, long ackTs); ListenableFuture<AlarmOperationResult> ackAlarm(TenantId tenantId, AlarmId alarmId, long ackTs);
@ -53,6 +54,8 @@ public interface AlarmService {
ListenableFuture<PageData<AlarmInfo>> findAlarms(TenantId tenantId, AlarmQuery query); ListenableFuture<PageData<AlarmInfo>> findAlarms(TenantId tenantId, AlarmQuery query);
ListenableFuture<PageData<AlarmInfo>> findCustomerAlarms(TenantId tenantId, CustomerId customerId, AlarmQuery query);
AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus, AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus,
AlarmStatus alarmStatus); AlarmStatus alarmStatus);

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

@ -28,6 +28,8 @@ public interface EntityService {
ListenableFuture<String> fetchEntityNameAsync(TenantId tenantId, EntityId entityId); ListenableFuture<String> fetchEntityNameAsync(TenantId tenantId, EntityId entityId);
CustomerId fetchEntityCustomerId(TenantId tenantId, EntityId entityId);
void deleteEntityRelations(TenantId tenantId, EntityId entityId); void deleteEntityRelations(TenantId tenantId, EntityId entityId);
long countEntitiesByQuery(TenantId tenantId, CustomerId customerId, EntityCountQuery query); long countEntitiesByQuery(TenantId tenantId, CustomerId customerId, EntityCountQuery query);

3
common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java

@ -23,7 +23,8 @@ public enum ApiFeature {
RE("ruleEngineApiState", "Rule Engine execution"), RE("ruleEngineApiState", "Rule Engine execution"),
JS("jsExecutionApiState", "JavaScript functions execution"), JS("jsExecutionApiState", "JavaScript functions execution"),
EMAIL("emailApiState", "Email messages"), EMAIL("emailApiState", "Email messages"),
SMS("smsApiState", "SMS messages"); SMS("smsApiState", "SMS messages"),
ALARM("alarmApiState", "Created alarms");
@Getter @Getter
private final String apiStateKey; private final String apiStateKey;

7
common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java

@ -25,13 +25,16 @@ public enum ApiUsageRecordKey {
RE_EXEC_COUNT(ApiFeature.RE, "ruleEngineExecutionCount", "ruleEngineExecutionLimit"), RE_EXEC_COUNT(ApiFeature.RE, "ruleEngineExecutionCount", "ruleEngineExecutionLimit"),
JS_EXEC_COUNT(ApiFeature.JS, "jsExecutionCount", "jsExecutionLimit"), JS_EXEC_COUNT(ApiFeature.JS, "jsExecutionCount", "jsExecutionLimit"),
EMAIL_EXEC_COUNT(ApiFeature.EMAIL, "emailCount", "emailLimit"), EMAIL_EXEC_COUNT(ApiFeature.EMAIL, "emailCount", "emailLimit"),
SMS_EXEC_COUNT(ApiFeature.SMS, "smsCount", "smsLimit"); SMS_EXEC_COUNT(ApiFeature.SMS, "smsCount", "smsLimit"),
CREATED_ALARMS_COUNT(ApiFeature.ALARM, "createdAlarmsCount", "createdAlarmsLimit");
private static final ApiUsageRecordKey[] JS_RECORD_KEYS = {JS_EXEC_COUNT}; private static final ApiUsageRecordKey[] JS_RECORD_KEYS = {JS_EXEC_COUNT};
private static final ApiUsageRecordKey[] RE_RECORD_KEYS = {RE_EXEC_COUNT}; private static final ApiUsageRecordKey[] RE_RECORD_KEYS = {RE_EXEC_COUNT};
private static final ApiUsageRecordKey[] DB_RECORD_KEYS = {STORAGE_DP_COUNT}; private static final ApiUsageRecordKey[] DB_RECORD_KEYS = {STORAGE_DP_COUNT};
private static final ApiUsageRecordKey[] TRANSPORT_RECORD_KEYS = {TRANSPORT_MSG_COUNT, TRANSPORT_DP_COUNT}; private static final ApiUsageRecordKey[] TRANSPORT_RECORD_KEYS = {TRANSPORT_MSG_COUNT, TRANSPORT_DP_COUNT};
private static final ApiUsageRecordKey[] EMAIL_RECORD_KEYS = {EMAIL_EXEC_COUNT}; private static final ApiUsageRecordKey[] EMAIL_RECORD_KEYS = {EMAIL_EXEC_COUNT};
private static final ApiUsageRecordKey[] SMS_RECORD_KEYS = {SMS_EXEC_COUNT}; private static final ApiUsageRecordKey[] SMS_RECORD_KEYS = {SMS_EXEC_COUNT};
private static final ApiUsageRecordKey[] ALARM_RECORD_KEYS = {CREATED_ALARMS_COUNT};
@Getter @Getter
private final ApiFeature apiFeature; private final ApiFeature apiFeature;
@ -60,6 +63,8 @@ public enum ApiUsageRecordKey {
return EMAIL_RECORD_KEYS; return EMAIL_RECORD_KEYS;
case SMS: case SMS:
return SMS_RECORD_KEYS; return SMS_RECORD_KEYS;
case ALARM:
return ALARM_RECORD_KEYS;
default: default:
return new ApiUsageRecordKey[]{}; return new ApiUsageRecordKey[]{};
} }

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

@ -25,34 +25,21 @@ import org.thingsboard.server.common.data.id.ApiUsageStateId;
@ToString @ToString
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@Getter
@Setter
public class ApiUsageState extends BaseData<ApiUsageStateId> implements HasTenantId { public class ApiUsageState extends BaseData<ApiUsageStateId> implements HasTenantId {
private static final long serialVersionUID = 8250339805336035966L; private static final long serialVersionUID = 8250339805336035966L;
@Getter
@Setter
private TenantId tenantId; private TenantId tenantId;
@Getter
@Setter
private EntityId entityId; private EntityId entityId;
@Getter
@Setter
private ApiUsageStateValue transportState; private ApiUsageStateValue transportState;
@Getter
@Setter
private ApiUsageStateValue dbStorageState; private ApiUsageStateValue dbStorageState;
@Getter
@Setter
private ApiUsageStateValue reExecState; private ApiUsageStateValue reExecState;
@Getter
@Setter
private ApiUsageStateValue jsExecState; private ApiUsageStateValue jsExecState;
@Getter
@Setter
private ApiUsageStateValue emailExecState; private ApiUsageStateValue emailExecState;
@Getter
@Setter
private ApiUsageStateValue smsExecState; private ApiUsageStateValue smsExecState;
private ApiUsageStateValue alarmExecState;
public ApiUsageState() { public ApiUsageState() {
super(); super();
@ -72,6 +59,7 @@ public class ApiUsageState extends BaseData<ApiUsageStateId> implements HasTenan
this.jsExecState = ur.getJsExecState(); this.jsExecState = ur.getJsExecState();
this.emailExecState = ur.getEmailExecState(); this.emailExecState = ur.getEmailExecState();
this.smsExecState = ur.getSmsExecState(); this.smsExecState = ur.getSmsExecState();
this.alarmExecState = ur.getAlarmExecState();
} }
public boolean isTransportEnabled() { public boolean isTransportEnabled() {
@ -97,4 +85,8 @@ public class ApiUsageState extends BaseData<ApiUsageStateId> implements HasTenan
public boolean isSmsSendEnabled(){ public boolean isSmsSendEnabled(){
return !ApiUsageStateValue.DISABLED.equals(smsExecState); return !ApiUsageStateValue.DISABLED.equals(smsExecState);
} }
public boolean isAlarmCreationEnabled() {
return alarmExecState != ApiUsageStateValue.DISABLED;
}
} }

10
common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java

@ -30,6 +30,7 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
private TenantId tenantId; private TenantId tenantId;
@NoXss @NoXss
private String title; private String title;
private String image;
@Valid @Valid
private Set<ShortCustomerInfo> assignedCustomers; private Set<ShortCustomerInfo> assignedCustomers;
@ -45,6 +46,7 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
super(dashboardInfo); super(dashboardInfo);
this.tenantId = dashboardInfo.getTenantId(); this.tenantId = dashboardInfo.getTenantId();
this.title = dashboardInfo.getTitle(); this.title = dashboardInfo.getTitle();
this.image = dashboardInfo.getImage();
this.assignedCustomers = dashboardInfo.getAssignedCustomers(); this.assignedCustomers = dashboardInfo.getAssignedCustomers();
} }
@ -64,6 +66,14 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
this.title = title; this.title = title;
} }
public String getImage() {
return image;
}
public void setImage(String image) {
this.image = image;
}
public Set<ShortCustomerInfo> getAssignedCustomers() { public Set<ShortCustomerInfo> getAssignedCustomers() {
return assignedCustomers; return assignedCustomers;
} }

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

@ -92,26 +92,6 @@ public class DataConstants {
public static final String CLIENT_ID = "clientId"; public static final String CLIENT_ID = "clientId";
public static final String USERNAME = "username"; public static final String USERNAME = "username";
public static final String PASSWORD = "password"; public static final String PASSWORD = "password";
//<<<<<<< HEAD
//=======
// //firmware
// //telemetry
// public static final String CURRENT_FIRMWARE_TITLE = "current_fw_title";
// public static final String CURRENT_FIRMWARE_VERSION = "current_fw_version";
// public static final String TARGET_FIRMWARE_TITLE = "target_fw_title";
// public static final String TARGET_FIRMWARE_VERSION = "target_fw_version";
// public static final String TARGET_FIRMWARE_TS = "target_fw_ts";
// public static final String FIRMWARE_STATE = "fw_state";
//
// //attributes
// //telemetry
// public static final String FIRMWARE_TITLE = "fw_title";
// public static final String FIRMWARE_VERSION = "fw_version";
// public static final String FIRMWARE_SIZE = "fw_size";
// public static final String FIRMWARE_CHECKSUM = "fw_checksum";
// public static final String FIRMWARE_CHECKSUM_ALGORITHM = "fw_checksum_algorithm";
//>>>>>>> origin/master
public static final String EDGE_MSG_SOURCE = "edge"; public static final String EDGE_MSG_SOURCE = "edge";
public static final String MSG_SOURCE_KEY = "source"; public static final String MSG_SOURCE_KEY = "source";

5
common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java

@ -21,6 +21,7 @@ import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData; import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.FirmwareId; import org.thingsboard.server.common.data.id.FirmwareId;
import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleChainId;
@ -43,11 +44,13 @@ public class DeviceProfile extends SearchTextBased<DeviceProfileId> implements H
private String name; private String name;
@NoXss @NoXss
private String description; private String description;
private String image;
private boolean isDefault; private boolean isDefault;
private DeviceProfileType type; private DeviceProfileType type;
private DeviceTransportType transportType; private DeviceTransportType transportType;
private DeviceProfileProvisionType provisionType; private DeviceProfileProvisionType provisionType;
private RuleChainId defaultRuleChainId; private RuleChainId defaultRuleChainId;
private DashboardId defaultDashboardId;
@NoXss @NoXss
private String defaultQueueName; private String defaultQueueName;
@Valid @Valid
@ -74,8 +77,10 @@ public class DeviceProfile extends SearchTextBased<DeviceProfileId> implements H
this.tenantId = deviceProfile.getTenantId(); this.tenantId = deviceProfile.getTenantId();
this.name = deviceProfile.getName(); this.name = deviceProfile.getName();
this.description = deviceProfile.getDescription(); this.description = deviceProfile.getDescription();
this.image = deviceProfile.getImage();
this.isDefault = deviceProfile.isDefault(); this.isDefault = deviceProfile.isDefault();
this.defaultRuleChainId = deviceProfile.getDefaultRuleChainId(); this.defaultRuleChainId = deviceProfile.getDefaultRuleChainId();
this.defaultDashboardId = deviceProfile.getDefaultDashboardId();
this.defaultQueueName = deviceProfile.getDefaultQueueName(); this.defaultQueueName = deviceProfile.getDefaultQueueName();
this.setProfileData(deviceProfile.getProfileData()); this.setProfileData(deviceProfile.getProfileData());
this.provisionDeviceKey = deviceProfile.getProvisionDeviceKey(); this.provisionDeviceKey = deviceProfile.getProvisionDeviceKey();

11
common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileInfo.java

@ -20,6 +20,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.ToString; import lombok.ToString;
import lombok.Value; import lombok.Value;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.EntityIdFactory;
@ -30,21 +31,29 @@ import java.util.UUID;
@ToString(callSuper = true) @ToString(callSuper = true)
public class DeviceProfileInfo extends EntityInfo { public class DeviceProfileInfo extends EntityInfo {
private final String image;
private final DashboardId defaultDashboardId;
private final DeviceProfileType type; private final DeviceProfileType type;
private final DeviceTransportType transportType; private final DeviceTransportType transportType;
@JsonCreator @JsonCreator
public DeviceProfileInfo(@JsonProperty("id") EntityId id, public DeviceProfileInfo(@JsonProperty("id") EntityId id,
@JsonProperty("name") String name, @JsonProperty("name") String name,
@JsonProperty("image") String image,
@JsonProperty("defaultDashboardId") DashboardId defaultDashboardId,
@JsonProperty("type") DeviceProfileType type, @JsonProperty("type") DeviceProfileType type,
@JsonProperty("transportType") DeviceTransportType transportType) { @JsonProperty("transportType") DeviceTransportType transportType) {
super(id, name); super(id, name);
this.image = image;
this.defaultDashboardId = defaultDashboardId;
this.type = type; this.type = type;
this.transportType = transportType; this.transportType = transportType;
} }
public DeviceProfileInfo(UUID uuid, String name, DeviceProfileType type, DeviceTransportType transportType) { public DeviceProfileInfo(UUID uuid, String name, String image, UUID defaultDashboardId, DeviceProfileType type, DeviceTransportType transportType) {
super(EntityIdFactory.getByTypeAndUuid(EntityType.DEVICE_PROFILE, uuid), name); super(EntityIdFactory.getByTypeAndUuid(EntityType.DEVICE_PROFILE, uuid), name);
this.image = image;
this.defaultDashboardId = defaultDashboardId != null ? new DashboardId(defaultDashboardId) : null;
this.type = type; this.type = type;
this.transportType = transportType; this.transportType = transportType;
} }

6
common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java

@ -21,9 +21,11 @@ import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.HasCustomerId;
import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
@ -35,9 +37,10 @@ import java.util.List;
@Data @Data
@Builder @Builder
@AllArgsConstructor @AllArgsConstructor
public class Alarm extends BaseData<AlarmId> implements HasName, HasTenantId { public class Alarm extends BaseData<AlarmId> implements HasName, HasTenantId, HasCustomerId {
private TenantId tenantId; private TenantId tenantId;
private CustomerId customerId;
private String type; private String type;
private EntityId originator; private EntityId originator;
private AlarmSeverity severity; private AlarmSeverity severity;
@ -62,6 +65,7 @@ public class Alarm extends BaseData<AlarmId> implements HasName, HasTenantId {
super(alarm.getId()); super(alarm.getId());
this.createdTime = alarm.getCreatedTime(); this.createdTime = alarm.getCreatedTime();
this.tenantId = alarm.getTenantId(); this.tenantId = alarm.getTenantId();
this.customerId = alarm.getCustomerId();
this.type = alarm.getType(); this.type = alarm.getType();
this.originator = alarm.getOriginator(); this.originator = alarm.getOriginator();
this.severity = alarm.getSeverity(); this.severity = alarm.getSeverity();

2
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java

@ -82,7 +82,7 @@ public class MqttTopics {
public static final String DEVICE_FIRMWARE_REQUEST_TOPIC_PATTERN = BASE_DEVICE_API_TOPIC_V2 + FIRMWARE + REQUEST + "/" + REQUEST_ID_PATTERN + CHUNK + CHUNK_PATTERN; public static final String DEVICE_FIRMWARE_REQUEST_TOPIC_PATTERN = BASE_DEVICE_API_TOPIC_V2 + FIRMWARE + REQUEST + "/" + REQUEST_ID_PATTERN + CHUNK + CHUNK_PATTERN;
public static final String DEVICE_FIRMWARE_RESPONSES_TOPIC = BASE_DEVICE_API_TOPIC_V2 + FIRMWARE + RESPONSE + "/" + SUB_TOPIC + CHUNK + SUB_TOPIC; public static final String DEVICE_FIRMWARE_RESPONSES_TOPIC = BASE_DEVICE_API_TOPIC_V2 + FIRMWARE + RESPONSE + "/" + SUB_TOPIC + CHUNK + SUB_TOPIC;
public static final String DEVICE_FIRMWARE_ERROR_TOPIC = BASE_DEVICE_API_TOPIC_V2 + FIRMWARE + ERROR; public static final String DEVICE_FIRMWARE_ERROR_TOPIC = BASE_DEVICE_API_TOPIC_V2 + FIRMWARE + ERROR;
public static final String DEVICE_FIRMWARE_RESPONSES_TOPIC_FORMAT = BASE_DEVICE_API_TOPIC_V2 + "%s" + RESPONSE + "/"+ "%s" + CHUNK + "%d"; public static final String DEVICE_FIRMWARE_RESPONSES_TOPIC_FORMAT = BASE_DEVICE_API_TOPIC_V2 + "/%s" + RESPONSE + "/"+ "%s" + CHUNK + "%d";
public static final String DEVICE_SOFTWARE_REQUEST_TOPIC_PATTERN = BASE_DEVICE_API_TOPIC_V2 + SOFTWARE + REQUEST + "/" + REQUEST_ID_PATTERN + CHUNK + CHUNK_PATTERN; public static final String DEVICE_SOFTWARE_REQUEST_TOPIC_PATTERN = BASE_DEVICE_API_TOPIC_V2 + SOFTWARE + REQUEST + "/" + REQUEST_ID_PATTERN + CHUNK + CHUNK_PATTERN;
public static final String DEVICE_SOFTWARE_RESPONSES_TOPIC = BASE_DEVICE_API_TOPIC_V2 + SOFTWARE + RESPONSE + "/" + SUB_TOPIC + CHUNK + SUB_TOPIC; public static final String DEVICE_SOFTWARE_RESPONSES_TOPIC = BASE_DEVICE_API_TOPIC_V2 + SOFTWARE + RESPONSE + "/" + SUB_TOPIC + CHUNK + SUB_TOPIC;

2
application/src/main/java/org/thingsboard/server/service/firmware/FirmwareUpdateStatus.java → common/data/src/main/java/org/thingsboard/server/common/data/firmware/FirmwareUpdateStatus.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.server.service.firmware; package org.thingsboard.server.common.data.firmware;
public enum FirmwareUpdateStatus { public enum FirmwareUpdateStatus {
QUEUED, INITIATED, DOWNLOADING, DOWNLOADED, VERIFIED, UPDATING, UPDATED, FAILED QUEUED, INITIATED, DOWNLOADING, DOWNLOADED, VERIFIED, UPDATING, UPDATED, FAILED

3
common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java

@ -50,6 +50,7 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura
private int maxRuleNodeExecutionsPerMessage; private int maxRuleNodeExecutionsPerMessage;
private long maxEmails; private long maxEmails;
private long maxSms; private long maxSms;
private long maxCreatedAlarms;
private int defaultStorageTtlDays; private int defaultStorageTtlDays;
@ -72,6 +73,8 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura
return maxEmails; return maxEmails;
case SMS_EXEC_COUNT: case SMS_EXEC_COUNT:
return maxSms; return maxSms;
case CREATED_ALARMS_COUNT:
return maxCreatedAlarms;
} }
return 0L; return 0L;
} }

4
common/transport/lwm2m/pom.xml

@ -52,6 +52,10 @@
<groupId>org.springframework</groupId> <groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId> <artifactId>spring-context</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-redis</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.slf4j</groupId> <groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId> <artifactId>slf4j-api</artifactId>

5
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java

@ -46,6 +46,7 @@ import java.util.UUID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.BOOTSTRAP_SERVER; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.BOOTSTRAP_SERVER;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_ERROR; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_ERROR;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_INFO; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_INFO;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_TELEMETRY;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LWM2M_SERVER; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LWM2M_SERVER;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SERVERS; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SERVERS;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.getBootstrapParametersFromThingsboard; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.getBootstrapParametersFromThingsboard;
@ -165,13 +166,13 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
lwM2MBootstrapConfig.bootstrapServer = new LwM2MServerBootstrap(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap); lwM2MBootstrapConfig.bootstrapServer = new LwM2MServerBootstrap(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap);
lwM2MBootstrapConfig.lwm2mServer = new LwM2MServerBootstrap(lwM2MBootstrapConfig.lwm2mServer, profileLwm2mServer); lwM2MBootstrapConfig.lwm2mServer = new LwM2MServerBootstrap(lwM2MBootstrapConfig.lwm2mServer, profileLwm2mServer);
String logMsg = String.format("%s: getParametersBootstrap: %s Access connect client with bootstrap server.", LOG_LW2M_INFO, store.getEndpoint()); String logMsg = String.format("%s: getParametersBootstrap: %s Access connect client with bootstrap server.", LOG_LW2M_INFO, store.getEndpoint());
helper.sendParametersOnThingsboardTelemetry(helper.getKvLogyToThingsboard(logMsg), sessionInfo); helper.sendParametersOnThingsboardTelemetry(helper.getKvStringtoThingsboard(LOG_LW2M_TELEMETRY, logMsg), sessionInfo);
return lwM2MBootstrapConfig; return lwM2MBootstrapConfig;
} else { } else {
log.error(" [{}] Different values SecurityMode between of client and profile.", store.getEndpoint()); log.error(" [{}] Different values SecurityMode between of client and profile.", store.getEndpoint());
log.error("{} getParametersBootstrap: [{}] Different values SecurityMode between of client and profile.", LOG_LW2M_ERROR, store.getEndpoint()); log.error("{} getParametersBootstrap: [{}] Different values SecurityMode between of client and profile.", LOG_LW2M_ERROR, store.getEndpoint());
String logMsg = String.format("%s: getParametersBootstrap: %s Different values SecurityMode between of client and profile.", LOG_LW2M_ERROR, store.getEndpoint()); String logMsg = String.format("%s: getParametersBootstrap: %s Different values SecurityMode between of client and profile.", LOG_LW2M_ERROR, store.getEndpoint());
helper.sendParametersOnThingsboardTelemetry(helper.getKvLogyToThingsboard(logMsg), sessionInfo); helper.sendParametersOnThingsboardTelemetry(helper.getKvStringtoThingsboard(LOG_LW2M_TELEMETRY, logMsg), sessionInfo);
return null; return null;
} }
} }

8
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java

@ -64,6 +64,14 @@ public class LwM2MTransportServerConfig implements LwM2MSecureServerConfig {
@Value("${transport.lwm2m.registered_pool_size:}") @Value("${transport.lwm2m.registered_pool_size:}")
private int registeredPoolSize; private int registeredPoolSize;
@Getter
@Value("${transport.lwm2m.registration_store_pool_size:}")
private int registrationStorePoolSize;
@Getter
@Value("${transport.lwm2m.clean_period_in_sec:}")
private int cleanPeriodInSec;
@Getter @Getter
@Value("${transport.lwm2m.update_registered_pool_size:}") @Value("${transport.lwm2m.update_registered_pool_size:}")
private int updateRegisteredPoolSize; private int updateRegisteredPoolSize;

358
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MTransportMsgHandler.java

@ -23,13 +23,12 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.model.ObjectModel;
import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.model.ResourceModel;
import org.eclipse.leshan.core.node.LwM2mMultipleResource;
import org.eclipse.leshan.core.node.LwM2mObject; import org.eclipse.leshan.core.node.LwM2mObject;
import org.eclipse.leshan.core.node.LwM2mObjectInstance; import org.eclipse.leshan.core.node.LwM2mObjectInstance;
import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.LwM2mPath;
import org.eclipse.leshan.core.node.LwM2mResource; import org.eclipse.leshan.core.node.LwM2mResource;
import org.eclipse.leshan.core.node.LwM2mSingleResource;
import org.eclipse.leshan.core.observation.Observation; import org.eclipse.leshan.core.observation.Observation;
import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.core.request.ContentFormat;
import org.eclipse.leshan.core.request.WriteRequest; import org.eclipse.leshan.core.request.WriteRequest;
@ -56,6 +55,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.SessionEvent;
import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto;
import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper;
import org.thingsboard.server.transport.lwm2m.server.adaptors.LwM2MJsonAdaptor; import org.thingsboard.server.transport.lwm2m.server.adaptors.LwM2MJsonAdaptor;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext;
@ -85,19 +85,21 @@ import java.util.stream.Collectors;
import static org.eclipse.californium.core.coap.CoAP.ResponseCode.BAD_REQUEST; import static org.eclipse.californium.core.coap.CoAP.ResponseCode.BAD_REQUEST;
import static org.eclipse.leshan.core.attributes.Attribute.OBJECT_VERSION; import static org.eclipse.leshan.core.attributes.Attribute.OBJECT_VERSION;
import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_KEY; import static org.thingsboard.server.common.data.firmware.FirmwareUpdateStatus.DOWNLOADED;
import static org.thingsboard.server.common.data.firmware.FirmwareUpdateStatus.UPDATING;
import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH; import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper.getValueFromKvProto; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper.getValueFromKvProto;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.CLIENT_NOT_AUTHORIZED; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.CLIENT_NOT_AUTHORIZED;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.DEVICE_ATTRIBUTES_REQUEST; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.DEVICE_ATTRIBUTES_REQUEST;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FR_OBJECT_ID; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FR_PATH_RESOURCE_VER_ID; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_RESULT_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_ERROR; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_ERROR;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_INFO; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_INFO;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_TELEMETRY;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_VALUE; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_VALUE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LWM2M_STRATEGY_2; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LWM2M_STRATEGY_2;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.DISCOVER; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.DISCOVER;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.DISCOVER_All;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.EXECUTE; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.EXECUTE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.OBSERVE; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.OBSERVE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.OBSERVE_CANCEL; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.OBSERVE_CANCEL;
@ -105,6 +107,9 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.L
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.READ; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.READ;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.WRITE_ATTRIBUTES; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.WRITE_ATTRIBUTES;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.WRITE_REPLACE; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.WRITE_REPLACE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.WRITE_UPDATE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_RESULT_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertJsonArrayToSet; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertJsonArrayToSet;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromIdVerToObjectId; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromIdVerToObjectId;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromObjectIdToIdVer; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromObjectIdToIdVer;
@ -119,19 +124,20 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler
private ExecutorService registrationExecutor; private ExecutorService registrationExecutor;
private ExecutorService updateRegistrationExecutor; private ExecutorService updateRegistrationExecutor;
private ExecutorService unregistrationExecutor; private ExecutorService unRegistrationExecutor;
private LwM2mValueConverterImpl converter; private LwM2mValueConverterImpl converter;
private final TransportService transportService; private final TransportService transportService;
private final LwM2mTransportContext context; private final LwM2mTransportContext context;
private final LwM2MTransportServerConfig config; public final LwM2MTransportServerConfig config;
private final FirmwareDataCache firmwareDataCache; public final FirmwareDataCache firmwareDataCache;
private final LwM2mTransportServerHelper helper; public final LwM2mTransportServerHelper helper;
private final LwM2MJsonAdaptor adaptor; private final LwM2MJsonAdaptor adaptor;
private final LwM2mClientContext clientContext; private final LwM2mClientContext clientContext;
private final LwM2mTransportRequest lwM2mTransportRequest; private final LwM2mTransportRequest lwM2mTransportRequest;
private final TbLwM2MDtlsSessionStore sessionStore; private final TbLwM2MDtlsSessionStore sessionStore;
public DefaultLwM2MTransportMsgHandler(TransportService transportService, LwM2MTransportServerConfig config, LwM2mTransportServerHelper helper, public DefaultLwM2MTransportMsgHandler(TransportService transportService, LwM2MTransportServerConfig config, LwM2mTransportServerHelper helper,
LwM2mClientContext clientContext, LwM2mClientContext clientContext,
@Lazy LwM2mTransportRequest lwM2mTransportRequest, @Lazy LwM2mTransportRequest lwM2mTransportRequest,
@ -153,7 +159,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler
this.context.getScheduler().scheduleAtFixedRate(this::reportActivity, new Random().nextInt((int) config.getSessionReportTimeout()), config.getSessionReportTimeout(), TimeUnit.MILLISECONDS); this.context.getScheduler().scheduleAtFixedRate(this::reportActivity, new Random().nextInt((int) config.getSessionReportTimeout()), config.getSessionReportTimeout(), TimeUnit.MILLISECONDS);
this.registrationExecutor = ThingsBoardExecutors.newWorkStealingPool(this.config.getRegisteredPoolSize(), "LwM2M registration"); this.registrationExecutor = ThingsBoardExecutors.newWorkStealingPool(this.config.getRegisteredPoolSize(), "LwM2M registration");
this.updateRegistrationExecutor = ThingsBoardExecutors.newWorkStealingPool(this.config.getUpdateRegisteredPoolSize(), "LwM2M update registration"); this.updateRegistrationExecutor = ThingsBoardExecutors.newWorkStealingPool(this.config.getUpdateRegisteredPoolSize(), "LwM2M update registration");
this.unregistrationExecutor = ThingsBoardExecutors.newWorkStealingPool(this.config.getUnRegisteredPoolSize(), "LwM2M unregistration"); this.unRegistrationExecutor = ThingsBoardExecutors.newWorkStealingPool(this.config.getUnRegisteredPoolSize(), "LwM2M unRegistration");
this.converter = LwM2mValueConverterImpl.getInstance(); this.converter = LwM2mValueConverterImpl.getInstance();
} }
@ -184,6 +190,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler
transportService.process(sessionInfo, TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().build(), null); transportService.process(sessionInfo, TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().build(), null);
transportService.process(sessionInfo, TransportProtos.SubscribeToRPCMsg.newBuilder().build(), null); transportService.process(sessionInfo, TransportProtos.SubscribeToRPCMsg.newBuilder().build(), null);
this.getInfoFirmwareUpdate(lwM2MClient); this.getInfoFirmwareUpdate(lwM2MClient);
this.getInfoSoftwareUpdate(lwM2MClient);
this.initLwM2mFromClientValue(registration, lwM2MClient); this.initLwM2mFromClientValue(registration, lwM2MClient);
this.sendLogsToThingsboard(LOG_LW2M_INFO + ": Client create after Registration", registration.getId()); this.sendLogsToThingsboard(LOG_LW2M_INFO + ": Client create after Registration", registration.getId());
} else { } else {
@ -216,11 +223,14 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler
request.send(); request.send();
} }
} }
this.sendLogsToThingsboard(LOG_LW2M_INFO + ": Client update Registration", registration.getId());
} else { } else {
log.error("Client: [{}] updatedReg [{}] name [{}] sessionInfo ", registration.getId(), registration.getEndpoint(), null); log.error("Client: [{}] updatedReg [{}] name [{}] sessionInfo ", registration.getId(), registration.getEndpoint(), null);
this.sendLogsToThingsboard(LOG_LW2M_ERROR + ": Client update Registration", registration.getId());
} }
} catch (Throwable t) { } catch (Throwable t) {
log.error("[{}] endpoint [{}] error Unable update registration.", registration.getEndpoint(), t); log.error("[{}] endpoint [{}] error Unable update registration.", registration.getEndpoint(), t);
this.sendLogsToThingsboard(LOG_LW2M_ERROR + String.format(": Client update Registration, %s", t.getMessage()), registration.getId());
} }
}); });
} }
@ -231,13 +241,15 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler
* !!! Warn: if have not finishing unReg, then this operation will be finished on next Client`s connect * !!! Warn: if have not finishing unReg, then this operation will be finished on next Client`s connect
*/ */
public void unReg(Registration registration, Collection<Observation> observations) { public void unReg(Registration registration, Collection<Observation> observations) {
unregistrationExecutor.submit(() -> { unRegistrationExecutor.submit(() -> {
try { try {
this.setCancelObservations(registration); this.setCancelObservationsAll(registration);
this.sendLogsToThingsboard(LOG_LW2M_INFO + ": Client unRegistration", registration.getId()); this.sendLogsToThingsboard(LOG_LW2M_INFO + ": Client unRegistration", registration.getId());
this.closeClientSession(registration); this.closeClientSession(registration);
;
} catch (Throwable t) { } catch (Throwable t) {
log.error("[{}] endpoint [{}] error Unable un registration.", registration.getEndpoint(), t); log.error("[{}] endpoint [{}] error Unable un registration.", registration.getEndpoint(), t);
this.sendLogsToThingsboard(LOG_LW2M_ERROR + String.format(": Client Unable un Registration, %s", t.getMessage()), registration.getId());
} }
}); });
} }
@ -263,13 +275,18 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler
//TODO: associate endpointId with device information. //TODO: associate endpointId with device information.
} }
/**
* Cancel observation for All objects for this registration
*/
@Override @Override
public void setCancelObservations(Registration registration) { public void setCancelObservationsAll(Registration registration) {
if (registration != null) { if (registration != null) {
Set<Observation> observations = context.getServer().getObservationService().getObservations(registration); lwM2mTransportRequest.sendAllRequest(registration, null, OBSERVE_CANCEL,
observations.forEach(observation -> lwM2mTransportRequest.sendAllRequest(registration, null, null, this.config.getTimeout(), null);
convertPathFromObjectIdToIdVer(observation.getPath().toString(), registration), OBSERVE_CANCEL, // Set<Observation> observations = context.getServer().getObservationService().getObservations(registration);
null, null, this.config.getTimeout(), null)); // observations.forEach(observation -> lwM2mTransportRequest.sendAllRequest(registration,
// convertPathFromObjectIdToIdVer(observation.getPath().toString(), registration), OBSERVE_CANCEL,
// null, null, this.config.getTimeout(), null));
} }
} }
@ -283,34 +300,43 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler
@Override @Override
public void onUpdateValueAfterReadResponse(Registration registration, String path, ReadResponse response, Lwm2mClientRpcRequest rpcRequest) { public void onUpdateValueAfterReadResponse(Registration registration, String path, ReadResponse response, Lwm2mClientRpcRequest rpcRequest) {
if (response.getContent() != null) { if (response.getContent() != null) {
Object value = null; LwM2mClient lwM2MClient = clientContext.getOrRegister(registration);
if (response.getContent() instanceof LwM2mObject) { ObjectModel objectModelVersion = lwM2MClient.getObjectModel(path, this.config.getModelProvider());
LwM2mObject lwM2mObject = (LwM2mObject) response.getContent(); if (objectModelVersion != null) {
if (rpcRequest != null) { if (response.getContent() instanceof LwM2mObject) {
value = lwM2mObject.toString(); LwM2mObject lwM2mObject = (LwM2mObject) response.getContent();
this.updateObjectResourceValue(registration, lwM2mObject, path);
} else if (response.getContent() instanceof LwM2mObjectInstance) {
LwM2mObjectInstance lwM2mObjectInstance = (LwM2mObjectInstance) response.getContent();
this.updateObjectInstanceResourceValue(registration, lwM2mObjectInstance, path);
} else if (response.getContent() instanceof LwM2mResource) {
LwM2mResource lwM2mResource = (LwM2mResource) response.getContent();
this.updateResourcesValue(registration, lwM2mResource, path);
} }
this.updateObjectResourceValue(registration, lwM2mObject, path);
} else if (response.getContent() instanceof LwM2mObjectInstance) {
LwM2mObjectInstance lwM2mObjectInstance = (LwM2mObjectInstance) response.getContent();
if (rpcRequest != null) {
value = lwM2mObjectInstance.toString();
}
this.updateObjectInstanceResourceValue(registration, lwM2mObjectInstance, path);
} else if (response.getContent() instanceof LwM2mResource) {
LwM2mResource lwM2mResource = (LwM2mResource) response.getContent();
if (rpcRequest != null) {
value = lwM2mResource.isMultiInstances() ? ((LwM2mMultipleResource) lwM2mResource).toString() :
((LwM2mSingleResource) lwM2mResource).toString();
}
this.updateResourcesValue(registration, lwM2mResource, path);
} }
if (rpcRequest != null) { if (rpcRequest != null) {
rpcRequest.setValueMsg(String.format("%s", value)); this.sendRpcRequestAfterReadResponse(registration, lwM2MClient, path, response, rpcRequest);
this.sentRpcRequest(rpcRequest, response.getCode().getName(), (String) value, LOG_LW2M_VALUE);
} }
} }
} }
private void sendRpcRequestAfterReadResponse(Registration registration, LwM2mClient lwM2MClient, String pathIdVer, ReadResponse response,
Lwm2mClientRpcRequest rpcRequest) {
Object value = null;
if (response.getContent() instanceof LwM2mObject) {
value = lwM2MClient.objectToString((LwM2mObject) response.getContent(), this.converter, pathIdVer);
} else if (response.getContent() instanceof LwM2mObjectInstance) {
value = lwM2MClient.instanceToString((LwM2mObjectInstance) response.getContent(), this.converter, pathIdVer);
} else if (response.getContent() instanceof LwM2mResource) {
value = lwM2MClient.resourceToString((LwM2mResource) response.getContent(), this.converter, pathIdVer);
}
String msg = String.format("%s: type operation %s path - %s value - %s", LOG_LW2M_INFO,
READ, pathIdVer, value);
this.sendLogsToThingsboard(msg, registration.getId());
rpcRequest.setValueMsg(String.format("%s", value));
this.sentRpcRequest(rpcRequest, response.getCode().getName(), (String) value, LOG_LW2M_VALUE);
}
/** /**
* Update - send request in change value resources in Client * Update - send request in change value resources in Client
* 1. FirmwareUpdate: * 1. FirmwareUpdate:
@ -330,9 +356,16 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler
String pathName = tsKvProto.getKv().getKey(); String pathName = tsKvProto.getKv().getKey();
String pathIdVer = this.getPresentPathIntoProfile(sessionInfo, pathName); String pathIdVer = this.getPresentPathIntoProfile(sessionInfo, pathName);
Object valueNew = getValueFromKvProto(tsKvProto.getKv()); Object valueNew = getValueFromKvProto(tsKvProto.getKv());
//TODO: react on change of the firmware name. if ((FirmwareUtil.getAttributeKey(FirmwareType.FIRMWARE, FirmwareKey.VERSION).equals(pathName)
if (FirmwareUtil.getAttributeKey(FirmwareType.FIRMWARE, FirmwareKey.VERSION).equals(pathName) && !valueNew.equals(lwM2MClient.getFrUpdate().getCurrentFwVersion())) { && (!valueNew.equals(lwM2MClient.getFwUpdate().getCurrentVersion())))
|| (FirmwareUtil.getAttributeKey(FirmwareType.FIRMWARE, FirmwareKey.TITLE).equals(pathName)
&& (!valueNew.equals(lwM2MClient.getFwUpdate().getCurrentTitle())))) {
this.getInfoFirmwareUpdate(lwM2MClient); this.getInfoFirmwareUpdate(lwM2MClient);
} else if ((FirmwareUtil.getAttributeKey(FirmwareType.SOFTWARE, FirmwareKey.VERSION).equals(pathName)
&& (!valueNew.equals(lwM2MClient.getSwUpdate().getCurrentVersion())))
|| (FirmwareUtil.getAttributeKey(FirmwareType.SOFTWARE, FirmwareKey.TITLE).equals(pathName)
&& (!valueNew.equals(lwM2MClient.getSwUpdate().getCurrentTitle())))) {
this.getInfoSoftwareUpdate(lwM2MClient);
} }
if (pathIdVer != null) { if (pathIdVer != null) {
ResourceModel resourceModel = lwM2MClient.getResourceModel(pathIdVer, this.config ResourceModel resourceModel = lwM2MClient.getResourceModel(pathIdVer, this.config
@ -357,8 +390,8 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler
msg.getSharedUpdatedList().forEach(tsKvProto -> { msg.getSharedUpdatedList().forEach(tsKvProto -> {
String pathName = tsKvProto.getKv().getKey(); String pathName = tsKvProto.getKv().getKey();
Object valueNew = getValueFromKvProto(tsKvProto.getKv()); Object valueNew = getValueFromKvProto(tsKvProto.getKv());
if (FirmwareUtil.getAttributeKey(FirmwareType.FIRMWARE, FirmwareKey.VERSION).equals(pathName) && !valueNew.equals(lwM2MClient.getFrUpdate().getCurrentFwVersion())) { if (FirmwareUtil.getAttributeKey(FirmwareType.FIRMWARE, FirmwareKey.VERSION).equals(pathName) && !valueNew.equals(lwM2MClient.getFwUpdate().getCurrentVersion())) {
lwM2MClient.getFrUpdate().setCurrentFwVersion((String) valueNew); lwM2MClient.getFwUpdate().setCurrentVersion((String) valueNew);
} }
}); });
log.info("[{}] delete [{}] onAttributeUpdate", msg.getSharedDeletedList(), sessionInfo); log.info("[{}] delete [{}] onAttributeUpdate", msg.getSharedDeletedList(), sessionInfo);
@ -402,12 +435,12 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler
} }
@Override @Override
public void onToDeviceRpcRequest(TransportProtos.ToDeviceRpcRequestMsg toDeviceRequest, SessionInfoProto sessionInfo) { public void onToDeviceRpcRequest(TransportProtos.ToDeviceRpcRequestMsg toDeviceRpcRequestMsg, SessionInfoProto sessionInfo) {
log.warn("4) RPC-OK finish to [{}]", toDeviceRpcRequestMsg);
Lwm2mClientRpcRequest lwm2mClientRpcRequest = null; Lwm2mClientRpcRequest lwm2mClientRpcRequest = null;
try { try {
log.info("[{}] toDeviceRpcRequest", toDeviceRequest);
Registration registration = clientContext.getClient(new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB())).getRegistration(); Registration registration = clientContext.getClient(new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB())).getRegistration();
lwm2mClientRpcRequest = this.getDeviceRpcRequest(toDeviceRequest, sessionInfo, registration); lwm2mClientRpcRequest = this.getDeviceRpcRequest(toDeviceRpcRequestMsg, sessionInfo, registration);
if (lwm2mClientRpcRequest.getErrorMsg() != null) { if (lwm2mClientRpcRequest.getErrorMsg() != null) {
lwm2mClientRpcRequest.setResponseCode(BAD_REQUEST.name()); lwm2mClientRpcRequest.setResponseCode(BAD_REQUEST.name());
this.onToDeviceRpcResponse(lwm2mClientRpcRequest.getDeviceRpcResponseResultMsg(), sessionInfo); this.onToDeviceRpcResponse(lwm2mClientRpcRequest.getDeviceRpcResponseResultMsg(), sessionInfo);
@ -459,12 +492,27 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler
lwm2mClientRpcRequest.setValue(rpcRequest.get(lwm2mClientRpcRequest.valueKey).getAsString()); lwm2mClientRpcRequest.setValue(rpcRequest.get(lwm2mClientRpcRequest.valueKey).getAsString());
} }
if (rpcRequest.has(lwm2mClientRpcRequest.paramsKey) && rpcRequest.get(lwm2mClientRpcRequest.paramsKey).isJsonObject()) { if (rpcRequest.has(lwm2mClientRpcRequest.paramsKey) && rpcRequest.get(lwm2mClientRpcRequest.paramsKey).isJsonObject()) {
lwm2mClientRpcRequest.setParams(new Gson().fromJson(rpcRequest.get(lwm2mClientRpcRequest.paramsKey) ConcurrentHashMap<String, Object> params = new Gson().fromJson(rpcRequest.get(lwm2mClientRpcRequest.paramsKey)
.getAsJsonObject().toString(), new TypeToken<ConcurrentHashMap<String, Object>>() {
}.getType());
if (WRITE_UPDATE == lwm2mClientRpcRequest.getTypeOper()) {
ConcurrentHashMap<String, Object> paramsResourceId = convertParamsToResourceId(params, sessionInfo);
if (paramsResourceId.size() > 0) {
lwm2mClientRpcRequest.setParams(paramsResourceId);
}
} else {
lwm2mClientRpcRequest.setParams(params);
}
} else if (rpcRequest.has(lwm2mClientRpcRequest.paramsKey) && rpcRequest.get(lwm2mClientRpcRequest.paramsKey).isJsonArray()) {
new Gson().fromJson(rpcRequest.get(lwm2mClientRpcRequest.paramsKey)
.getAsJsonObject().toString(), new TypeToken<ConcurrentHashMap<String, Object>>() { .getAsJsonObject().toString(), new TypeToken<ConcurrentHashMap<String, Object>>() {
}.getType())); }.getType());
} }
lwm2mClientRpcRequest.setSessionInfo(sessionInfo); lwm2mClientRpcRequest.setSessionInfo(sessionInfo);
if (OBSERVE_READ_ALL != lwm2mClientRpcRequest.getTypeOper() && lwm2mClientRpcRequest.getTargetIdVer() == null) { if (!(OBSERVE_READ_ALL == lwm2mClientRpcRequest.getTypeOper()
|| DISCOVER_All == lwm2mClientRpcRequest.getTypeOper()
|| OBSERVE_CANCEL == lwm2mClientRpcRequest.getTypeOper())
&& lwm2mClientRpcRequest.getTargetIdVer() == null) {
lwm2mClientRpcRequest.setErrorMsg(lwm2mClientRpcRequest.targetIdVerKey + " and " + lwm2mClientRpcRequest.setErrorMsg(lwm2mClientRpcRequest.targetIdVerKey + " and " +
lwm2mClientRpcRequest.keyNameKey + " is null or bad format"); lwm2mClientRpcRequest.keyNameKey + " is null or bad format");
} }
@ -488,6 +536,21 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler
return lwm2mClientRpcRequest; return lwm2mClientRpcRequest;
} }
private ConcurrentHashMap<String, Object> convertParamsToResourceId(ConcurrentHashMap<String, Object> params,
SessionInfoProto sessionInfo) {
ConcurrentHashMap<String, Object> paramsIdVer = new ConcurrentHashMap<>();
params.forEach((k, v) -> {
String targetIdVer = this.getPresentPathIntoProfile(sessionInfo, k);
if (targetIdVer != null) {
LwM2mPath targetId = new LwM2mPath(convertPathFromIdVerToObjectId(targetIdVer));
if (targetId.isResource()) {
paramsIdVer.put(String.valueOf(targetId.getResourceId()), v);
}
}
});
return paramsIdVer;
}
public void sentRpcRequest(Lwm2mClientRpcRequest rpcRequest, String requestCode, String msg, String typeMsg) { public void sentRpcRequest(Lwm2mClientRpcRequest rpcRequest, String requestCode, String msg, String typeMsg) {
rpcRequest.setResponseCode(requestCode); rpcRequest.setResponseCode(requestCode);
if (LOG_LW2M_ERROR.equals(typeMsg)) { if (LOG_LW2M_ERROR.equals(typeMsg)) {
@ -561,7 +624,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler
*/ */
@Override @Override
public void onAwakeDev(Registration registration) { public void onAwakeDev(Registration registration) {
log.info("[{}] [{}] Received endpoint Awake version event", registration.getId(), registration.getEndpoint()); log.trace("[{}] [{}] Received endpoint Awake version event", registration.getId(), registration.getEndpoint());
this.sendLogsToThingsboard(LOG_LW2M_INFO + ": Client is awake!", registration.getId()); this.sendLogsToThingsboard(LOG_LW2M_INFO + ": Client is awake!", registration.getId());
//TODO: associate endpointId with device information. //TODO: associate endpointId with device information.
} }
@ -577,7 +640,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler
if (logMsg.length() > 1024) { if (logMsg.length() > 1024) {
logMsg = logMsg.substring(0, 1024); logMsg = logMsg.substring(0, 1024);
} }
this.helper.sendParametersOnThingsboardTelemetry(this.helper.getKvLogyToThingsboard(logMsg), sessionInfo); this.helper.sendParametersOnThingsboardTelemetry(this.helper.getKvStringtoThingsboard(LOG_LW2M_TELEMETRY, logMsg), sessionInfo);
} }
} }
@ -614,7 +677,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler
/** /**
* @param registration - * @param registration -
* @param lwM2mObject - * @param lwM2mObject -
* @param pathIdVer - * @param pathIdVer -
*/ */
private void updateObjectResourceValue(Registration registration, LwM2mObject lwM2mObject, String pathIdVer) { private void updateObjectResourceValue(Registration registration, LwM2mObject lwM2mObject, String pathIdVer) {
LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(pathIdVer)); LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(pathIdVer));
@ -651,18 +714,63 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler
private void updateResourcesValue(Registration registration, LwM2mResource lwM2mResource, String path) { private void updateResourcesValue(Registration registration, LwM2mResource lwM2mResource, String path) {
LwM2mClient lwM2MClient = clientContext.getOrRegister(registration); LwM2mClient lwM2MClient = clientContext.getOrRegister(registration);
if (lwM2MClient.saveResourceValue(path, lwM2mResource, this.config.getModelProvider())) { if (lwM2MClient.saveResourceValue(path, lwM2mResource, this.config.getModelProvider())) {
if (FR_PATH_RESOURCE_VER_ID.equals(convertPathFromIdVerToObjectId(path)) && /** version != null
lwM2MClient.getFrUpdate().getCurrentFwVersion() != null * set setClient_fw_info... = value
&& !lwM2MClient.getFrUpdate().getCurrentFwVersion().equals(lwM2MClient.getFrUpdate().getClientFwVersion()) **/
&& lwM2MClient.isUpdateFw()) { if (lwM2MClient.getFwUpdate().isInfoFwSwUpdate()) {
lwM2MClient.getFwUpdate().initReadValue(this, lwM2mTransportRequest, path);
/** version != null }
* set setClient_fw_version = value if (lwM2MClient.getSwUpdate().isInfoFwSwUpdate()) {
**/ lwM2MClient.getSwUpdate().initReadValue(this, lwM2mTransportRequest, path);
lwM2MClient.setUpdateFw(false); }
lwM2MClient.getFrUpdate().setClientFwVersion(lwM2mResource.getValue().toString());
log.warn("updateFirmwareClient3"); /**
this.updateFirmwareClient(lwM2MClient); * Before operation Execute (FwUpdate) inspection Update Result :
* - after finished operation Write result: success (FwUpdate): fw_state = DOWNLOADED
* - before start operation Execute (FwUpdate) Update Result = 0 - Initial value
* - start Execute (FwUpdate)
* After finished operation Execute (FwUpdate) inspection Update Result :
* - after start operation Execute (FwUpdate): fw_state = UPDATING
* - after success finished operation Execute (FwUpdate) Update Result == 1 ("Firmware updated successfully")
* - finished operation Execute (FwUpdate)
*/
if (lwM2MClient.getFwUpdate() != null
&& (convertPathFromObjectIdToIdVer(FW_RESULT_ID, registration).equals(path))) {
if (DOWNLOADED.name().equals(lwM2MClient.getFwUpdate().getStateUpdate())
&& lwM2MClient.getFwUpdate().conditionalFwExecuteStart()) {
lwM2MClient.getFwUpdate().executeFwSwWare(this, lwM2mTransportRequest);
} else if (UPDATING.name().equals(lwM2MClient.getFwUpdate().getStateUpdate())
&& lwM2MClient.getFwUpdate().conditionalFwExecuteAfterSuccess()) {
lwM2MClient.getFwUpdate().finishFwSwUpdate(this, true);
} else if (UPDATING.name().equals(lwM2MClient.getFwUpdate().getStateUpdate())
&& lwM2MClient.getFwUpdate().conditionalFwExecuteAfterError()) {
lwM2MClient.getFwUpdate().finishFwSwUpdate(this, false);
}
}
/**
* Before operation Execute (SwUpdate) inspection Update Result :
* - after finished operation Write result: success (SwUpdate): fw_state = DOWNLOADED
* - before operation Execute (SwUpdate) Update Result = 3 - Successfully Downloaded and package integrity verified
* - start Execute (SwUpdate)
* After finished operation Execute (SwUpdate) inspection Update Result :
* - after start operation Execute (SwUpdate): fw_state = UPDATING
* - after success finished operation Execute (SwUpdate) Update Result == 2 "Software successfully installed.""
* - after success finished operation Execute (SwUpdate) Update Result == 2 "Software successfully installed.""
* - finished operation Execute (SwUpdate)
*/
if (lwM2MClient.getSwUpdate() != null
&& (convertPathFromObjectIdToIdVer(SW_RESULT_ID, registration).equals(path))) {
if (DOWNLOADED.name().equals(lwM2MClient.getSwUpdate().getStateUpdate())
&& lwM2MClient.getSwUpdate().conditionalSwUpdateExecute()) {
lwM2MClient.getSwUpdate().executeFwSwWare(this, lwM2mTransportRequest);
} else if (UPDATING.name().equals(lwM2MClient.getSwUpdate().getStateUpdate())
&& lwM2MClient.getSwUpdate().conditionalSwExecuteAfterSuccess()) {
lwM2MClient.getSwUpdate().finishFwSwUpdate(this, true);
} else if (UPDATING.name().equals(lwM2MClient.getSwUpdate().getStateUpdate())
&& lwM2MClient.getSwUpdate().conditionalSwExecuteAfterError()) {
lwM2MClient.getSwUpdate().finishFwSwUpdate(this, false);
}
} }
Set<String> paths = new HashSet<>(); Set<String> paths = new HashSet<>();
paths.add(path); paths.add(path);
@ -684,7 +792,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler
*/ */
private void updateAttrTelemetry(Registration registration, Set<String> paths) { private void updateAttrTelemetry(Registration registration, Set<String> paths) {
try { try {
ResultsAddKeyValueProto results = getParametersFromProfile(registration, paths); ResultsAddKeyValueProto results = this.getParametersFromProfile(registration, paths);
SessionInfoProto sessionInfo = this.getSessionInfoOrCloseSession(registration); SessionInfoProto sessionInfo = this.getSessionInfoOrCloseSession(registration);
if (results != null && sessionInfo != null) { if (results != null && sessionInfo != null) {
if (results.getResultAttributes().size() > 0) { if (results.getResultAttributes().size() > 0) {
@ -818,7 +926,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler
Object finalvalueKvProto = valueKvProto; Object finalvalueKvProto = valueKvProto;
Gson gson = new GsonBuilder().create(); Gson gson = new GsonBuilder().create();
resourceValue.getValues().forEach((k, v) -> { resourceValue.getValues().forEach((k, v) -> {
Object val = this.converter.convertValue(resourceValue.getValue(), currentType, expectedType, Object val = this.converter.convertValue(v, currentType, expectedType,
new LwM2mPath(convertPathFromIdVerToObjectId(pathIdVer))); new LwM2mPath(convertPathFromIdVerToObjectId(pathIdVer)));
JsonElement element = gson.toJsonTree(val, val.getClass()); JsonElement element = gson.toJsonTree(val, val.getClass());
((JsonObject) finalvalueKvProto).add(String.valueOf(k), element); ((JsonObject) finalvalueKvProto).add(String.valueOf(k), element);
@ -910,7 +1018,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler
*/ */
private void onDeviceProfileUpdate(Set<String> registrationIds, DeviceProfile deviceProfile) { private void onDeviceProfileUpdate(Set<String> registrationIds, DeviceProfile deviceProfile) {
LwM2mClientProfile lwM2MClientProfileOld = clientContext.getProfiles().get(deviceProfile.getUuidId()).clone(); LwM2mClientProfile lwM2MClientProfileOld = clientContext.getProfiles().get(deviceProfile.getUuidId()).clone();
if (clientContext.toClientProfile(deviceProfile) != null) { if (clientContext.profileUpdate(deviceProfile) != null) {
// #1 // #1
JsonArray attributeOld = lwM2MClientProfileOld.getPostAttributeProfile(); JsonArray attributeOld = lwM2MClientProfileOld.getPostAttributeProfile();
Set<String> attributeSetOld = convertJsonArrayToSet(attributeOld); Set<String> attributeSetOld = convertJsonArrayToSet(attributeOld);
@ -920,7 +1028,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler
JsonObject keyNameOld = lwM2MClientProfileOld.getPostKeyNameProfile(); JsonObject keyNameOld = lwM2MClientProfileOld.getPostKeyNameProfile();
JsonObject attributeLwm2mOld = lwM2MClientProfileOld.getPostAttributeLwm2mProfile(); JsonObject attributeLwm2mOld = lwM2MClientProfileOld.getPostAttributeLwm2mProfile();
LwM2mClientProfile lwM2MClientProfileNew = clientContext.getProfiles().get(deviceProfile.getUuidId()); LwM2mClientProfile lwM2MClientProfileNew = clientContext.getProfiles().get(deviceProfile.getUuidId()).clone();
JsonArray attributeNew = lwM2MClientProfileNew.getPostAttributeProfile(); JsonArray attributeNew = lwM2MClientProfileNew.getPostAttributeProfile();
Set<String> attributeSetNew = convertJsonArrayToSet(attributeNew); Set<String> attributeSetNew = convertJsonArrayToSet(attributeNew);
JsonArray telemetryNew = lwM2MClientProfileNew.getPostTelemetryProfile(); JsonArray telemetryNew = lwM2MClientProfileNew.getPostTelemetryProfile();
@ -968,8 +1076,6 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler
registrationIds.forEach(registrationId -> { registrationIds.forEach(registrationId -> {
Registration registration = clientContext.getRegistration(registrationId); Registration registration = clientContext.getRegistration(registrationId);
this.readObserveFromProfile(registration, sendAttrToThingsboard.getPathPostParametersAdd(), READ); this.readObserveFromProfile(registration, sendAttrToThingsboard.getPathPostParametersAdd(), READ);
// send attr/telemetry to tingsboard for new path
this.updateAttrTelemetry(registration, sendAttrToThingsboard.getPathPostParametersAdd());
}); });
} }
// #4.2 del // #4.2 del
@ -1248,7 +1354,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler
* @param registration - Registration LwM2M Client * @param registration - Registration LwM2M Client
* @return - sessionInfo after access connect client * @return - sessionInfo after access connect client
*/ */
private SessionInfoProto getSessionInfoOrCloseSession(Registration registration) { public SessionInfoProto getSessionInfoOrCloseSession(Registration registration) {
return getSessionInfoOrCloseSession(clientContext.getOrRegister(registration)); return getSessionInfoOrCloseSession(clientContext.getOrRegister(registration));
} }
@ -1296,7 +1402,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler
TransportProtos.GetAttributeRequestMsg getAttributeMsg = adaptor.convertToGetAttributes(null, keyNamesMap.values()); TransportProtos.GetAttributeRequestMsg getAttributeMsg = adaptor.convertToGetAttributes(null, keyNamesMap.values());
transportService.process(sessionInfo, getAttributeMsg, getAckCallback(lwM2MClient, getAttributeMsg.getRequestId(), DEVICE_ATTRIBUTES_REQUEST)); transportService.process(sessionInfo, getAttributeMsg, getAckCallback(lwM2MClient, getAttributeMsg.getRequestId(), DEVICE_ATTRIBUTES_REQUEST));
} catch (AdaptorException e) { } catch (AdaptorException e) {
log.warn("Failed to decode get attributes request", e); log.trace("Failed to decode get attributes request", e);
} }
} }
@ -1304,62 +1410,72 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler
} }
public void getInfoFirmwareUpdate(LwM2mClient lwM2MClient) { public void getInfoFirmwareUpdate(LwM2mClient lwM2MClient) {
SessionInfoProto sessionInfo = this.getSessionInfoOrCloseSession(lwM2MClient); if (lwM2MClient.getRegistration().getSupportedVersion(FW_ID) != null) {
if (sessionInfo != null) { SessionInfoProto sessionInfo = this.getSessionInfoOrCloseSession(lwM2MClient);
TransportProtos.GetFirmwareRequestMsg getFirmwareRequestMsg = TransportProtos.GetFirmwareRequestMsg.newBuilder() if (sessionInfo != null) {
.setDeviceIdMSB(sessionInfo.getDeviceIdMSB()) DefaultLwM2MTransportMsgHandler serviceImpl = this;
.setDeviceIdLSB(sessionInfo.getDeviceIdLSB()) transportService.process(sessionInfo, createFirmwareRequestMsg(sessionInfo, FirmwareType.FIRMWARE.name()),
.setTenantIdMSB(sessionInfo.getTenantIdMSB()) new TransportServiceCallback<>() {
.setTenantIdLSB(sessionInfo.getTenantIdLSB()) @Override
.setType(FirmwareType.FIRMWARE.name()) public void onSuccess(TransportProtos.GetFirmwareResponseMsg response) {
.build(); if (TransportProtos.ResponseStatus.SUCCESS.equals(response.getResponseStatus())
transportService.process(sessionInfo, getFirmwareRequestMsg, && response.getType().equals(FirmwareType.FIRMWARE.name())) {
new TransportServiceCallback<>() { lwM2MClient.getFwUpdate().setCurrentVersion(response.getVersion());
@Override lwM2MClient.getFwUpdate().setCurrentTitle(response.getTitle());
public void onSuccess(TransportProtos.GetFirmwareResponseMsg response) { lwM2MClient.getFwUpdate().setCurrentId(new FirmwareId(new UUID(response.getFirmwareIdMSB(), response.getFirmwareIdLSB())).getId());
if (TransportProtos.ResponseStatus.SUCCESS.equals(response.getResponseStatus())) { lwM2MClient.getFwUpdate().sendReadObserveInfo(lwM2mTransportRequest);
lwM2MClient.getFrUpdate().setCurrentFwVersion(response.getVersion()); } else {
lwM2MClient.getFrUpdate().setCurrentFwId(new FirmwareId(new UUID(response.getFirmwareIdMSB(), response.getFirmwareIdLSB())).getId()); log.trace("Firmware [{}] [{}]", lwM2MClient.getDeviceName(), response.getResponseStatus().toString());
lwM2MClient.setUpdateFw(true); }
readRequestToClientFirmwareVer(lwM2MClient.getRegistration());
} else {
log.trace("Firmware [{}] [{}]", lwM2MClient.getDeviceName(), response.getResponseStatus().toString());
} }
}
@Override @Override
public void onError(Throwable e) { public void onError(Throwable e) {
log.trace("Failed to process credentials ", e); log.trace("Failed to process firmwareUpdate ", e);
} }
}); });
}
} }
} }
/** public void getInfoSoftwareUpdate(LwM2mClient lwM2MClient) {
* @param registration if (lwM2MClient.getRegistration().getSupportedVersion(SW_ID) != null) {
*/ SessionInfoProto sessionInfo = this.getSessionInfoOrCloseSession(lwM2MClient);
public void readRequestToClientFirmwareVer(Registration registration) { if (sessionInfo != null) {
String pathIdVer = convertPathFromObjectIdToIdVer(FR_PATH_RESOURCE_VER_ID, registration); DefaultLwM2MTransportMsgHandler serviceImpl = this;
lwM2mTransportRequest.sendAllRequest(registration, pathIdVer, READ, ContentFormat.TLV.getName(), transportService.process(sessionInfo, createFirmwareRequestMsg(sessionInfo, FirmwareType.SOFTWARE.name()),
null, config.getTimeout(), null); new TransportServiceCallback<>() {
} @Override
public void onSuccess(TransportProtos.GetFirmwareResponseMsg response) {
if (TransportProtos.ResponseStatus.SUCCESS.equals(response.getResponseStatus())
&& response.getType().equals(FirmwareType.SOFTWARE.name())) {
lwM2MClient.getSwUpdate().setCurrentVersion(response.getVersion());
lwM2MClient.getSwUpdate().setCurrentTitle(response.getTitle());
lwM2MClient.getSwUpdate().setCurrentId(new FirmwareId(new UUID(response.getFirmwareIdMSB(), response.getFirmwareIdLSB())).getId());
lwM2MClient.getSwUpdate().sendReadObserveInfo(lwM2mTransportRequest);
} else {
log.trace("Software [{}] [{}]", lwM2MClient.getDeviceName(), response.getResponseStatus().toString());
}
}
/** @Override
* @param lwM2MClient - public void onError(Throwable e) {
*/ log.trace("Failed to process softwareUpdate ", e);
public void updateFirmwareClient(LwM2mClient lwM2MClient) { }
if (!lwM2MClient.getFrUpdate().getCurrentFwVersion().equals(lwM2MClient.getFrUpdate().getClientFwVersion())) { });
int chunkSize = 0; }
int chunk = 0;
byte[] firmwareChunk = firmwareDataCache.get(lwM2MClient.getFrUpdate().getCurrentFwId().toString(), chunkSize, chunk);
String verSupportedObject = lwM2MClient.getRegistration().getSupportedObject().get(FR_OBJECT_ID);
String targetIdVer = LWM2M_SEPARATOR_PATH + FR_OBJECT_ID + LWM2M_SEPARATOR_KEY + verSupportedObject + LWM2M_SEPARATOR_PATH + 0 + LWM2M_SEPARATOR_PATH + 0;
lwM2mTransportRequest.sendAllRequest(lwM2MClient.getRegistration(), targetIdVer, WRITE_REPLACE, ContentFormat.OPAQUE.getName(),
firmwareChunk, config.getTimeout(), null);
log.warn("updateFirmwareClient [{}] [{}]", lwM2MClient.getFrUpdate().getCurrentFwVersion(), lwM2MClient.getFrUpdate().getClientFwVersion());
} }
} }
private TransportProtos.GetFirmwareRequestMsg createFirmwareRequestMsg(SessionInfoProto sessionInfo, String nameFwSW) {
return TransportProtos.GetFirmwareRequestMsg.newBuilder()
.setDeviceIdMSB(sessionInfo.getDeviceIdMSB())
.setDeviceIdLSB(sessionInfo.getDeviceIdLSB())
.setTenantIdMSB(sessionInfo.getTenantIdMSB())
.setTenantIdLSB(sessionInfo.getTenantIdLSB())
.setType(nameFwSW)
.build();
}
/** /**
* !!! sharedAttr === profileAttr !!! * !!! sharedAttr === profileAttr !!!
@ -1386,4 +1502,8 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler
objectId != null && objectVer != null && objectVer.equals(lwM2mClient.getRegistration().getSupportedVersion(objectId))); objectId != null && objectVer != null && objectVer.equals(lwM2mClient.getRegistration().getSupportedVersion(objectId)));
} }
public LwM2MTransportServerConfig getConfig() {
return this.config;
}
} }

4
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java

@ -28,6 +28,7 @@ import org.eclipse.leshan.server.californium.registration.CaliforniumRegistratio
import org.eclipse.leshan.server.model.LwM2mModelProvider; import org.eclipse.leshan.server.model.LwM2mModelProvider;
import org.eclipse.leshan.server.security.EditableSecurityStore; import org.eclipse.leshan.server.security.EditableSecurityStore;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
@ -57,6 +58,8 @@ import java.security.spec.InvalidParameterSpecException;
import java.security.spec.KeySpec; import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Arrays; import java.util.Arrays;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256; import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256;
import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8; import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8;
@ -122,7 +125,6 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService {
/* Use a magic converter to support bad type send by the UI. */ /* Use a magic converter to support bad type send by the UI. */
builder.setEncoder(new DefaultLwM2mNodeEncoder(LwM2mValueConverterImpl.getInstance())); builder.setEncoder(new DefaultLwM2mNodeEncoder(LwM2mValueConverterImpl.getInstance()));
/* Create CoAP Config */ /* Create CoAP Config */
builder.setCoapConfig(getCoapConfig(config.getPort(), config.getSecurePort())); builder.setCoapConfig(getCoapConfig(config.getPort(), config.getSecurePort()));

22
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java

@ -86,21 +86,22 @@ public class LwM2mServerListener {
@Override @Override
public void cancelled(Observation observation) { public void cancelled(Observation observation) {
String msg = String.format("%s: Cancel Observation %s.", LOG_LW2M_INFO, observation.getPath()); String msg = String.format("%s: Canceled Observation %s.", LOG_LW2M_INFO, observation.getPath());
service.sendLogsToThingsboard(msg, observation.getRegistrationId()); service.sendLogsToThingsboard(msg, observation.getRegistrationId());
log.trace(msg); log.warn(msg);
} }
@Override @Override
public void onResponse(Observation observation, Registration registration, ObserveResponse response) { public void onResponse(Observation observation, Registration registration, ObserveResponse response) {
if (registration != null) { if (registration != null) {
try { // if (observation.getPath().isResource() || observation.getPath().isResourceInstance()) {
service.onUpdateValueAfterReadResponse(registration, convertPathFromObjectIdToIdVer(observation.getPath().toString(), // String msg = String.format("%s: Successful Observation %s.", LOG_LW2M_INFO,
registration), response, null); // observation.getPath());
} catch (Exception e) { // log.warn(msg);
log.error("Observation/Read onResponse", e); // service.sendLogsToThingsboard(msg, registration.getId());
// }
} service.onUpdateValueAfterReadResponse(registration, convertPathFromObjectIdToIdVer(observation.getPath().toString(),
registration), response, null);
} }
} }
@ -113,9 +114,8 @@ public class LwM2mServerListener {
public void newObservation(Observation observation, Registration registration) { public void newObservation(Observation observation, Registration registration) {
String msg = String.format("%s: Successful start newObservation %s.", LOG_LW2M_INFO, String msg = String.format("%s: Successful start newObservation %s.", LOG_LW2M_INFO,
observation.getPath()); observation.getPath());
log.warn(msg);
service.sendLogsToThingsboard(msg, registration.getId()); service.sendLogsToThingsboard(msg, registration.getId());
log.trace(msg);
} }
}; };
} }

5
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportMsgHandler.java

@ -21,6 +21,7 @@ import org.eclipse.leshan.server.registration.Registration;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
import org.thingsboard.server.transport.lwm2m.server.client.Lwm2mClientRpcRequest; import org.thingsboard.server.transport.lwm2m.server.client.Lwm2mClientRpcRequest;
import java.util.Collection; import java.util.Collection;
@ -36,7 +37,7 @@ public interface LwM2mTransportMsgHandler {
void onSleepingDev(Registration registration); void onSleepingDev(Registration registration);
void setCancelObservations(Registration registration); void setCancelObservationsAll(Registration registration);
void onUpdateValueAfterReadResponse(Registration registration, String path, ReadResponse response, Lwm2mClientRpcRequest rpcRequest); void onUpdateValueAfterReadResponse(Registration registration, String path, ReadResponse response, Lwm2mClientRpcRequest rpcRequest);
@ -63,4 +64,6 @@ public interface LwM2mTransportMsgHandler {
void onAwakeDev(Registration registration); void onAwakeDev(Registration registration);
void sendLogsToThingsboard(String msg, String registrationId); void sendLogsToThingsboard(String msg, String registrationId);
LwM2MTransportServerConfig getConfig();
} }

445
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java

@ -19,6 +19,7 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.core.coap.CoAP; import org.eclipse.californium.core.coap.CoAP;
import org.eclipse.californium.core.coap.Response; import org.eclipse.californium.core.coap.Response;
import org.eclipse.leshan.core.Link;
import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.model.ResourceModel;
import org.eclipse.leshan.core.node.LwM2mNode; import org.eclipse.leshan.core.node.LwM2mNode;
import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.LwM2mPath;
@ -35,7 +36,6 @@ import org.eclipse.leshan.core.request.ObserveRequest;
import org.eclipse.leshan.core.request.ReadRequest; import org.eclipse.leshan.core.request.ReadRequest;
import org.eclipse.leshan.core.request.WriteRequest; import org.eclipse.leshan.core.request.WriteRequest;
import org.eclipse.leshan.core.request.exception.ClientSleepingException; import org.eclipse.leshan.core.request.exception.ClientSleepingException;
import org.eclipse.leshan.core.response.CancelObservationResponse;
import org.eclipse.leshan.core.response.DeleteResponse; import org.eclipse.leshan.core.response.DeleteResponse;
import org.eclipse.leshan.core.response.DiscoverResponse; import org.eclipse.leshan.core.response.DiscoverResponse;
import org.eclipse.leshan.core.response.ExecuteResponse; import org.eclipse.leshan.core.response.ExecuteResponse;
@ -68,15 +68,26 @@ import java.util.stream.Collectors;
import static org.eclipse.californium.core.coap.CoAP.ResponseCode.CONTENT; import static org.eclipse.californium.core.coap.CoAP.ResponseCode.CONTENT;
import static org.eclipse.leshan.core.ResponseCode.BAD_REQUEST; import static org.eclipse.leshan.core.ResponseCode.BAD_REQUEST;
import static org.eclipse.leshan.core.ResponseCode.NOT_FOUND; import static org.eclipse.leshan.core.ResponseCode.NOT_FOUND;
import static org.thingsboard.server.common.data.firmware.FirmwareUpdateStatus.DOWNLOADED;
import static org.thingsboard.server.common.data.firmware.FirmwareUpdateStatus.FAILED;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper.getContentFormatByResourceModelType;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.DEFAULT_TIMEOUT; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.DEFAULT_TIMEOUT;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FR_PATH_RESOURCE_VER_ID; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_PACKAGE_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_UPDATE_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_ERROR; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_ERROR;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_INFO; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_INFO;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_VALUE; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_VALUE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.DISCOVER;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.DISCOVER_All;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.EXECUTE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.OBSERVE_CANCEL; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.OBSERVE_CANCEL;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.OBSERVE_READ_ALL; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.OBSERVE_READ_ALL;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.RESPONSE_CHANNEL; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.WRITE_REPLACE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.WRITE_UPDATE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.RESPONSE_REQUEST_CHANNEL;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_INSTALL_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_PACKAGE_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromIdVerToObjectId; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromIdVerToObjectId;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromObjectIdToIdVer; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromObjectIdToIdVer;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.createWriteAttributeRequest; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.createWriteAttributeRequest;
@ -86,20 +97,20 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.c
@TbLwM2mTransportComponent @TbLwM2mTransportComponent
@RequiredArgsConstructor @RequiredArgsConstructor
public class LwM2mTransportRequest { public class LwM2mTransportRequest {
private ExecutorService executorResponse; private ExecutorService responseRequestExecutor;
public LwM2mValueConverterImpl converter; public LwM2mValueConverterImpl converter;
private final LwM2mTransportContext context; private final LwM2mTransportContext context;
private final LwM2MTransportServerConfig config; private final LwM2MTransportServerConfig config;
private final LwM2mClientContext lwM2mClientContext; private final LwM2mClientContext lwM2mClientContext;
private final DefaultLwM2MTransportMsgHandler serviceImpl; private final DefaultLwM2MTransportMsgHandler handler;
@PostConstruct @PostConstruct
public void init() { public void init() {
this.converter = LwM2mValueConverterImpl.getInstance(); this.converter = LwM2mValueConverterImpl.getInstance();
executorResponse = Executors.newFixedThreadPool(this.config.getResponsePoolSize(), responseRequestExecutor = Executors.newFixedThreadPool(this.config.getResponsePoolSize(),
new NamedThreadFactory(String.format("LwM2M %s channel response", RESPONSE_CHANNEL))); new NamedThreadFactory(String.format("LwM2M %s channel response after request", RESPONSE_REQUEST_CHANNEL)));
} }
/** /**
@ -115,154 +126,186 @@ public class LwM2mTransportRequest {
String contentFormatName, Object params, long timeoutInMs, Lwm2mClientRpcRequest rpcRequest) { String contentFormatName, Object params, long timeoutInMs, Lwm2mClientRpcRequest rpcRequest) {
try { try {
String target = convertPathFromIdVerToObjectId(targetIdVer); String target = convertPathFromIdVerToObjectId(targetIdVer);
DownlinkRequest request = null;
ContentFormat contentFormat = contentFormatName != null ? ContentFormat.fromName(contentFormatName.toUpperCase()) : ContentFormat.DEFAULT; ContentFormat contentFormat = contentFormatName != null ? ContentFormat.fromName(contentFormatName.toUpperCase()) : ContentFormat.DEFAULT;
LwM2mClient lwM2MClient = this.lwM2mClientContext.getOrRegister(registration); LwM2mClient lwM2MClient = this.lwM2mClientContext.getOrRegister(registration);
LwM2mPath resultIds = target != null ? new LwM2mPath(target) : null; LwM2mPath resultIds = target != null ? new LwM2mPath(target) : null;
if (!OBSERVE_READ_ALL.name().equals(typeOper.name()) && resultIds != null && registration != null && resultIds.getObjectId() >= 0 && lwM2MClient != null) { if (!OBSERVE_READ_ALL.name().equals(typeOper.name()) && resultIds != null && registration != null && resultIds.getObjectId() >= 0 && lwM2MClient != null) {
if (lwM2MClient.isValidObjectVersion(targetIdVer)) { if (lwM2MClient.isValidObjectVersion(targetIdVer)) {
timeoutInMs = timeoutInMs > 0 ? timeoutInMs : DEFAULT_TIMEOUT; timeoutInMs = timeoutInMs > 0 ? timeoutInMs : DEFAULT_TIMEOUT;
ResourceModel resourceModel = null; DownlinkRequest request = createRequest (registration, lwM2MClient, typeOper, contentFormat, target,
switch (typeOper) { targetIdVer, resultIds, params, rpcRequest);
case READ:
request = new ReadRequest(contentFormat, target);
break;
case DISCOVER:
request = new DiscoverRequest(target);
break;
case OBSERVE:
if (resultIds.isResource()) {
request = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId());
} else if (resultIds.isObjectInstance()) {
request = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId());
} else if (resultIds.getObjectId() >= 0) {
request = new ObserveRequest(contentFormat, resultIds.getObjectId());
}
break;
case OBSERVE_CANCEL:
/*
lwM2MTransportRequest.sendAllRequest(lwServer, registration, path, POST_TYPE_OPER_OBSERVE_CANCEL, null, null, null, null, context.getTimeout());
At server side this will not remove the observation from the observation store, to do it you need to use
{@code ObservationService#cancelObservation()}
*/
context.getServer().getObservationService().cancelObservations(registration, target);
break;
case EXECUTE:
resourceModel = lwM2MClient.getResourceModel(targetIdVer, this.config
.getModelProvider());
if (params != null && !resourceModel.multiple) {
request = new ExecuteRequest(target, (String) this.converter.convertValue(params, resourceModel.type, ResourceModel.Type.STRING, resultIds));
} else {
request = new ExecuteRequest(target);
}
break;
case WRITE_REPLACE:
// Request to write a <b>String Single-Instance Resource</b> using the TLV content format.
resourceModel = lwM2MClient.getResourceModel(targetIdVer, this.config.getModelProvider());
if (contentFormat.equals(ContentFormat.TLV)) {
request = this.getWriteRequestSingleResource(null, resultIds.getObjectId(),
resultIds.getObjectInstanceId(), resultIds.getResourceId(), params, resourceModel.type,
registration, rpcRequest);
}
// Mode.REPLACE && Request to write a <b>String Single-Instance Resource</b> using the given content format (TEXT, TLV, JSON)
else if (!contentFormat.equals(ContentFormat.TLV)) {
request = this.getWriteRequestSingleResource(contentFormat, resultIds.getObjectId(),
resultIds.getObjectInstanceId(), resultIds.getResourceId(), params, resourceModel.type,
registration, rpcRequest);
}
break;
case WRITE_UPDATE:
if (resultIds.isResource()) {
/**
* send request: path = '/3/0' node == wM2mObjectInstance
* with params == "\"resources\": {15: resource:{id:15. value:'+01'...}}
**/
Collection<LwM2mResource> resources = lwM2MClient.getNewOneResourceForInstance(
targetIdVer, params,
this.config.getModelProvider(),
this.converter);
request = new WriteRequest(WriteRequest.Mode.UPDATE, contentFormat, resultIds.getObjectId(),
resultIds.getObjectInstanceId(), resources);
}
/**
* params = "{\"id\":0,\"resources\":[{\"id\":14,\"value\":\"+5\"},{\"id\":15,\"value\":\"+9\"}]}"
*
* int rscId = resultIds.getObjectInstanceId();
*/
else if (resultIds.isObjectInstance()) {
if (((ConcurrentHashMap) params).size() > 0) {
Collection<LwM2mResource> resources = lwM2MClient.getNewManyResourcesForInstance(
targetIdVer, params,
this.config.getModelProvider(),
this.converter);
request = new WriteRequest(WriteRequest.Mode.UPDATE, contentFormat, resultIds.getObjectId(),
resultIds.getObjectInstanceId(), resources);
}
} else if (resultIds.getObjectId() >= 0) {
request = new ObserveRequest(resultIds.getObjectId());
}
break;
case WRITE_ATTRIBUTES:
request = createWriteAttributeRequest(target, params);
break;
case DELETE:
request = new DeleteRequest(target);
break;
}
if (request != null) { if (request != null) {
try { try {
this.sendRequest(registration, lwM2MClient, request, timeoutInMs, rpcRequest); this.sendRequest(registration, lwM2MClient, request, timeoutInMs, rpcRequest);
} catch (ClientSleepingException e) { } catch (ClientSleepingException e) {
DownlinkRequest finalRequest = request; DownlinkRequest finalRequest = request;
long finalTimeoutInMs = timeoutInMs; long finalTimeoutInMs = timeoutInMs;
lwM2MClient.getQueuedRequests().add(() -> sendRequest(registration, lwM2MClient, finalRequest, finalTimeoutInMs, rpcRequest)); Lwm2mClientRpcRequest finalRpcRequest = rpcRequest;
lwM2MClient.getQueuedRequests().add(() -> sendRequest(registration, lwM2MClient, finalRequest, finalTimeoutInMs, finalRpcRequest));
} catch (Exception e) { } catch (Exception e) {
log.error("[{}] [{}] [{}] Failed to send downlink.", registration.getEndpoint(), targetIdVer, typeOper.name(), e); log.error("[{}] [{}] [{}] Failed to send downlink.", registration.getEndpoint(), targetIdVer, typeOper.name(), e);
} }
} else if (OBSERVE_CANCEL == typeOper) { }
log.trace("[{}], [{}] - [{}] SendRequest", registration.getEndpoint(), typeOper.name(), targetIdVer); else if (WRITE_UPDATE.name().equals(typeOper.name())) {
if (rpcRequest != null) { Lwm2mClientRpcRequest rpcRequestClone = (Lwm2mClientRpcRequest) rpcRequest.clone();
rpcRequest.setInfoMsg(null); if (rpcRequestClone != null) {
serviceImpl.sentRpcRequest(rpcRequest, CONTENT.name(), null, null); String errorMsg = String.format("Path %s params is not valid", targetIdVer);
handler.sentRpcRequest(rpcRequestClone, BAD_REQUEST.getName(), errorMsg, LOG_LW2M_ERROR);
rpcRequest = null;
} }
} else { }
else if (!OBSERVE_CANCEL.name().equals(typeOper.name())) {
log.error("[{}], [{}] - [{}] error SendRequest", registration.getEndpoint(), typeOper.name(), targetIdVer); log.error("[{}], [{}] - [{}] error SendRequest", registration.getEndpoint(), typeOper.name(), targetIdVer);
if (rpcRequest != null) { if (rpcRequest != null) {
ResourceModel resourceModel = lwM2MClient.getResourceModel(targetIdVer, this.config.getModelProvider());
String errorMsg = resourceModel == null ? String.format("Path %s not found in object version", targetIdVer) : "SendRequest - null"; String errorMsg = resourceModel == null ? String.format("Path %s not found in object version", targetIdVer) : "SendRequest - null";
serviceImpl.sentRpcRequest(rpcRequest, NOT_FOUND.getName(), errorMsg, LOG_LW2M_ERROR); handler.sentRpcRequest(rpcRequest, NOT_FOUND.getName(), errorMsg, LOG_LW2M_ERROR);
} }
} }
} else if (rpcRequest != null) { } else if (rpcRequest != null) {
String errorMsg = String.format("Path %s not found in object version", targetIdVer); String errorMsg = String.format("Path %s not found in object version", targetIdVer);
serviceImpl.sentRpcRequest(rpcRequest, NOT_FOUND.getName(), errorMsg, LOG_LW2M_ERROR); handler.sentRpcRequest(rpcRequest, NOT_FOUND.getName(), errorMsg, LOG_LW2M_ERROR);
}
} else if (OBSERVE_READ_ALL.name().equals(typeOper.name()) || DISCOVER_All.name().equals(typeOper.name())) {
Set<String> paths;
if (OBSERVE_READ_ALL.name().equals(typeOper.name())) {
Set<Observation> observations = context.getServer().getObservationService().getObservations(registration);
paths = observations.stream().map(observation -> observation.getPath().toString()).collect(Collectors.toUnmodifiableSet());
} else {
assert registration != null;
Link[] objectLinks = registration.getSortedObjectLinks();
paths = Arrays.stream(objectLinks).map(Link::toString).collect(Collectors.toUnmodifiableSet());
String msg = String.format("%s: type operation %s paths - %s", LOG_LW2M_INFO,
typeOper.name(), paths);
handler.sendLogsToThingsboard(msg, registration.getId());
} }
} else if (OBSERVE_READ_ALL.name().equals(typeOper.name())) {
Set<Observation> observations = context.getServer().getObservationService().getObservations(registration);
Set<String> observationPaths = observations.stream().map(observation -> observation.getPath().toString()).collect(Collectors.toUnmodifiableSet());
String msg = String.format("%s: type operation %s observation paths - %s", LOG_LW2M_INFO,
OBSERVE_READ_ALL.type, observationPaths);
serviceImpl.sendLogsToThingsboard(msg, registration.getId());
log.trace("[{}] [{}], [{}]", typeOper.name(), registration.getEndpoint(), msg);
if (rpcRequest != null) { if (rpcRequest != null) {
String valueMsg = String.format("Observation paths - %s", observationPaths); String valueMsg = String.format("Paths - %s", paths);
serviceImpl.sentRpcRequest(rpcRequest, CONTENT.name(), valueMsg, LOG_LW2M_VALUE); handler.sentRpcRequest(rpcRequest, CONTENT.name(), valueMsg, LOG_LW2M_VALUE);
} }
} else if (OBSERVE_CANCEL.name().equals(typeOper.name())) {
int observeCancelCnt = context.getServer().getObservationService().cancelObservations(registration);
String observeCancelMsgAll = String.format("%s: type operation %s paths: All count: %d", LOG_LW2M_INFO,
OBSERVE_CANCEL.name(), observeCancelCnt);
this.afterObserveCancel(registration, observeCancelCnt, observeCancelMsgAll, rpcRequest);
} }
} catch (Exception e) { } catch (Exception e) {
String msg = String.format("%s: type operation %s %s", LOG_LW2M_ERROR, String msg = String.format("%s: type operation %s %s", LOG_LW2M_ERROR,
typeOper.name(), e.getMessage()); typeOper.name(), e.getMessage());
serviceImpl.sendLogsToThingsboard(msg, registration.getId()); handler.sendLogsToThingsboard(msg, registration.getId());
try { if (rpcRequest != null) {
throw new Exception(e); String errorMsg = String.format("Path %s type operation %s %s", targetIdVer, typeOper.name(), e.getMessage());
} catch (Exception exception) { handler.sentRpcRequest(rpcRequest, NOT_FOUND.getName(), errorMsg, LOG_LW2M_ERROR);
exception.printStackTrace();
} }
} }
} }
private DownlinkRequest createRequest (Registration registration, LwM2mClient lwM2MClient, LwM2mTypeOper typeOper,
ContentFormat contentFormat, String target, String targetIdVer,
LwM2mPath resultIds, Object params, Lwm2mClientRpcRequest rpcRequest) {
DownlinkRequest request = null;
switch (typeOper) {
case READ:
request = new ReadRequest(contentFormat, target);
break;
case DISCOVER:
request = new DiscoverRequest(target);
break;
case OBSERVE:
String msg = String.format("%s: Send Observation %s.", LOG_LW2M_INFO, targetIdVer);
log.warn(msg);
if (resultIds.isResource()) {
Set<Observation> observations = context.getServer().getObservationService().getObservations(registration);
Set<Observation> paths = observations.stream().filter(observation -> observation.getPath().equals(resultIds)).collect(Collectors.toSet());
if (paths.size() == 0) {
request = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId());
} else {
request = new ReadRequest(contentFormat, target);
}
} else if (resultIds.isObjectInstance()) {
request = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId());
} else if (resultIds.getObjectId() >= 0) {
request = new ObserveRequest(contentFormat, resultIds.getObjectId());
}
break;
case OBSERVE_CANCEL:
/*
lwM2MTransportRequest.sendAllRequest(lwServer, registration, path, POST_TYPE_OPER_OBSERVE_CANCEL, null, null, null, null, context.getTimeout());
At server side this will not remove the observation from the observation store, to do it you need to use
{@code ObservationService#cancelObservation()}
*/
int observeCancelCnt = context.getServer().getObservationService().cancelObservations(registration, target);
String observeCancelMsg = String.format("%s: type operation %s paths: %s count: %d", LOG_LW2M_INFO,
OBSERVE_CANCEL.name(), target, observeCancelCnt);
this.afterObserveCancel(registration, observeCancelCnt, observeCancelMsg, rpcRequest);
break;
case EXECUTE:
ResourceModel resourceModelExe = lwM2MClient.getResourceModel(targetIdVer, this.config.getModelProvider());
if (params != null && !resourceModelExe.multiple) {
request = new ExecuteRequest(target, (String) this.converter.convertValue(params, resourceModelExe.type, ResourceModel.Type.STRING, resultIds));
} else {
request = new ExecuteRequest(target);
}
break;
case WRITE_REPLACE:
/**
* Request to write a <b>String Single-Instance Resource</b> using the TLV content format.
* Type from resourceModel -> STRING, INTEGER, FLOAT, BOOLEAN, OPAQUE, TIME, OBJLNK
* contentFormat -> TLV, TLV, TLV, TLV, OPAQUE, TLV, LINK
* JSON, TEXT;
**/
ResourceModel resourceModelWrite = lwM2MClient.getResourceModel(targetIdVer, this.config.getModelProvider());
contentFormat = getContentFormatByResourceModelType(resourceModelWrite, contentFormat);
request = this.getWriteRequestSingleResource(contentFormat, resultIds.getObjectId(),
resultIds.getObjectInstanceId(), resultIds.getResourceId(), params, resourceModelWrite.type,
registration, rpcRequest);
break;
case WRITE_UPDATE:
if (resultIds.isResource()) {
/**
* send request: path = '/3/0' node == wM2mObjectInstance
* with params == "\"resources\": {15: resource:{id:15. value:'+01'...}}
**/
Collection<LwM2mResource> resources = lwM2MClient.getNewResourceForInstance(
targetIdVer, params,
this.config.getModelProvider(),
this.converter);
contentFormat = getContentFormatByResourceModelType(lwM2MClient.getResourceModel(targetIdVer, this.config.getModelProvider()),
contentFormat);
request = new WriteRequest(WriteRequest.Mode.UPDATE, contentFormat, resultIds.getObjectId(),
resultIds.getObjectInstanceId(), resources);
}
/**
* params = "{\"id\":0,\"resources\":[{\"id\":14,\"value\":\"+5\"},{\"id\":15,\"value\":\"+9\"}]}"
* int rscId = resultIds.getObjectInstanceId();
* contentFormat – Format of the payload (TLV or JSON).
*/
else if (resultIds.isObjectInstance()) {
if (((ConcurrentHashMap) params).size() > 0) {
Collection<LwM2mResource> resources = lwM2MClient.getNewResourcesForInstance(
targetIdVer, params,
this.config.getModelProvider(),
this.converter);
if (resources.size() > 0) {
contentFormat = contentFormat.equals(ContentFormat.JSON) ? contentFormat : ContentFormat.TLV;
request = new WriteRequest(WriteRequest.Mode.UPDATE, contentFormat, resultIds.getObjectId(),
resultIds.getObjectInstanceId(), resources);
}
}
} else if (resultIds.getObjectId() >= 0) {
request = new ObserveRequest(resultIds.getObjectId());
}
break;
case WRITE_ATTRIBUTES:
request = createWriteAttributeRequest(target, params);
break;
case DELETE:
request = new DeleteRequest(target);
break;
}
return request;
}
/** /**
* @param registration - * @param registration -
* @param request - * @param request -
@ -273,52 +316,66 @@ public class LwM2mTransportRequest {
private void sendRequest(Registration registration, LwM2mClient lwM2MClient, DownlinkRequest request, private void sendRequest(Registration registration, LwM2mClient lwM2MClient, DownlinkRequest request,
long timeoutInMs, Lwm2mClientRpcRequest rpcRequest) { long timeoutInMs, Lwm2mClientRpcRequest rpcRequest) {
context.getServer().send(registration, request, timeoutInMs, (ResponseCallback<?>) response -> { context.getServer().send(registration, request, timeoutInMs, (ResponseCallback<?>) response -> {
if (!lwM2MClient.isInit()) { if (!lwM2MClient.isInit()) {
lwM2MClient.initReadValue(this.serviceImpl, convertPathFromObjectIdToIdVer(request.getPath().toString(), registration)); lwM2MClient.initReadValue(this.handler, convertPathFromObjectIdToIdVer(request.getPath().toString(), registration));
} }
if (CoAP.ResponseCode.isSuccess(((Response) response.getCoapResponse()).getCode())) { if (CoAP.ResponseCode.isSuccess(((Response) response.getCoapResponse()).getCode())) {
this.handleResponse(registration, request.getPath().toString(), response, request, rpcRequest); this.handleResponse(registration, request.getPath().toString(), response, request, rpcRequest);
} else { } else {
String msg = String.format("%s: SendRequest %s: CoapCode - %s Lwm2m code - %d name - %s Resource path - %s", LOG_LW2M_ERROR, request.getClass().getName().toString(), String msg = String.format("%s: SendRequest %s: CoapCode - %s Lwm2m code - %d name - %s Resource path - %s", LOG_LW2M_ERROR, request.getClass().getName().toString(),
((Response) response.getCoapResponse()).getCode(), response.getCode().getCode(), response.getCode().getName(), request.getPath().toString()); ((Response) response.getCoapResponse()).getCode(), response.getCode().getCode(), response.getCode().getName(), request.getPath().toString());
serviceImpl.sendLogsToThingsboard(msg, registration.getId()); handler.sendLogsToThingsboard(msg, registration.getId());
log.error("[{}] [{}], [{}] - [{}] [{}] error SendRequest", request.getClass().getName().toString(), registration.getEndpoint(), log.error("[{}] [{}], [{}] - [{}] [{}] error SendRequest", request.getClass().getName().toString(), registration.getEndpoint(),
((Response) response.getCoapResponse()).getCode(), response.getCode(), request.getPath().toString()); ((Response) response.getCoapResponse()).getCode(), response.getCode(), request.getPath().toString());
if (!lwM2MClient.isInit()) { if (!lwM2MClient.isInit()) {
lwM2MClient.initReadValue(this.serviceImpl, convertPathFromObjectIdToIdVer(request.getPath().toString(), registration)); lwM2MClient.initReadValue(this.handler, convertPathFromObjectIdToIdVer(request.getPath().toString(), registration));
} }
/** Not Found */
if (rpcRequest != null) { if (rpcRequest != null) {
serviceImpl.sentRpcRequest(rpcRequest, response.getCode().getName(), response.getErrorMessage(), LOG_LW2M_ERROR); handler.sentRpcRequest(rpcRequest, response.getCode().getName(), response.getErrorMessage(), LOG_LW2M_ERROR);
} }
/* Not Found /** Not Found
set setClient_fw_version = empty set setClient_fw_info... = empty
*/ **/
if (FR_PATH_RESOURCE_VER_ID.equals(request.getPath().toString()) && lwM2MClient.isUpdateFw()) { if (lwM2MClient.getFwUpdate().isInfoFwSwUpdate()) {
lwM2MClient.setUpdateFw(false); lwM2MClient.getFwUpdate().initReadValue(handler, this, request.getPath().toString());
lwM2MClient.getFrUpdate().setClientFwVersion(""); }
log.warn("updateFirmwareClient1"); if (lwM2MClient.getSwUpdate().isInfoFwSwUpdate()) {
serviceImpl.updateFirmwareClient(lwM2MClient); lwM2MClient.getSwUpdate().initReadValue(handler, this, request.getPath().toString());
}
if (request.getPath().toString().equals(FW_PACKAGE_ID) || request.getPath().toString().equals(SW_PACKAGE_ID)) {
this.afterWriteFwSWUpdateError(registration, request, response.getErrorMessage());
}
if (request.getPath().toString().equals(FW_UPDATE_ID) || request.getPath().toString().equals(SW_INSTALL_ID)) {
this.afterExecuteFwSwUpdateError(registration, request, response.getErrorMessage());
} }
} }
}, e -> { }, e -> {
/* version == null /** version == null
set setClient_fw_version = empty set setClient_fw_info... = empty
*/ **/
if (FR_PATH_RESOURCE_VER_ID.equals(request.getPath().toString()) && lwM2MClient.isUpdateFw()) { if (lwM2MClient.getFwUpdate().isInfoFwSwUpdate()) {
lwM2MClient.setUpdateFw(false); lwM2MClient.getFwUpdate().initReadValue(handler, this, request.getPath().toString());
lwM2MClient.getFrUpdate().setClientFwVersion(""); }
log.warn("updateFirmwareClient2"); if (lwM2MClient.getSwUpdate().isInfoFwSwUpdate()) {
serviceImpl.updateFirmwareClient(lwM2MClient); lwM2MClient.getSwUpdate().initReadValue(handler, this, request.getPath().toString());
}
if (request.getPath().toString().equals(FW_PACKAGE_ID) || request.getPath().toString().equals(SW_PACKAGE_ID)) {
this.afterWriteFwSWUpdateError(registration, request, e.getMessage());
}
if (request.getPath().toString().equals(FW_UPDATE_ID) || request.getPath().toString().equals(SW_INSTALL_ID)) {
this.afterExecuteFwSwUpdateError(registration, request, e.getMessage());
} }
if (!lwM2MClient.isInit()) { if (!lwM2MClient.isInit()) {
lwM2MClient.initReadValue(this.serviceImpl, convertPathFromObjectIdToIdVer(request.getPath().toString(), registration)); lwM2MClient.initReadValue(this.handler, convertPathFromObjectIdToIdVer(request.getPath().toString(), registration));
} }
String msg = String.format("%s: SendRequest %s: Resource path - %s msg error - %s", String msg = String.format("%s: SendRequest %s: Resource path - %s msg error - %s",
LOG_LW2M_ERROR, request.getClass().getName().toString(), request.getPath().toString(), e.getMessage()); LOG_LW2M_ERROR, request.getClass().getName().toString(), request.getPath().toString(), e.getMessage());
serviceImpl.sendLogsToThingsboard(msg, registration.getId()); handler.sendLogsToThingsboard(msg, registration.getId());
log.error("[{}] [{}] - [{}] error SendRequest", request.getClass().getName().toString(), request.getPath().toString(), e.toString()); log.error("[{}] [{}] - [{}] error SendRequest", request.getClass().getName().toString(), request.getPath().toString(), e.toString());
if (rpcRequest != null) { if (rpcRequest != null) {
serviceImpl.sentRpcRequest(rpcRequest, CoAP.CodeClass.ERROR_RESPONSE.name(), e.getMessage(), LOG_LW2M_ERROR); handler.sentRpcRequest(rpcRequest, CoAP.CodeClass.ERROR_RESPONSE.name(), e.getMessage(), LOG_LW2M_ERROR);
} }
}); });
} }
@ -360,11 +417,11 @@ public class LwM2mTransportRequest {
String patn = "/" + objectId + "/" + instanceId + "/" + resourceId; String patn = "/" + objectId + "/" + instanceId + "/" + resourceId;
String msg = String.format(LOG_LW2M_ERROR + ": NumberFormatException: Resource path - %s type - %s value - %s msg error - %s SendRequest to Client", String msg = String.format(LOG_LW2M_ERROR + ": NumberFormatException: Resource path - %s type - %s value - %s msg error - %s SendRequest to Client",
patn, type, value, e.toString()); patn, type, value, e.toString());
serviceImpl.sendLogsToThingsboard(msg, registration.getId()); handler.sendLogsToThingsboard(msg, registration.getId());
log.error("Path: [{}] type: [{}] value: [{}] errorMsg: [{}]]", patn, type, value, e.toString()); log.error("Path: [{}] type: [{}] value: [{}] errorMsg: [{}]]", patn, type, value, e.toString());
if (rpcRequest != null) { if (rpcRequest != null) {
String errorMsg = String.format("NumberFormatException: Resource path - %s type - %s value - %s", patn, type, value); String errorMsg = String.format("NumberFormatException: Resource path - %s type - %s value - %s", patn, type, value);
serviceImpl.sentRpcRequest(rpcRequest, BAD_REQUEST.getName(), errorMsg, LOG_LW2M_ERROR); handler.sentRpcRequest(rpcRequest, BAD_REQUEST.getName(), errorMsg, LOG_LW2M_ERROR);
} }
return null; return null;
} }
@ -372,7 +429,7 @@ public class LwM2mTransportRequest {
private void handleResponse(Registration registration, final String path, LwM2mResponse response, private void handleResponse(Registration registration, final String path, LwM2mResponse response,
DownlinkRequest request, Lwm2mClientRpcRequest rpcRequest) { DownlinkRequest request, Lwm2mClientRpcRequest rpcRequest) {
executorResponse.submit(() -> { responseRequestExecutor.submit(() -> {
try { try {
this.sendResponse(registration, path, response, request, rpcRequest); this.sendResponse(registration, path, response, request, rpcRequest);
} catch (Exception e) { } catch (Exception e) {
@ -391,39 +448,37 @@ public class LwM2mTransportRequest {
private void sendResponse(Registration registration, String path, LwM2mResponse response, private void sendResponse(Registration registration, String path, LwM2mResponse response,
DownlinkRequest request, Lwm2mClientRpcRequest rpcRequest) { DownlinkRequest request, Lwm2mClientRpcRequest rpcRequest) {
String pathIdVer = convertPathFromObjectIdToIdVer(path, registration); String pathIdVer = convertPathFromObjectIdToIdVer(path, registration);
String msgLog = "";
if (response instanceof ReadResponse) { if (response instanceof ReadResponse) {
serviceImpl.onUpdateValueAfterReadResponse(registration, pathIdVer, (ReadResponse) response, rpcRequest); handler.onUpdateValueAfterReadResponse(registration, pathIdVer, (ReadResponse) response, rpcRequest);
} else if (response instanceof CancelObservationResponse) {
log.info("[{}] Path [{}] CancelObservationResponse 3_Send", pathIdVer, response);
} else if (response instanceof DeleteResponse) { } else if (response instanceof DeleteResponse) {
log.info("[{}] Path [{}] DeleteResponse 5_Send", pathIdVer, response); log.warn("[{}] Path [{}] DeleteResponse 5_Send", pathIdVer, response);
} else if (response instanceof DiscoverResponse) { } else if (response instanceof DiscoverResponse) {
log.info("[{}] [{}] - [{}] [{}] Discovery value: [{}]", registration.getEndpoint(), String discoverValue = Link.serialize(((DiscoverResponse)response).getObjectLinks());
((Response) response.getCoapResponse()).getCode(), response.getCode(), msgLog = String.format("%s: type operation: %s path: %s value: %s",
request.getPath().toString(), ((DiscoverResponse) response).getObjectLinks()); LOG_LW2M_INFO, DISCOVER.name(), request.getPath().toString(), discoverValue);
handler.sendLogsToThingsboard(msgLog, registration.getId());
log.warn("DiscoverResponse: [{}]", (DiscoverResponse) response);
if (rpcRequest != null) { if (rpcRequest != null) {
String discoveryMsg = String.format("%s", handler.sentRpcRequest(rpcRequest, response.getCode().getName(), discoverValue, LOG_LW2M_VALUE);
Arrays.stream(((DiscoverResponse) response).getObjectLinks()).collect(Collectors.toSet()));
serviceImpl.sentRpcRequest(rpcRequest, response.getCode().getName(), discoveryMsg, LOG_LW2M_VALUE);
} }
} else if (response instanceof ExecuteResponse) { } else if (response instanceof ExecuteResponse) {
log.info("[{}] Path [{}] ExecuteResponse 7_Send", pathIdVer, response); log.warn("[{}] Path [{}] ExecuteResponse 7_Send", pathIdVer, response);
} else if (response instanceof WriteAttributesResponse) { } else if (response instanceof WriteAttributesResponse) {
log.info("[{}] Path [{}] WriteAttributesResponse 8_Send", pathIdVer, response); log.warn("[{}] Path [{}] WriteAttributesResponse 8_Send", pathIdVer, response);
} else if (response instanceof WriteResponse) { } else if (response instanceof WriteResponse) {
log.info("[{}] Path [{}] WriteResponse 9_Send", pathIdVer, response); log.warn("[{}] Path [{}] WriteResponse 9_Send", pathIdVer, response);
this.infoWriteResponse(registration, response, request); this.infoWriteResponse(registration, response, request);
serviceImpl.onWriteResponseOk(registration, pathIdVer, (WriteRequest) request); handler.onWriteResponseOk(registration, pathIdVer, (WriteRequest) request);
} }
if (rpcRequest != null) { if (rpcRequest != null) {
if (response instanceof ExecuteResponse if (response instanceof ExecuteResponse
|| response instanceof WriteAttributesResponse || response instanceof WriteAttributesResponse
|| response instanceof DeleteResponse) { || response instanceof DeleteResponse) {
rpcRequest.setInfoMsg(null); rpcRequest.setInfoMsg(null);
serviceImpl.sentRpcRequest(rpcRequest, response.getCode().getName(), null, null); handler.sentRpcRequest(rpcRequest, response.getCode().getName(), null, null);
} else if (response instanceof WriteResponse) { } else if (response instanceof WriteResponse) {
serviceImpl.sentRpcRequest(rpcRequest, response.getCode().getName(), null, LOG_LW2M_INFO); handler.sentRpcRequest(rpcRequest, response.getCode().getName(), null, LOG_LW2M_INFO);
} }
} }
} }
@ -447,21 +502,73 @@ public class LwM2mTransportRequest {
Math.min(valueLength, config.getLogMaxLength()))); Math.min(valueLength, config.getLogMaxLength())));
} }
value = valueLength > config.getLogMaxLength() ? value + "..." : value; value = valueLength > config.getLogMaxLength() ? value + "..." : value;
msg = String.format("%s: Update finished successfully: Lwm2m code - %d Resource path - %s length - %s value - %s", msg = String.format("%s: Update finished successfully: Lwm2m code - %d Resource path: %s length: %s value: %s",
LOG_LW2M_INFO, response.getCode().getCode(), request.getPath().toString(), valueLength, value); LOG_LW2M_INFO, response.getCode().getCode(), request.getPath().toString(), valueLength, value);
} else { } else {
value = this.converter.convertValue(singleResource.getValue(), value = this.converter.convertValue(singleResource.getValue(),
singleResource.getType(), ResourceModel.Type.STRING, request.getPath()); singleResource.getType(), ResourceModel.Type.STRING, request.getPath());
msg = String.format("%s: Update finished successfully: Lwm2m code - %d Resource path - %s value - %s", msg = String.format("%s: Update finished successfully. Lwm2m code: %d Resource path: %s value: %s",
LOG_LW2M_INFO, response.getCode().getCode(), request.getPath().toString(), value); LOG_LW2M_INFO, response.getCode().getCode(), request.getPath().toString(), value);
} }
if (msg != null) { if (msg != null) {
serviceImpl.sendLogsToThingsboard(msg, registration.getId()); handler.sendLogsToThingsboard(msg, registration.getId());
log.warn("[{}] [{}] [{}] - [{}] [{}] Update finished successfully: [{}]", request.getClass().getName(), registration.getEndpoint(), if (request.getPath().toString().equals(FW_PACKAGE_ID) || request.getPath().toString().equals(SW_PACKAGE_ID)) {
((Response) response.getCoapResponse()).getCode(), response.getCode(), request.getPath().toString(), value); this.afterWriteSuccessFwSwUpdate(registration, request);
}
} }
} catch (Exception e) { } catch (Exception e) {
log.trace("Fail convert value from request to string. ", e); log.trace("Fail convert value from request to string. ", e);
} }
} }
/**
* After finish operation FwSwUpdate Write (success):
* fw_state/sw_state = DOWNLOADED
* send operation Execute
*/
private void afterWriteSuccessFwSwUpdate(Registration registration, DownlinkRequest request) {
LwM2mClient lwM2MClient = this.lwM2mClientContext.getClientByRegistrationId(registration.getId());
if (request.getPath().toString().equals(FW_PACKAGE_ID) && lwM2MClient.getFwUpdate() != null) {
lwM2MClient.getFwUpdate().setStateUpdate(DOWNLOADED.name());
lwM2MClient.getFwUpdate().sendLogs(handler, WRITE_REPLACE.name(), LOG_LW2M_INFO, null);
}
if (request.getPath().toString().equals(SW_PACKAGE_ID) && lwM2MClient.getSwUpdate() != null) {
lwM2MClient.getSwUpdate().setStateUpdate(DOWNLOADED.name());
lwM2MClient.getSwUpdate().sendLogs(handler, WRITE_REPLACE.name(), LOG_LW2M_INFO, null);
}
}
/**
* After finish operation FwSwUpdate Write (error): fw_state = FAILED
*/
private void afterWriteFwSWUpdateError(Registration registration, DownlinkRequest request, String msgError) {
LwM2mClient lwM2MClient = this.lwM2mClientContext.getClientByRegistrationId(registration.getId());
if (request.getPath().toString().equals(FW_PACKAGE_ID) && lwM2MClient.getFwUpdate() != null) {
lwM2MClient.getFwUpdate().setStateUpdate(FAILED.name());
lwM2MClient.getFwUpdate().sendLogs(handler, WRITE_REPLACE.name(), LOG_LW2M_ERROR, msgError);
}
if (request.getPath().toString().equals(SW_PACKAGE_ID) && lwM2MClient.getSwUpdate() != null) {
lwM2MClient.getSwUpdate().setStateUpdate(FAILED.name());
lwM2MClient.getSwUpdate().sendLogs(handler, WRITE_REPLACE.name(), LOG_LW2M_ERROR, msgError);
}
}
private void afterExecuteFwSwUpdateError(Registration registration, DownlinkRequest request, String msgError) {
LwM2mClient lwM2MClient = this.lwM2mClientContext.getClientByRegistrationId(registration.getId());
if (request.getPath().toString().equals(FW_UPDATE_ID) && lwM2MClient.getFwUpdate() != null) {
lwM2MClient.getFwUpdate().sendLogs(handler, EXECUTE.name(), LOG_LW2M_ERROR, msgError);
}
if (request.getPath().toString().equals(SW_INSTALL_ID) && lwM2MClient.getSwUpdate() != null) {
lwM2MClient.getSwUpdate().sendLogs(handler, EXECUTE.name(), LOG_LW2M_ERROR, msgError);
}
}
private void afterObserveCancel(Registration registration, int observeCancelCnt, String observeCancelMsg, Lwm2mClientRpcRequest rpcRequest) {
handler.sendLogsToThingsboard(observeCancelMsg, registration.getId());
log.warn("[{}]", observeCancelMsg);
if (rpcRequest != null) {
rpcRequest.setInfoMsg(String.format("Count: %d", observeCancelCnt));
handler.sentRpcRequest(rpcRequest, CONTENT.name(), null, LOG_LW2M_INFO);
}
}
} }

39
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelper.java

@ -38,6 +38,7 @@ import org.eclipse.leshan.core.model.InvalidDDFFileException;
import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.model.ObjectModel;
import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.model.ResourceModel;
import org.eclipse.leshan.core.node.codec.CodecException; import org.eclipse.leshan.core.node.codec.CodecException;
import org.eclipse.leshan.core.request.ContentFormat;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.TransportServiceCallback;
import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
@ -54,7 +55,6 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import static org.thingsboard.server.gen.transport.TransportProtos.KeyValueType.BOOLEAN_V; import static org.thingsboard.server.gen.transport.TransportProtos.KeyValueType.BOOLEAN_V;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_TELEMETRY;
@Slf4j @Slf4j
@Component @Component
@ -69,7 +69,7 @@ public class LwM2mTransportServerHelper {
* send to Thingsboard Attribute || Telemetry * send to Thingsboard Attribute || Telemetry
* *
* @param msg - JsonObject: [{name: value}] * @param msg - JsonObject: [{name: value}]
* @return - dummy * @return - dummyWriteReplace {\"targetIdVer\":\"/19_1.0/0/0\",\"value\":0082}
*/ */
private <T> TransportServiceCallback<Void> getPubAckCallbackSendAttrTelemetry(final T msg) { private <T> TransportServiceCallback<Void> getPubAckCallbackSendAttrTelemetry(final T msg) {
return new TransportServiceCallback<>() { return new TransportServiceCallback<>() {
@ -136,16 +136,16 @@ public class LwM2mTransportServerHelper {
} }
/** /**
* * @param value - info about Logs
* @param logMsg - info about Logs
* @return- KeyValueProto for telemetry (Logs) * @return- KeyValueProto for telemetry (Logs)
*/ */
public List<TransportProtos.KeyValueProto> getKvLogyToThingsboard(String logMsg) { public List<TransportProtos.KeyValueProto> getKvStringtoThingsboard(String key, String value) {
List<TransportProtos.KeyValueProto> result = new ArrayList<>(); List<TransportProtos.KeyValueProto> result = new ArrayList<>();
value = value.replaceAll("<", "").replaceAll(">", "");
result.add(TransportProtos.KeyValueProto.newBuilder() result.add(TransportProtos.KeyValueProto.newBuilder()
.setKey(LOG_LW2M_TELEMETRY) .setKey(key)
.setType(TransportProtos.KeyValueType.STRING_V) .setType(TransportProtos.KeyValueType.STRING_V)
.setStringV(logMsg).build()); .setStringV(value).build());
return result; return result;
} }
@ -181,8 +181,7 @@ public class LwM2mTransportServerHelper {
} }
/** /**
* * @param currentType -
* @param currentType -
* @param resourcePath - * @param resourcePath -
* @return * @return
*/ */
@ -204,6 +203,28 @@ public class LwM2mTransportServerHelper {
throw new CodecException("Invalid ResourceModel_Type for resource %s, got %s", resourcePath, currentType); throw new CodecException("Invalid ResourceModel_Type for resource %s, got %s", resourcePath, currentType);
} }
public static ContentFormat convertResourceModelTypeToContentFormat(ResourceModel.Type type) {
switch (type) {
case BOOLEAN:
case STRING:
case TIME:
case INTEGER:
case FLOAT:
return ContentFormat.TLV;
case OPAQUE:
return ContentFormat.OPAQUE;
case OBJLNK:
return ContentFormat.LINK;
default:
}
throw new CodecException("Invalid ResourceModel_Type for %s ContentFormat.", type);
}
public static ContentFormat getContentFormatByResourceModelType(ResourceModel resourceModel, ContentFormat contentFormat) {
return contentFormat.equals(ContentFormat.TLV) ? convertResourceModelTypeToContentFormat(resourceModel.type) :
contentFormat;
}
public static Object getValueFromKvProto(TransportProtos.KeyValueProto kv) { public static Object getValueFromKvProto(TransportProtos.KeyValueProto kv) {
switch (kv.getType()) { switch (kv.getType()) {
case BOOLEAN_V: case BOOLEAN_V:

371
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportUtil.java

@ -43,6 +43,7 @@ import org.eclipse.leshan.server.registration.Registration;
import org.nustaq.serialization.FSTConfiguration; import org.nustaq.serialization.FSTConfiguration;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.firmware.FirmwareUpdateStatus;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.TransportServiceCallback;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
@ -70,6 +71,12 @@ import static org.eclipse.leshan.core.model.ResourceModel.Type.OBJLNK;
import static org.eclipse.leshan.core.model.ResourceModel.Type.OPAQUE; import static org.eclipse.leshan.core.model.ResourceModel.Type.OPAQUE;
import static org.eclipse.leshan.core.model.ResourceModel.Type.STRING; import static org.eclipse.leshan.core.model.ResourceModel.Type.STRING;
import static org.eclipse.leshan.core.model.ResourceModel.Type.TIME; import static org.eclipse.leshan.core.model.ResourceModel.Type.TIME;
import static org.thingsboard.server.common.data.firmware.FirmwareUpdateStatus.DOWNLOADED;
import static org.thingsboard.server.common.data.firmware.FirmwareUpdateStatus.DOWNLOADING;
import static org.thingsboard.server.common.data.firmware.FirmwareUpdateStatus.FAILED;
import static org.thingsboard.server.common.data.firmware.FirmwareUpdateStatus.UPDATED;
import static org.thingsboard.server.common.data.firmware.FirmwareUpdateStatus.UPDATING;
import static org.thingsboard.server.common.data.firmware.FirmwareUpdateStatus.VERIFIED;
import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_KEY; import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_KEY;
import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH; import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH;
@ -97,7 +104,8 @@ public class LwM2mTransportUtil {
public static final long DEFAULT_TIMEOUT = 2 * 60 * 1000L; // 2min in ms public static final long DEFAULT_TIMEOUT = 2 * 60 * 1000L; // 2min in ms
public static final String LOG_LW2M_TELEMETRY = "logLwm2m"; public static final String
LOG_LW2M_TELEMETRY = "LwM2MLog";
public static final String LOG_LW2M_INFO = "info"; public static final String LOG_LW2M_INFO = "info";
public static final String LOG_LW2M_ERROR = "error"; public static final String LOG_LW2M_ERROR = "error";
public static final String LOG_LW2M_WARN = "warn"; public static final String LOG_LW2M_WARN = "warn";
@ -107,11 +115,41 @@ public class LwM2mTransportUtil {
public static final int LWM2M_STRATEGY_2 = 2; public static final int LWM2M_STRATEGY_2 = 2;
public static final String CLIENT_NOT_AUTHORIZED = "Client not authorized"; public static final String CLIENT_NOT_AUTHORIZED = "Client not authorized";
public static final String LWM2M_VERSION_DEFAULT = "1.0";
public static final Integer FR_OBJECT_ID = 5;
public static final Integer FR_RESOURCE_VER_ID = 7; // FirmWare
public static final String FR_PATH_RESOURCE_VER_ID = LWM2M_SEPARATOR_PATH + FR_OBJECT_ID + LWM2M_SEPARATOR_PATH public static final String FW_UPDATE = "Firmware update";
+ "0" + LWM2M_SEPARATOR_PATH + FR_RESOURCE_VER_ID; public static final Integer FW_ID = 5;
// Package W
public static final String FW_PACKAGE_ID = "/5/0/0";
// State R
public static final String FW_STATE_ID = "/5/0/3";
// Update Result R
public static final String FW_RESULT_ID = "/5/0/5";
// PkgName R
public static final String FW_NAME_ID = "/5/0/6";
// PkgVersion R
public static final String FW_VER_ID = "/5/0/7";
// Update E
public static final String FW_UPDATE_ID = "/5/0/2";
// SoftWare
public static final String SW_UPDATE = "Software update";
public static final Integer SW_ID = 9;
// Package W
public static final String SW_PACKAGE_ID = "/9/0/2";
// Update State R
public static final String SW_UPDATE_STATE_ID = "/9/0/7";
// Update Result R
public static final String SW_RESULT_ID = "/9/0/9";
// PkgName R
public static final String SW_NAME_ID = "/9/0/0";
// PkgVersion R
public static final String SW_VER_ID = "/9/0/1";
// Install E
public static final String SW_INSTALL_ID = "/9/0/4";
// Uninstall E
public static final String SW_UN_INSTALL_ID = "/9/0/6";
public enum LwM2mTypeServer { public enum LwM2mTypeServer {
BOOTSTRAP(0, "bootstrap"), BOOTSTRAP(0, "bootstrap"),
@ -144,19 +182,20 @@ public class LwM2mTransportUtil {
*/ */
READ(0, "Read"), READ(0, "Read"),
DISCOVER(1, "Discover"), DISCOVER(1, "Discover"),
OBSERVE_READ_ALL(2, "ObserveReadAll"), DISCOVER_All(2, "DiscoverAll"),
OBSERVE_READ_ALL(3, "ObserveReadAll"),
/** /**
* POST * POST
*/ */
OBSERVE(3, "Observe"), OBSERVE(4, "Observe"),
OBSERVE_CANCEL(4, "ObserveCancel"), OBSERVE_CANCEL(5, "ObserveCancel"),
EXECUTE(5, "Execute"), EXECUTE(6, "Execute"),
/** /**
* Replaces the Object Instance or the Resource(s) with the new value provided in the “Write” operation. (see * Replaces the Object Instance or the Resource(s) with the new value provided in the “Write” operation. (see
* section 5.3.3 of the LW M2M spec). * section 5.3.3 of the LW M2M spec).
* if all resources are to be replaced * if all resources are to be replaced
*/ */
WRITE_REPLACE(6, "WriteReplace"), WRITE_REPLACE(7, "WriteReplace"),
/* /*
PUT PUT
*/ */
@ -165,11 +204,18 @@ public class LwM2mTransportUtil {
* 5.3.3 of the LW M2M spec). * 5.3.3 of the LW M2M spec).
* if this is a partial update request * if this is a partial update request
*/ */
WRITE_UPDATE(7, "WriteUpdate"), WRITE_UPDATE(8, "WriteUpdate"),
WRITE_ATTRIBUTES(8, "WriteAttributes"), WRITE_ATTRIBUTES(9, "WriteAttributes"),
DELETE(9, "Delete"); DELETE(10, "Delete"),
// READ_INFO_FW(10, "ReadInfoFirmware"); // only for RPC
FW_READ_INFO(11, "FirmwareReadInfo"),
FW_UPDATE(12, "FirmwareUpdate"),
FW_UPDATE_URL(14, "FirmwareUpdateUrl"),
SW_READ_INFO(15, "SoftwareReadInfo"),
SW_UPDATE(16, "SoftwareUpdate"),
SW_UPDATE_URL(17, "SoftwareUpdateUrl"),
SW_UNINSTALL(18, "SoftwareUninstall");
public int code; public int code;
public String type; public String type;
@ -189,10 +235,290 @@ public class LwM2mTransportUtil {
} }
} }
/**
* /** State R
* 0: Idle (before downloading or after successful updating)
* 1: Downloading (The data sequence is on the way)
* 2: Downloaded
* 3: Updating
*/
public enum StateFw {
IDLE(0, "Idle"),
DOWNLOADING(1, "Downloading"),
DOWNLOADED(2, "Downloaded"),
UPDATING(3, "Updating");
public int code;
public String type;
StateFw(int code, String type) {
this.code = code;
this.type = type;
}
public static StateFw fromStateFwByType(String type) {
for (StateFw to : StateFw.values()) {
if (to.type.equals(type)) {
return to;
}
}
throw new IllegalArgumentException(String.format("Unsupported FW State type : %s", type));
}
public static StateFw fromStateFwByCode(int code) {
for (StateFw to : StateFw.values()) {
if (to.code == code) {
return to;
}
}
throw new IllegalArgumentException(String.format("Unsupported FW State code : %s", code));
}
}
/**
* FW Update Result
* 0: Initial value. Once the updating process is initiated (Download /Update), this Resource MUST be reset to Initial value.
* 1: Firmware updated successfully.
* 2: Not enough flash memory for the new firmware package.
* 3: Out of RAM during downloading process.
* 4: Connection lost during downloading process.
* 5: Integrity check failure for new downloaded package.
* 6: Unsupported package type.
* 7: Invalid URI.
* 8: Firmware update failed.
* 9: Unsupported protocol.
*/
public enum UpdateResultFw {
INITIAL(0, "Initial value", false),
UPDATE_SUCCESSFULLY(1, "Firmware updated successfully", false),
NOT_ENOUGH(2, "Not enough flash memory for the new firmware package", false),
OUT_OFF_MEMORY(3, "Out of RAM during downloading process", false),
CONNECTION_LOST(4, "Connection lost during downloading process", true),
INTEGRITY_CHECK_FAILURE(5, "Integrity check failure for new downloaded package", true),
UNSUPPORTED_TYPE(6, "Unsupported package type", false),
INVALID_URI(7, "Invalid URI", false),
UPDATE_FAILED(8, "Firmware update failed", false),
UNSUPPORTED_PROTOCOL(9, "Unsupported protocol", false);
public int code;
public String type;
public boolean isAgain;
UpdateResultFw(int code, String type, boolean isAgain) {
this.code = code;
this.type = type;
this.isAgain = isAgain;
}
public static UpdateResultFw fromUpdateResultFwByType(String type) {
for (UpdateResultFw to : UpdateResultFw.values()) {
if (to.type.equals(type)) {
return to;
}
}
throw new IllegalArgumentException(String.format("Unsupported FW Update Result type : %s", type));
}
public static UpdateResultFw fromUpdateResultFwByCode(int code) {
for (UpdateResultFw to : UpdateResultFw.values()) {
if (to.code == code) {
return to;
}
}
throw new IllegalArgumentException(String.format("Unsupported FW Update Result code : %s", code));
}
}
/**
* FirmwareUpdateStatus {
* DOWNLOADING, DOWNLOADED, VERIFIED, UPDATING, UPDATED, FAILED
*/
public static FirmwareUpdateStatus EqualsFwSateToFirmwareUpdateStatus(StateFw stateFw, UpdateResultFw updateResultFw) {
switch (updateResultFw) {
case INITIAL:
switch (stateFw) {
case IDLE:
return VERIFIED;
case DOWNLOADING:
return DOWNLOADING;
case DOWNLOADED:
return DOWNLOADED;
case UPDATING:
return UPDATING;
}
case UPDATE_SUCCESSFULLY:
return UPDATED;
case NOT_ENOUGH:
case OUT_OFF_MEMORY:
case CONNECTION_LOST:
case INTEGRITY_CHECK_FAILURE:
case UNSUPPORTED_TYPE:
case INVALID_URI:
case UPDATE_FAILED:
case UNSUPPORTED_PROTOCOL:
return FAILED;
default:
throw new CodecException("Invalid value stateFw %s %s for FirmwareUpdateStatus.", stateFw.name(), updateResultFw.name());
}
}
/**
* SW Update State R
* 0: INITIAL Before downloading. (see 5.1.2.1)
* 1: DOWNLOAD STARTED The downloading process has started and is on-going. (see 5.1.2.2)
* 2: DOWNLOADED The package has been completely downloaded (see 5.1.2.3)
* 3: DELIVERED In that state, the package has been correctly downloaded and is ready to be installed. (see 5.1.2.4)
* If executing the Install Resource failed, the state remains at DELIVERED.
* If executing the Install Resource was successful, the state changes from DELIVERED to INSTALLED.
* After executing the UnInstall Resource, the state changes to INITIAL.
* 4: INSTALLED
*/
public enum UpdateStateSw {
INITIAL(0, "Initial"),
DOWNLOAD_STARTED(1, "DownloadStarted"),
DOWNLOADED(2, "Downloaded"),
DELIVERED(3, "Delivered"),
INSTALLED(4, "Installed");
public int code;
public String type;
UpdateStateSw(int code, String type) {
this.code = code;
this.type = type;
}
public static UpdateStateSw fromUpdateStateSwByType(String type) {
for (UpdateStateSw to : UpdateStateSw.values()) {
if (to.type.equals(type)) {
return to;
}
}
throw new IllegalArgumentException(String.format("Unsupported SW State type : %s", type));
}
public static UpdateStateSw fromUpdateStateSwByCode(int code) {
for (UpdateStateSw to : UpdateStateSw.values()) {
if (to.code == code) {
return to;
}
}
throw new IllegalArgumentException(String.format("Unsupported SW State type : %s", code));
}
}
/**
* SW Update Result
* Contains the result of downloading or installing/uninstalling the software
* 0: Initial value.
* - Prior to download any new package in the Device, Update Result MUST be reset to this initial value.
* - One side effect of executing the Uninstall resource is to reset Update Result to this initial value "0".
* 1: Downloading.
* - The package downloading process is on-going.
* 2: Software successfully installed.
* 3: Successfully Downloaded and package integrity verified
* (( 4-49, for expansion, of other scenarios))
* ** Failed
* 50: Not enough storage for the new software package.
* 51: Out of memory during downloading process.
* 52: Connection lost during downloading process.
* 53: Package integrity check failure.
* 54: Unsupported package type.
* 56: Invalid URI
* 57: Device defined update error
* 58: Software installation failure
* 59: Uninstallation Failure during forUpdate(arg=0)
* 60-200 : (for expansion, selection to be in blocks depending on new introduction of features)
* This Resource MAY be reported by sending Observe operation.
*/
public enum UpdateResultSw {
INITIAL(0, "Initial value", false),
DOWNLOADING(1, "Downloading", false),
SUCCESSFULLY_INSTALLED(2, "Software successfully installed", false),
SUCCESSFULLY_DOWNLOADED_VERIFIED(3, "Successfully Downloaded and package integrity verified", false),
NOT_ENOUGH_STORAGE(50, "Not enough storage for the new software package", true),
OUT_OFF_MEMORY(51, "Out of memory during downloading process", true),
CONNECTION_LOST(52, "Connection lost during downloading process", false),
PACKAGE_CHECK_FAILURE(53, "Package integrity check failure.", false),
UNSUPPORTED_PACKAGE_TYPE(54, "Unsupported package type", false),
INVALID_URI(56, "Invalid URI", true),
UPDATE_ERROR(57, "Device defined update error", true),
INSTALL_FAILURE(58, "Software installation failure", true),
UN_INSTALL_FAILURE(59, "Uninstallation Failure during forUpdate(arg=0)", true);
public int code;
public String type;
public boolean isAgain;
UpdateResultSw(int code, String type, boolean isAgain) {
this.code = code;
this.type = type;
this.isAgain = isAgain;
}
public static UpdateResultSw fromUpdateResultSwByType(String type) {
for (UpdateResultSw to : UpdateResultSw.values()) {
if (to.type.equals(type)) {
return to;
}
}
throw new IllegalArgumentException(String.format("Unsupported SW Update Result type : %s", type));
}
public static UpdateResultSw fromUpdateResultSwByCode(int code) {
for (UpdateResultSw to : UpdateResultSw.values()) {
if (to.code == code) {
return to;
}
}
throw new IllegalArgumentException(String.format("Unsupported SW Update Result code : %s", code));
}
}
/**
* FirmwareUpdateStatus {
* DOWNLOADING, DOWNLOADED, VERIFIED, UPDATING, UPDATED, FAILED
*/
public static FirmwareUpdateStatus EqualsSwSateToFirmwareUpdateStatus(UpdateStateSw updateStateSw, UpdateResultSw updateResultSw) {
switch (updateResultSw) {
case INITIAL:
switch (updateStateSw) {
case INITIAL:
case DOWNLOAD_STARTED:
return DOWNLOADING;
case DOWNLOADED:
return DOWNLOADED;
case DELIVERED:
return VERIFIED;
}
case DOWNLOADING:
return DOWNLOADING;
case SUCCESSFULLY_INSTALLED:
return UPDATED;
case SUCCESSFULLY_DOWNLOADED_VERIFIED:
return VERIFIED;
case NOT_ENOUGH_STORAGE:
case OUT_OFF_MEMORY:
case CONNECTION_LOST:
case PACKAGE_CHECK_FAILURE:
case UNSUPPORTED_PACKAGE_TYPE:
case INVALID_URI:
case UPDATE_ERROR:
case INSTALL_FAILURE:
case UN_INSTALL_FAILURE:
return FAILED;
default:
throw new CodecException("Invalid value stateFw %s %s for FirmwareUpdateStatus.", updateStateSw.name(), updateResultSw.name());
}
}
public static final String EVENT_AWAKE = "AWAKE"; public static final String EVENT_AWAKE = "AWAKE";
public static final String RESPONSE_REQUEST_CHANNEL = "RESP_REQ";
public static final String RESPONSE_CHANNEL = "RESP"; public static final String RESPONSE_CHANNEL = "RESP";
public static final String OBSERVE_CHANNEL = "OBSERVE";
public static boolean equalsResourceValue(Object valueOld, Object valueNew, ResourceModel.Type type, LwM2mPath resourcePath) throws CodecException { public static boolean equalsResourceValue(Object valueOld, Object valueNew, ResourceModel.Type type, LwM2mPath
resourcePath) throws CodecException {
switch (type) { switch (type) {
case BOOLEAN: case BOOLEAN:
case INTEGER: case INTEGER:
@ -256,7 +582,7 @@ public class LwM2mTransportUtil {
* "/3_1.0/0/9": {"pmax": 45}, "/3_1.2": {ver": "3_1.2"}} * "/3_1.0/0/9": {"pmax": 45}, "/3_1.2": {ver": "3_1.2"}}
*/ */
public static LwM2mClientProfile toLwM2MClientProfile(DeviceProfile deviceProfile) { public static LwM2mClientProfile toLwM2MClientProfile(DeviceProfile deviceProfile) {
if (deviceProfile != null && ((Lwm2mDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration()).getProperties().size() > 0) { if (((Lwm2mDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration()).getProperties().size() > 0) {
Object profile = ((Lwm2mDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration()).getProperties(); Object profile = ((Lwm2mDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration()).getProperties();
try { try {
ObjectMapper mapper = new ObjectMapper(); ObjectMapper mapper = new ObjectMapper();
@ -375,7 +701,8 @@ public class LwM2mTransportUtil {
return StringUtils.join(linkedListOut, ""); return StringUtils.join(linkedListOut, "");
} }
public static <T> TransportServiceCallback<Void> getAckCallback(LwM2mClient lwM2MClient, int requestId, String typeTopic) { public static <T> TransportServiceCallback<Void> getAckCallback(LwM2mClient lwM2MClient,
int requestId, String typeTopic) {
return new TransportServiceCallback<Void>() { return new TransportServiceCallback<Void>() {
@Override @Override
public void onSuccess(Void dummy) { public void onSuccess(Void dummy) {
@ -420,7 +747,8 @@ public class LwM2mTransportUtil {
return null; return null;
} }
public static String validPathIdVer(String pathIdVer, Registration registration) throws IllegalArgumentException { public static String validPathIdVer(String pathIdVer, Registration registration) throws
IllegalArgumentException {
if (!pathIdVer.contains(LWM2M_SEPARATOR_PATH)) { if (!pathIdVer.contains(LWM2M_SEPARATOR_PATH)) {
throw new IllegalArgumentException(String.format("Error:")); throw new IllegalArgumentException(String.format("Error:"));
} else { } else {
@ -436,6 +764,7 @@ public class LwM2mTransportUtil {
public static String convertPathFromObjectIdToIdVer(String path, Registration registration) { public static String convertPathFromObjectIdToIdVer(String path, Registration registration) {
String ver = registration.getSupportedObject().get(new LwM2mPath(path).getObjectId()); String ver = registration.getSupportedObject().get(new LwM2mPath(path).getObjectId());
ver = ver != null ? ver : LWM2M_VERSION_DEFAULT;
try { try {
String[] keyArray = path.split(LWM2M_SEPARATOR_PATH); String[] keyArray = path.split(LWM2M_SEPARATOR_PATH);
if (keyArray.length > 1) { if (keyArray.length > 1) {
@ -531,7 +860,7 @@ public class LwM2mTransportUtil {
case "ObjectLink": case "ObjectLink":
return OBJLNK; return OBJLNK;
default: default:
return null; return null;
} }
} }
} }

2
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mVersionedModelProvider.java

@ -82,7 +82,7 @@ public class LwM2mVersionedModelProvider implements LwM2mModelProvider {
if (objectModel != null) if (objectModel != null)
return objectModel.resources.get(resourceId); return objectModel.resources.get(resourceId);
else else
log.warn("TbResources (Object model) with id [{}/0/{}] not found on the server", objectId, resourceId); log.trace("TbResources (Object model) with id [{}/0/{}] not found on the server", objectId, resourceId);
return null; return null;
} catch (Exception e) { } catch (Exception e) {
log.error("", e); log.error("", e);

1
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/adaptors/LwM2MJsonAdaptor.java

@ -64,7 +64,6 @@ public class LwM2MJsonAdaptor implements LwM2MTransportAdaptor {
} }
return result.build(); return result.build();
} catch (RuntimeException e) { } catch (RuntimeException e) {
log.warn("Failed to decode get attributes request", e);
throw new AdaptorException(e); throw new AdaptorException(e);
} }
} }

109
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java

@ -18,7 +18,11 @@ package org.thingsboard.server.transport.lwm2m.server.client;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.model.ObjectModel;
import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.model.ResourceModel;
import org.eclipse.leshan.core.node.LwM2mMultipleResource;
import org.eclipse.leshan.core.node.LwM2mObject;
import org.eclipse.leshan.core.node.LwM2mObjectInstance;
import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.LwM2mPath;
import org.eclipse.leshan.core.node.LwM2mResource; import org.eclipse.leshan.core.node.LwM2mResource;
import org.eclipse.leshan.core.node.LwM2mSingleResource; import org.eclipse.leshan.core.node.LwM2mSingleResource;
@ -28,9 +32,9 @@ import org.eclipse.leshan.server.security.SecurityInfo;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
import org.thingsboard.server.common.data.firmware.FirmwareType;
import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto;
import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto; import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto;
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg;
import org.thingsboard.server.transport.lwm2m.server.DefaultLwM2MTransportMsgHandler; import org.thingsboard.server.transport.lwm2m.server.DefaultLwM2MTransportMsgHandler;
import org.thingsboard.server.transport.lwm2m.server.LwM2mQueuedRequest; import org.thingsboard.server.transport.lwm2m.server.LwM2mQueuedRequest;
import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl;
@ -47,11 +51,14 @@ import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.eclipse.leshan.core.model.ResourceModel.Type.OPAQUE;
import static org.eclipse.leshan.core.model.ResourceModel.Type.STRING;
import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH; import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.TRANSPORT_DEFAULT_LWM2M_VERSION; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.TRANSPORT_DEFAULT_LWM2M_VERSION;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromIdVerToObjectId; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromIdVerToObjectId;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.getVerFromPathIdVerOrId; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromObjectIdToIdVer;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.equalsResourceTypeGetSimpleName; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.equalsResourceTypeGetSimpleName;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.getVerFromPathIdVerOrId;
@Slf4j @Slf4j
public class LwM2mClient implements Cloneable { public class LwM2mClient implements Cloneable {
@ -75,25 +82,27 @@ public class LwM2mClient implements Cloneable {
private UUID profileId; private UUID profileId;
@Getter @Getter
@Setter @Setter
private volatile LwM2mFirmwareUpdate frUpdate; private volatile LwM2mFwSwUpdate fwUpdate;
@Getter
@Setter
private volatile LwM2mFwSwUpdate swUpdate;
@Getter @Getter
@Setter @Setter
private Registration registration; private Registration registration;
private ValidateDeviceCredentialsResponse credentials; private ValidateDeviceCredentialsResponse credentials;
@Getter @Getter
private final Map<String, ResourceValue> resources; private final Map<String, ResourceValue> resources;
@Getter @Getter
private final Map<String, TsKvProto> delayedRequests; private final Map<String, TsKvProto> delayedRequests;
@Getter @Getter
@Setter
private final List<String> pendingReadRequests; private final List<String> pendingReadRequests;
@Getter @Getter
private final Queue<LwM2mQueuedRequest> queuedRequests; private final Queue<LwM2mQueuedRequest> queuedRequests;
@Getter @Getter
private boolean init; private boolean init;
@Getter
@Setter
private volatile boolean updateFw;
public Object clone() throws CloneNotSupportedException { public Object clone() throws CloneNotSupportedException {
return super.clone(); return super.clone();
@ -110,9 +119,10 @@ public class LwM2mClient implements Cloneable {
this.profileId = profileId; this.profileId = profileId;
this.sessionId = sessionId; this.sessionId = sessionId;
this.init = false; this.init = false;
this.updateFw = false;
this.queuedRequests = new ConcurrentLinkedQueue<>(); this.queuedRequests = new ConcurrentLinkedQueue<>();
this.frUpdate = new LwM2mFirmwareUpdate();
this.fwUpdate = new LwM2mFwSwUpdate(this, FirmwareType.FIRMWARE);
this.swUpdate = new LwM2mFwSwUpdate(this, FirmwareType.SOFTWARE);
if (this.credentials != null && this.credentials.hasDeviceInfo()) { if (this.credentials != null && this.credentials.hasDeviceInfo()) {
this.session = createSession(nodeId, sessionId, credentials); this.session = createSession(nodeId, sessionId, credentials);
this.deviceId = new UUID(session.getDeviceIdMSB(), session.getDeviceIdLSB()); this.deviceId = new UUID(session.getDeviceIdMSB(), session.getDeviceIdLSB());
@ -165,15 +175,15 @@ public class LwM2mClient implements Cloneable {
.build(); .build();
} }
public boolean saveResourceValue(String pathRez, LwM2mResource rez, LwM2mModelProvider modelProvider) { public boolean saveResourceValue(String pathRezIdVer, LwM2mResource rez, LwM2mModelProvider modelProvider) {
if (this.resources.get(pathRez) != null && this.resources.get(pathRez).getResourceModel() != null) { if (this.resources.get(pathRezIdVer) != null && this.resources.get(pathRezIdVer).getResourceModel() != null) {
this.resources.get(pathRez).setLwM2mResource(rez); this.resources.get(pathRezIdVer).setLwM2mResource(rez);
return true; return true;
} else { } else {
LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(pathRez)); LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(pathRezIdVer));
ResourceModel resourceModel = modelProvider.getObjectModel(registration).getResourceModel(pathIds.getObjectId(), pathIds.getResourceId()); ResourceModel resourceModel = modelProvider.getObjectModel(registration).getResourceModel(pathIds.getObjectId(), pathIds.getResourceId());
if (resourceModel != null) { if (resourceModel != null) {
this.resources.put(pathRez, new ResourceValue(rez, resourceModel)); this.resources.put(pathRezIdVer, new ResourceValue(rez, resourceModel));
return true; return true;
} else { } else {
return false; return false;
@ -181,6 +191,24 @@ public class LwM2mClient implements Cloneable {
} }
} }
public Object getResourceValue(String pathRezIdVer, String pathRezId) {
String pathRez = pathRezIdVer == null ? convertPathFromObjectIdToIdVer(pathRezId, this.registration) : pathRezIdVer;
if (this.resources.get(pathRez) != null) {
return this.resources.get(pathRez).getLwM2mResource().isMultiInstances() ?
this.resources.get(pathRez).getLwM2mResource().getValues() :
this.resources.get(pathRez).getLwM2mResource().getValue();
}
return null;
}
public Object getResourceName (String pathRezIdVer, String pathRezId) {
String pathRez = pathRezIdVer == null ? convertPathFromObjectIdToIdVer(pathRezId, this.registration) : pathRezIdVer;
if (this.resources.get(pathRez) != null) {
return this.resources.get(pathRez).getResourceModel().name;
}
return null;
}
public ResourceModel getResourceModel(String pathIdVer, LwM2mModelProvider modelProvider) { public ResourceModel getResourceModel(String pathIdVer, LwM2mModelProvider modelProvider) {
LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(pathIdVer)); LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(pathIdVer));
String verSupportedObject = registration.getSupportedObject().get(pathIds.getObjectId()); String verSupportedObject = registration.getSupportedObject().get(pathIds.getObjectId());
@ -189,8 +217,55 @@ public class LwM2mClient implements Cloneable {
.getResourceModel(pathIds.getObjectId(), pathIds.getResourceId()) : null; .getResourceModel(pathIds.getObjectId(), pathIds.getResourceId()) : null;
} }
public Collection<LwM2mResource> getNewOneResourceForInstance(String pathRezIdVer, Object params, LwM2mModelProvider modelProvider, public ObjectModel getObjectModel(String pathIdVer, LwM2mModelProvider modelProvider) {
LwM2mValueConverterImpl converter) { LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(pathIdVer));
String verSupportedObject = registration.getSupportedObject().get(pathIds.getObjectId());
String verRez = getVerFromPathIdVerOrId(pathIdVer);
return verRez == null || verRez.equals(verSupportedObject) ? modelProvider.getObjectModel(registration)
.getObjectModel(pathIds.getObjectId()) : null;
}
public String objectToString (LwM2mObject lwM2mObject, LwM2mValueConverterImpl converter, String pathIdVer) {
StringBuilder builder = new StringBuilder();
builder.append("LwM2mObject [id=").append(lwM2mObject.getId()).append(", instances={");
lwM2mObject.getInstances().forEach((instId, inst) -> {
builder.append(instId).append("=").append(this.instanceToString(inst, converter, pathIdVer)).append(", ");
});
int startInd = builder.lastIndexOf(", ");
if (startInd > 0) {
builder.delete(startInd, startInd + 2);
}
builder.append("}]");
return builder.toString();
}
public String instanceToString (LwM2mObjectInstance objectInstance, LwM2mValueConverterImpl converter, String pathIdVer) {
StringBuilder builder = new StringBuilder();
builder.append("LwM2mObjectInstance [id=").append(objectInstance.getId()).append(", resources={");
objectInstance.getResources().forEach((resId, res) -> {
builder.append(resId).append("=").append(this.resourceToString (res, converter, pathIdVer)).append(", ");
});
int startInd = builder.lastIndexOf(", ");
if (startInd > 0) {
builder.delete(startInd, startInd + 2);
}
builder.append("}]");
return builder.toString();
}
public String resourceToString (LwM2mResource lwM2mResource, LwM2mValueConverterImpl converter, String pathIdVer) {
if (!OPAQUE.equals(lwM2mResource.getType())) {
return lwM2mResource.isMultiInstances() ? ((LwM2mMultipleResource) lwM2mResource).toString() :
((LwM2mSingleResource) lwM2mResource).toString();
}
else {
return String.format("LwM2mSingleResource [id=%s, value=%s, type=%s]", lwM2mResource.getId(),
converter.convertValue(lwM2mResource.getValue(),
OPAQUE, STRING, new LwM2mPath(convertPathFromIdVerToObjectId(pathIdVer))), lwM2mResource.getType().name());
}
}
public Collection<LwM2mResource> getNewResourceForInstance(String pathRezIdVer, Object params, LwM2mModelProvider modelProvider,
LwM2mValueConverterImpl converter) {
LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(pathRezIdVer)); LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(pathRezIdVer));
Collection<LwM2mResource> resources = ConcurrentHashMap.newKeySet(); Collection<LwM2mResource> resources = ConcurrentHashMap.newKeySet();
Map<Integer, ResourceModel> resourceModels = modelProvider.getObjectModel(registration) Map<Integer, ResourceModel> resourceModels = modelProvider.getObjectModel(registration)
@ -204,8 +279,8 @@ public class LwM2mClient implements Cloneable {
return resources; return resources;
} }
public Collection<LwM2mResource> getNewManyResourcesForInstance(String pathRezIdVer, Object params, LwM2mModelProvider modelProvider, public Collection<LwM2mResource> getNewResourcesForInstance(String pathRezIdVer, Object params, LwM2mModelProvider modelProvider,
LwM2mValueConverterImpl converter) { LwM2mValueConverterImpl converter) {
LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(pathRezIdVer)); LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(pathRezIdVer));
Collection<LwM2mResource> resources = ConcurrentHashMap.newKeySet(); Collection<LwM2mResource> resources = ConcurrentHashMap.newKeySet();
Map<Integer, ResourceModel> resourceModels = modelProvider.getObjectModel(registration) Map<Integer, ResourceModel> resourceModels = modelProvider.getObjectModel(registration)

2
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContext.java

@ -55,7 +55,7 @@ public interface LwM2mClientContext {
Map<UUID, LwM2mClientProfile> setProfiles(Map<UUID, LwM2mClientProfile> profiles); Map<UUID, LwM2mClientProfile> setProfiles(Map<UUID, LwM2mClientProfile> profiles);
LwM2mClientProfile toClientProfile(DeviceProfile deviceProfile); LwM2mClientProfile profileUpdate(DeviceProfile deviceProfile);
Set<String> getSupportedIdVerInClient(Registration registration); Set<String> getSupportedIdVerInClient(Registration registration);

25
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java

@ -16,6 +16,7 @@
package org.thingsboard.server.transport.lwm2m.server.client; package org.thingsboard.server.transport.lwm2m.server.client;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.LwM2mPath;
import org.eclipse.leshan.server.registration.Registration; import org.eclipse.leshan.server.registration.Registration;
import org.eclipse.leshan.server.security.EditableSecurityStore; import org.eclipse.leshan.server.security.EditableSecurityStore;
@ -40,6 +41,7 @@ import java.util.concurrent.ConcurrentHashMap;
import static org.eclipse.leshan.core.SecurityMode.NO_SEC; import static org.eclipse.leshan.core.SecurityMode.NO_SEC;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromObjectIdToIdVer; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromObjectIdToIdVer;
@Slf4j
@Service @Service
@TbLwM2mTransportComponent @TbLwM2mTransportComponent
@RequiredArgsConstructor @RequiredArgsConstructor
@ -112,8 +114,12 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
EndpointSecurityInfo securityInfo = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfo(endpoint, LwM2mTransportUtil.LwM2mTypeServer.CLIENT); EndpointSecurityInfo securityInfo = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfo(endpoint, LwM2mTransportUtil.LwM2mTypeServer.CLIENT);
if (securityInfo.getSecurityMode() != null) { if (securityInfo.getSecurityMode() != null) {
if (securityInfo.getDeviceProfile() != null) { if (securityInfo.getDeviceProfile() != null) {
toClientProfile(securityInfo.getDeviceProfile()); UUID profileUuid = profileUpdate(securityInfo.getDeviceProfile())!= null ?
UUID profileUuid = securityInfo.getDeviceProfile().getUuidId(); securityInfo.getDeviceProfile().getUuidId() : null;
// TODO: for tests bug.
if (profileUuid== null) {
log.warn("input parameters toClientProfile if the result is null: [{}]", securityInfo.getDeviceProfile());
}
LwM2mClient client; LwM2mClient client;
if (securityInfo.getSecurityInfo() != null) { if (securityInfo.getSecurityInfo() != null) {
client = new LwM2mClient(context.getNodeId(), securityInfo.getSecurityInfo().getEndpoint(), client = new LwM2mClient(context.getNodeId(), securityInfo.getSecurityInfo().getEndpoint(),
@ -141,7 +147,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
LwM2mClient client = new LwM2mClient(context.getNodeId(), registration.getEndpoint(), null, null, credentials, credentials.getDeviceProfile().getUuidId(), UUID.randomUUID()); LwM2mClient client = new LwM2mClient(context.getNodeId(), registration.getEndpoint(), null, null, credentials, credentials.getDeviceProfile().getUuidId(), UUID.randomUUID());
lwM2mClientsByEndpoint.put(registration.getEndpoint(), client); lwM2mClientsByEndpoint.put(registration.getEndpoint(), client);
lwM2mClientsByRegistrationId.put(registration.getId(), client); lwM2mClientsByRegistrationId.put(registration.getId(), client);
toClientProfile(credentials.getDeviceProfile()); profileUpdate(credentials.getDeviceProfile());
} }
@Override @Override
@ -170,13 +176,16 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
} }
@Override @Override
public LwM2mClientProfile toClientProfile(DeviceProfile deviceProfile) { public LwM2mClientProfile profileUpdate(DeviceProfile deviceProfile) {
LwM2mClientProfile lwM2MClientProfile = profiles.get(deviceProfile.getUuidId()); LwM2mClientProfile lwM2MClientProfile = deviceProfile != null ?
if (lwM2MClientProfile == null) { LwM2mTransportUtil.toLwM2MClientProfile(deviceProfile) : null;
lwM2MClientProfile = LwM2mTransportUtil.toLwM2MClientProfile(deviceProfile); if (lwM2MClientProfile != null) {
profiles.put(deviceProfile.getUuidId(), lwM2MClientProfile); profiles.put(deviceProfile.getUuidId(), lwM2MClientProfile);
return lwM2MClientProfile;
}
else {
return null;
} }
return lwM2MClientProfile;
} }
/** /**

27
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mFirmwareUpdate.java

@ -1,27 +0,0 @@
/**
* Copyright © 2016-2021 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.lwm2m.server.client;
import lombok.Data;
import java.util.UUID;
@Data
public class LwM2mFirmwareUpdate {
private volatile String clientFwVersion;
private volatile String currentFwVersion;
private volatile UUID currentFwId;
}

355
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mFwSwUpdate.java

@ -0,0 +1,355 @@
/**
* Copyright © 2016-2021 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.lwm2m.server.client;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.leshan.core.request.ContentFormat;
import org.thingsboard.server.common.data.firmware.FirmwareType;
import org.thingsboard.server.common.data.firmware.FirmwareUpdateStatus;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.transport.lwm2m.server.DefaultLwM2MTransportMsgHandler;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportRequest;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
import static org.thingsboard.server.common.data.firmware.FirmwareKey.STATE;
import static org.thingsboard.server.common.data.firmware.FirmwareType.FIRMWARE;
import static org.thingsboard.server.common.data.firmware.FirmwareType.SOFTWARE;
import static org.thingsboard.server.common.data.firmware.FirmwareUpdateStatus.UPDATING;
import static org.thingsboard.server.common.data.firmware.FirmwareUtil.getAttributeKey;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_NAME_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_PACKAGE_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_RESULT_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_STATE_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_UPDATE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_UPDATE_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_VER_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_ERROR;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_INFO;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.EXECUTE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.OBSERVE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.WRITE_REPLACE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_INSTALL_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_NAME_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_PACKAGE_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_RESULT_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_UN_INSTALL_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_UPDATE;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_UPDATE_STATE_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_VER_ID;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromObjectIdToIdVer;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.splitCamelCaseString;
@Slf4j
public class LwM2mFwSwUpdate {
// 5/0/6 PkgName
// 9/0/0 PkgName
@Getter
@Setter
private volatile String currentTitle;
// 5/0/7 PkgVersion
// 9/0/1 PkgVersion
@Getter
@Setter
private volatile String currentVersion;
@Getter
@Setter
private volatile UUID currentId;
@Getter
@Setter
private volatile String stateUpdate;
@Getter
private String pathPackageId;
@Getter
private String pathStateId;
@Getter
private String pathResultId;
@Getter
private String pathNameId;
@Getter
private String pathVerId;
@Getter
private String pathInstallId;
@Getter
private String pathUnInstallId;
@Getter
private String wUpdate;
@Getter
@Setter
private volatile boolean infoFwSwUpdate = false;
private final FirmwareType type;
@Getter
LwM2mClient lwM2MClient;
@Getter
@Setter
private final List<String> pendingInfoRequestsStart;
public LwM2mFwSwUpdate(LwM2mClient lwM2MClient, FirmwareType type) {
this.lwM2MClient = lwM2MClient;
this.pendingInfoRequestsStart = new CopyOnWriteArrayList<>();
this.type = type;
this.stateUpdate = null;
this.initPathId();
}
private void initPathId() {
if (FIRMWARE.equals(this.type)) {
this.pathPackageId = FW_PACKAGE_ID;
this.pathStateId = FW_STATE_ID;
this.pathResultId = FW_RESULT_ID;
this.pathNameId = FW_NAME_ID;
this.pathVerId = FW_VER_ID;
this.pathInstallId = FW_UPDATE_ID;
this.wUpdate = FW_UPDATE;
} else if (SOFTWARE.equals(this.type)) {
this.pathPackageId = SW_PACKAGE_ID;
this.pathStateId = SW_UPDATE_STATE_ID;
this.pathResultId = SW_RESULT_ID;
this.pathNameId = SW_NAME_ID;
this.pathVerId = SW_VER_ID;
this.pathInstallId = SW_INSTALL_ID;
this.pathUnInstallId = SW_UN_INSTALL_ID;
this.wUpdate = SW_UPDATE;
}
}
public void initReadValue(DefaultLwM2MTransportMsgHandler handler, LwM2mTransportRequest request, String pathIdVer) {
if (pathIdVer != null) {
this.pendingInfoRequestsStart.remove(pathIdVer);
}
if (this.pendingInfoRequestsStart.size() == 0) {
this.infoFwSwUpdate = false;
if (!FirmwareUpdateStatus.DOWNLOADING.name().equals(this.stateUpdate)) {
boolean conditionalStart = this.type.equals(FIRMWARE) ? this.conditionalFwUpdateStart() :
this.conditionalSwUpdateStart();
if (conditionalStart) {
this.writeFwSwWare(handler, request);
}
}
}
}
/**
* Send FsSw to Lwm2mClient:
* before operation Write: fw_state = DOWNLOADING
*/
private void writeFwSwWare(DefaultLwM2MTransportMsgHandler handler, LwM2mTransportRequest request) {
this.stateUpdate = FirmwareUpdateStatus.DOWNLOADING.name();
// this.observeStateUpdate();
this.sendLogs(handler, WRITE_REPLACE.name(), LOG_LW2M_INFO, null);
int chunkSize = 0;
int chunk = 0;
byte[] firmwareChunk = handler.firmwareDataCache.get(this.currentId.toString(), chunkSize, chunk);
String targetIdVer = convertPathFromObjectIdToIdVer(this.pathPackageId, this.lwM2MClient.getRegistration());
request.sendAllRequest(lwM2MClient.getRegistration(), targetIdVer, WRITE_REPLACE, ContentFormat.OPAQUE.getName(),
firmwareChunk, handler.config.getTimeout(), null);
}
public void sendLogs(DefaultLwM2MTransportMsgHandler handler, String typeOper, String typeInfo, String msgError) {
this.sendSateOnThingsBoard(handler);
String msg = String.format("%s: %s, %s, pkgVer: %s: pkgName - %s state - %s.",
typeInfo, this.wUpdate, typeOper, this.currentVersion, this.currentTitle, this.stateUpdate);
if (LOG_LW2M_ERROR.equals(typeInfo)) {
msg = String.format("%s Error: %s", msg, msgError);
}
handler.sendLogsToThingsboard(msg, lwM2MClient.getRegistration().getId());
}
/**
* After inspection Update Result
* fw_state/sw_state = UPDATING
* send execute
*/
public void executeFwSwWare(DefaultLwM2MTransportMsgHandler handler, LwM2mTransportRequest request) {
this.setStateUpdate(UPDATING.name());
this.sendLogs(handler, EXECUTE.name(), LOG_LW2M_INFO, null);
request.sendAllRequest(this.lwM2MClient.getRegistration(), this.pathInstallId, EXECUTE, ContentFormat.TLV.getName(),
null, 0, null);
}
/**
* Firmware start:
* -- Если Update Result -errors (более 1) - Это означает что пред. апдейт не прошел.
* - Запускаем апдейт в независимости от состяния прошивки и ее версии.
* -- Если Update Result - не errors (менее или равно 1) и ver не пустой - Это означает что пред. апдейт прошел.
* -- Если Update Result - не errors и ver пустой - Это означает что апдейта еще не было.
* - Проверяем поменялась ли версия и запускаем новый апдейт.
*/
private boolean conditionalFwUpdateStart() {
Long updateResultFw = (Long) this.lwM2MClient.getResourceValue(null, this.pathResultId);
// #1/#2
return updateResultFw > LwM2mTransportUtil.UpdateResultFw.UPDATE_SUCCESSFULLY.code ||
(
(updateResultFw <= LwM2mTransportUtil.UpdateResultFw.UPDATE_SUCCESSFULLY.code
) &&
(
(this.currentVersion != null && !this.currentVersion.equals(this.lwM2MClient.getResourceValue(null, this.pathVerId))) ||
(this.currentTitle != null && !this.currentTitle.equals(this.lwM2MClient.getResourceValue(null, this.pathNameId)))
)
);
}
/**
* Before operation Execute inspection Update Result :
* 0 - Initial value
*/
public boolean conditionalFwExecuteStart() {
Long updateResult = (Long) this.lwM2MClient.getResourceValue(null, this.pathResultId);
return LwM2mTransportUtil.UpdateResultFw.INITIAL.code == updateResult;
}
/**
* After operation Execute success inspection Update Result :
* 1 - "Firmware updated successfully"
*/
public boolean conditionalFwExecuteAfterSuccess() {
Long updateResult = (Long) this.lwM2MClient.getResourceValue(null, this.pathResultId);
return LwM2mTransportUtil.UpdateResultFw.UPDATE_SUCCESSFULLY.code == updateResult;
}
/**
* After operation Execute success inspection Update Result :
* > 1 error: "Firmware updated successfully"
*/
public boolean conditionalFwExecuteAfterError() {
Long updateResult = (Long) this.lwM2MClient.getResourceValue(null, this.pathResultId);
return LwM2mTransportUtil.UpdateResultFw.UPDATE_SUCCESSFULLY.code < updateResult;
}
/**
* Software start
* -- Если Update Result -errors (равно и более 50) - Это означает что пред. апдейт не прошел.
* * - Запускаем апдейт в независимости от состяния прошивки и ее версии.
* -- Если Update Result - не errors (менее 50) и ver не пустой - Это означает что пред. апдейт прошел.
* -- Если Update Result - не errors и ver пустой - Это означает что апдейта еще не было или пред. апдейт UnInstall
* -- Если Update Result - не errors и ver не пустой - Это означает что пред. апдейт UnInstall
* - Проверяем поменялась ли версия и запускаем новый апдейт.
*/
private boolean conditionalSwUpdateStart() {
Long updateResultSw = (Long) this.lwM2MClient.getResourceValue(null, this.pathResultId);
// #1/#2
return updateResultSw >= LwM2mTransportUtil.UpdateResultSw.NOT_ENOUGH_STORAGE.code ||
(
(updateResultSw <= LwM2mTransportUtil.UpdateResultSw.NOT_ENOUGH_STORAGE.code
) &&
(
(this.currentVersion != null && !this.currentVersion.equals(this.lwM2MClient.getResourceValue(null, this.pathVerId))) ||
(this.currentTitle != null && !this.currentTitle.equals(this.lwM2MClient.getResourceValue(null, this.pathNameId)))
)
);
}
/**
* Before operation Execute inspection Update Result :
* 3 - Successfully Downloaded and package integrity verified
*/
public boolean conditionalSwUpdateExecute() {
Long updateResult = (Long) this.lwM2MClient.getResourceValue(null, this.pathResultId);
return LwM2mTransportUtil.UpdateResultSw.SUCCESSFULLY_DOWNLOADED_VERIFIED.code == updateResult;
}
/**
* After finish operation Execute (success):
* -- inspection Update Result:
* ---- FW если Update Result == 1 ("Firmware updated successfully") или SW если Update Result == 2 ("Software successfully installed.")
* -- fw_state/sw_state = UPDATED
*
* After finish operation Execute (error):
* -- inspection updateResult and send to thingsboard info about error
* --- send to telemetry ( key - this is name Update Result in model) (
* -- fw_state/sw_state = FAILED
*/
public void finishFwSwUpdate(DefaultLwM2MTransportMsgHandler handler, boolean success) {
Long updateResult = (Long) this.lwM2MClient.getResourceValue(null, this.pathResultId);
String value = FIRMWARE.equals(this.type) ? LwM2mTransportUtil.UpdateResultFw.fromUpdateResultFwByCode(updateResult.intValue()).type :
LwM2mTransportUtil.UpdateResultSw.fromUpdateResultSwByCode(updateResult.intValue()).type;
String key = splitCamelCaseString((String) this.lwM2MClient.getResourceName(null, this.pathResultId));
if (success) {
this.stateUpdate = FirmwareUpdateStatus.UPDATED.name();
this.sendLogs(handler, EXECUTE.name(), LOG_LW2M_INFO, null);
} else {
this.stateUpdate = FirmwareUpdateStatus.FAILED.name();
this.sendLogs(handler, EXECUTE.name(), LOG_LW2M_ERROR, value);
}
handler.helper.sendParametersOnThingsboardTelemetry(
handler.helper.getKvStringtoThingsboard(key, value), this.lwM2MClient.getSession());
}
/**
* After operation Execute success inspection Update Result :
* 2 - "Software successfully installed."
*/
public boolean conditionalSwExecuteAfterSuccess() {
Long updateResult = (Long) this.lwM2MClient.getResourceValue(null, this.pathResultId);
return LwM2mTransportUtil.UpdateResultSw.SUCCESSFULLY_INSTALLED.code == updateResult;
}
/**
* After operation Execute success inspection Update Result :
* >= 50 - error "NOT_ENOUGH_STORAGE"
*/
public boolean conditionalSwExecuteAfterError() {
Long updateResult = (Long) this.lwM2MClient.getResourceValue(null, this.pathResultId);
return LwM2mTransportUtil.UpdateResultSw.NOT_ENOUGH_STORAGE.code <= updateResult;
}
private void observeStateUpdate(DefaultLwM2MTransportMsgHandler handler, LwM2mTransportRequest request) {
request.sendAllRequest(lwM2MClient.getRegistration(),
convertPathFromObjectIdToIdVer(this.pathStateId, this.lwM2MClient.getRegistration()), OBSERVE,
null, null, 0, null);
request.sendAllRequest(lwM2MClient.getRegistration(),
convertPathFromObjectIdToIdVer(this.pathResultId, this.lwM2MClient.getRegistration()), OBSERVE,
null, null, 0, null);
}
public void sendSateOnThingsBoard(DefaultLwM2MTransportMsgHandler handler) {
if (StringUtils.trimToNull(this.stateUpdate) != null) {
List<TransportProtos.KeyValueProto> result = new ArrayList<>();
TransportProtos.KeyValueProto.Builder kvProto = TransportProtos.KeyValueProto.newBuilder().setKey(getAttributeKey(this.type, STATE));
kvProto.setType(TransportProtos.KeyValueType.STRING_V).setStringV(stateUpdate);
result.add(kvProto.build());
handler.helper.sendParametersOnThingsboardTelemetry(result,
handler.getSessionInfoOrCloseSession(this.lwM2MClient.getRegistration()));
}
}
public void sendReadObserveInfo(LwM2mTransportRequest request) {
this.infoFwSwUpdate = true;
this.pendingInfoRequestsStart.add(convertPathFromObjectIdToIdVer(
this.pathVerId, this.lwM2MClient.getRegistration()));
this.pendingInfoRequestsStart.add(convertPathFromObjectIdToIdVer(
this.pathNameId, this.lwM2MClient.getRegistration()));
this.pendingInfoRequestsStart.add(convertPathFromObjectIdToIdVer(
this.pathStateId, this.lwM2MClient.getRegistration()));
this.pendingInfoRequestsStart.add(convertPathFromObjectIdToIdVer(
this.pathResultId, this.lwM2MClient.getRegistration()));
this.pendingInfoRequestsStart.forEach(pathIdVer -> {
request.sendAllRequest(this.lwM2MClient.getRegistration(), pathIdVer, OBSERVE, ContentFormat.TLV.getName(),
null, 0, null);
});
}
}

12
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/Lwm2mClientRpcRequest.java

@ -17,6 +17,7 @@ package org.thingsboard.server.transport.lwm2m.server.client;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import lombok.Data; import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.core.request.ContentFormat;
import org.eclipse.leshan.server.registration.Registration; import org.eclipse.leshan.server.registration.Registration;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
@ -27,6 +28,7 @@ import java.util.concurrent.ConcurrentHashMap;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.validPathIdVer; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.validPathIdVer;
@Slf4j
@Data @Data
public class Lwm2mClientRpcRequest { public class Lwm2mClientRpcRequest {
public final String targetIdVerKey = "targetIdVer"; public final String targetIdVerKey = "targetIdVer";
@ -107,4 +109,14 @@ public class Lwm2mClientRpcRequest {
.setRequestId(this.requestId) .setRequestId(this.requestId)
.build(); .build();
} }
@Override
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
log.error("", e);
}
return null;
}
} }

373
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java

@ -26,9 +26,7 @@ import org.eclipse.leshan.core.util.NamedThreadFactory;
import org.eclipse.leshan.core.util.Validate; import org.eclipse.leshan.core.util.Validate;
import org.eclipse.leshan.server.californium.observation.ObserveUtil; import org.eclipse.leshan.server.californium.observation.ObserveUtil;
import org.eclipse.leshan.server.californium.registration.CaliforniumRegistrationStore; import org.eclipse.leshan.server.californium.registration.CaliforniumRegistrationStore;
import org.eclipse.leshan.server.redis.JedisLock;
import org.eclipse.leshan.server.redis.RedisRegistrationStore; import org.eclipse.leshan.server.redis.RedisRegistrationStore;
import org.eclipse.leshan.server.redis.SingleInstanceJedisLock;
import org.eclipse.leshan.server.redis.serialization.ObservationSerDes; import org.eclipse.leshan.server.redis.serialization.ObservationSerDes;
import org.eclipse.leshan.server.redis.serialization.RegistrationSerDes; import org.eclipse.leshan.server.redis.serialization.RegistrationSerDes;
import org.eclipse.leshan.server.registration.Deregistration; import org.eclipse.leshan.server.registration.Deregistration;
@ -38,11 +36,12 @@ import org.eclipse.leshan.server.registration.RegistrationUpdate;
import org.eclipse.leshan.server.registration.UpdatedRegistration; import org.eclipse.leshan.server.registration.UpdatedRegistration;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.data.redis.connection.RedisClusterConnection;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.RedisConnectionFactory;
import redis.clients.jedis.Jedis; import org.springframework.data.redis.core.Cursor;
import redis.clients.jedis.ScanParams; import org.springframework.data.redis.core.ScanOptions;
import redis.clients.jedis.ScanResult; import org.springframework.integration.redis.util.RedisLockRegistry;
import redis.clients.jedis.Transaction;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.ArrayList; import java.util.ArrayList;
@ -50,13 +49,14 @@ import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set; import java.util.Set;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.charset.StandardCharsets.UTF_8;
@ -92,7 +92,7 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto
private final int cleanLimit; // maximum number to clean in a clean period private final int cleanLimit; // maximum number to clean in a clean period
private final long gracePeriod; // in seconds private final long gracePeriod; // in seconds
private final JedisLock lock; private final RedisLockRegistry redisLock;
public TbLwM2mRedisRegistrationStore(RedisConnectionFactory connectionFactory) { public TbLwM2mRedisRegistrationStore(RedisConnectionFactory connectionFactory) {
this(connectionFactory, DEFAULT_CLEAN_PERIOD, DEFAULT_GRACE_PERIOD, DEFAULT_CLEAN_LIMIT); // default clean period 60s this(connectionFactory, DEFAULT_CLEAN_PERIOD, DEFAULT_GRACE_PERIOD, DEFAULT_CLEAN_LIMIT); // default clean period 60s
@ -106,20 +106,12 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto
public TbLwM2mRedisRegistrationStore(RedisConnectionFactory connectionFactory, ScheduledExecutorService schedExecutor, long cleanPeriodInSec, public TbLwM2mRedisRegistrationStore(RedisConnectionFactory connectionFactory, ScheduledExecutorService schedExecutor, long cleanPeriodInSec,
long lifetimeGracePeriodInSec, int cleanLimit) { long lifetimeGracePeriodInSec, int cleanLimit) {
this(connectionFactory, schedExecutor, cleanPeriodInSec, lifetimeGracePeriodInSec, cleanLimit, new SingleInstanceJedisLock());
}
/**
* @since 1.1
*/
public TbLwM2mRedisRegistrationStore(RedisConnectionFactory connectionFactory, ScheduledExecutorService schedExecutor, long cleanPeriodInSec,
long lifetimeGracePeriodInSec, int cleanLimit, JedisLock redisLock) {
this.connectionFactory = connectionFactory; this.connectionFactory = connectionFactory;
this.schedExecutor = schedExecutor; this.schedExecutor = schedExecutor;
this.cleanPeriod = cleanPeriodInSec; this.cleanPeriod = cleanPeriodInSec;
this.cleanLimit = cleanLimit; this.cleanLimit = cleanLimit;
this.gracePeriod = lifetimeGracePeriodInSec; this.gracePeriod = lifetimeGracePeriodInSec;
this.lock = redisLock; this.redisLock = new RedisLockRegistry(connectionFactory, "Registration");
} }
/* *************** Redis Key utility function **************** */ /* *************** Redis Key utility function **************** */
@ -135,76 +127,79 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto
return (prefix + registrationID).getBytes(); return (prefix + registrationID).getBytes();
} }
private byte[] toLockKey(String endpoint) { private String toLockKey(String endpoint) {
return toKey(LOCK_EP, endpoint); return new String(toKey(LOCK_EP, endpoint));
} }
private byte[] toLockKey(byte[] endpoint) { private String toLockKey(byte[] endpoint) {
return toKey(LOCK_EP.getBytes(UTF_8), endpoint); return new String(toKey(LOCK_EP.getBytes(UTF_8), endpoint));
} }
/* *************** Leshan Registration API **************** */ /* *************** Leshan Registration API **************** */
@Override @Override
public Deregistration addRegistration(Registration registration) { public Deregistration addRegistration(Registration registration) {
try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) { Lock lock = null;
byte[] lockValue = null; try (var connection = connectionFactory.getConnection()) {
byte[] lockKey = toLockKey(registration.getEndpoint()); String lockKey = toLockKey(registration.getEndpoint());
try { try {
lockValue = lock.acquire(j, lockKey); lock = redisLock.obtain(lockKey);
lock.lock();
// add registration // add registration
byte[] k = toEndpointKey(registration.getEndpoint()); byte[] k = toEndpointKey(registration.getEndpoint());
byte[] old = j.getSet(k, serializeReg(registration)); byte[] old = connection.getSet(k, serializeReg(registration));
// add registration: secondary indexes // add registration: secondary indexes
byte[] regid_idx = toRegIdKey(registration.getId()); byte[] regid_idx = toRegIdKey(registration.getId());
j.set(regid_idx, registration.getEndpoint().getBytes(UTF_8)); connection.set(regid_idx, registration.getEndpoint().getBytes(UTF_8));
byte[] addr_idx = toRegAddrKey(registration.getSocketAddress()); byte[] addr_idx = toRegAddrKey(registration.getSocketAddress());
j.set(addr_idx, registration.getEndpoint().getBytes(UTF_8)); connection.set(addr_idx, registration.getEndpoint().getBytes(UTF_8));
// Add or update expiration // Add or update expiration
addOrUpdateExpiration(j, registration); addOrUpdateExpiration(connection, registration);
if (old != null) { if (old != null) {
Registration oldRegistration = deserializeReg(old); Registration oldRegistration = deserializeReg(old);
// remove old secondary index // remove old secondary index
if (!registration.getId().equals(oldRegistration.getId())) if (!registration.getId().equals(oldRegistration.getId()))
j.del(toRegIdKey(oldRegistration.getId())); connection.del(toRegIdKey(oldRegistration.getId()));
if (!oldRegistration.getSocketAddress().equals(registration.getSocketAddress())) { if (!oldRegistration.getSocketAddress().equals(registration.getSocketAddress())) {
removeAddrIndex(j, oldRegistration); removeAddrIndex(connection, oldRegistration);
} }
// remove old observation // remove old observation
Collection<Observation> obsRemoved = unsafeRemoveAllObservations(j, oldRegistration.getId()); Collection<Observation> obsRemoved = unsafeRemoveAllObservations(connection, oldRegistration.getId());
return new Deregistration(oldRegistration, obsRemoved); return new Deregistration(oldRegistration, obsRemoved);
} }
return null; return null;
} finally { } finally {
lock.release(j, lockKey, lockValue); if (lock != null) {
lock.unlock();
}
} }
} }
} }
@Override @Override
public UpdatedRegistration updateRegistration(RegistrationUpdate update) { public UpdatedRegistration updateRegistration(RegistrationUpdate update) {
try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) { Lock lock = null;
try (var connection = connectionFactory.getConnection()) {
// Fetch the registration ep by registration ID index // Fetch the registration ep by registration ID index
byte[] ep = j.get(toRegIdKey(update.getRegistrationId())); byte[] ep = connection.get(toRegIdKey(update.getRegistrationId()));
if (ep == null) { if (ep == null) {
return null; return null;
} }
byte[] lockValue = null; String lockKey = toLockKey(ep);
byte[] lockKey = toLockKey(ep);
try { try {
lockValue = lock.acquire(j, lockKey); lock = redisLock.obtain(lockKey);
lock.lock();
// Fetch the registration // Fetch the registration
byte[] data = j.get(toEndpointKey(ep)); byte[] data = connection.get(toEndpointKey(ep));
if (data == null) { if (data == null) {
return null; return null;
} }
@ -214,40 +209,42 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto
Registration updatedRegistration = update.update(r); Registration updatedRegistration = update.update(r);
// Store the new registration // Store the new registration
j.set(toEndpointKey(updatedRegistration.getEndpoint()), serializeReg(updatedRegistration)); connection.set(toEndpointKey(updatedRegistration.getEndpoint()), serializeReg(updatedRegistration));
// Add or update expiration // Add or update expiration
addOrUpdateExpiration(j, updatedRegistration); addOrUpdateExpiration(connection, updatedRegistration);
// Update secondary index : // Update secondary index :
// If registration is already associated to this address we don't care as we only want to keep the most // If registration is already associated to this address we don't care as we only want to keep the most
// recent binding. // recent binding.
byte[] addr_idx = toRegAddrKey(updatedRegistration.getSocketAddress()); byte[] addr_idx = toRegAddrKey(updatedRegistration.getSocketAddress());
j.set(addr_idx, updatedRegistration.getEndpoint().getBytes(UTF_8)); connection.set(addr_idx, updatedRegistration.getEndpoint().getBytes(UTF_8));
if (!r.getSocketAddress().equals(updatedRegistration.getSocketAddress())) { if (!r.getSocketAddress().equals(updatedRegistration.getSocketAddress())) {
removeAddrIndex(j, r); removeAddrIndex(connection, r);
} }
return new UpdatedRegistration(r, updatedRegistration); return new UpdatedRegistration(r, updatedRegistration);
} finally { } finally {
lock.release(j, lockKey, lockValue); if (lock != null) {
lock.unlock();
}
} }
} }
} }
@Override @Override
public Registration getRegistration(String registrationId) { public Registration getRegistration(String registrationId) {
try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) { try (var connection = connectionFactory.getConnection()) {
return getRegistration(j, registrationId); return getRegistration(connection, registrationId);
} }
} }
@Override @Override
public Registration getRegistrationByEndpoint(String endpoint) { public Registration getRegistrationByEndpoint(String endpoint) {
Validate.notNull(endpoint); Validate.notNull(endpoint);
try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) { try (var connection = connectionFactory.getConnection()) {
byte[] data = j.get(toEndpointKey(endpoint)); byte[] data = connection.get(toEndpointKey(endpoint));
if (data == null) { if (data == null) {
return null; return null;
} }
@ -258,12 +255,12 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto
@Override @Override
public Registration getRegistrationByAdress(InetSocketAddress address) { public Registration getRegistrationByAdress(InetSocketAddress address) {
Validate.notNull(address); Validate.notNull(address);
try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) { try (var connection = connectionFactory.getConnection()) {
byte[] ep = j.get(toRegAddrKey(address)); byte[] ep = connection.get(toRegAddrKey(address));
if (ep == null) { if (ep == null) {
return null; return null;
} }
byte[] data = j.get(toEndpointKey(ep)); byte[] data = connection.get(toEndpointKey(ep));
if (data == null) { if (data == null) {
return null; return null;
} }
@ -273,140 +270,99 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto
@Override @Override
public Iterator<Registration> getAllRegistrations() { public Iterator<Registration> getAllRegistrations() {
return new TbLwM2mRedisRegistrationStore.RedisIterator(connectionFactory, new ScanParams().match(REG_EP + "*").count(100)); try (var connection = connectionFactory.getConnection()) {
} Collection<Registration> list = new LinkedList<>();
ScanOptions scanOptions = ScanOptions.scanOptions().count(100).match(REG_EP + "*").build();
protected class RedisIterator implements Iterator<Registration> { List<Cursor<byte[]>> scans = new ArrayList<>();
if (connection instanceof RedisClusterConnection) {
private final RedisConnectionFactory connectionFactory; ((RedisClusterConnection) connection).clusterGetNodes().forEach(node -> {
private final ScanParams scanParams; scans.add(((RedisClusterConnection) connection).scan(node, scanOptions));
});
private String cursor; } else {
private List<Registration> scanResult; scans.add(connection.scan(scanOptions));
public RedisIterator(RedisConnectionFactory connectionFactory, ScanParams scanParams) {
this.connectionFactory = connectionFactory;
this.scanParams = scanParams;
// init scan result
scanNext("0");
}
private void scanNext(String cursor) {
try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) {
do {
ScanResult<byte[]> sr = j.scan(cursor.getBytes(), scanParams);
this.scanResult = new ArrayList<>();
if (sr.getResult() != null && !sr.getResult().isEmpty()) {
for (byte[] value : j.mget(sr.getResult().toArray(new byte[][]{}))) {
this.scanResult.add(deserializeReg(value));
}
}
cursor = sr.getCursor();
} while (!"0".equals(cursor) && scanResult.isEmpty());
this.cursor = cursor;
}
}
@Override
public boolean hasNext() {
if (!scanResult.isEmpty()) {
return true;
}
if ("0".equals(cursor)) {
// no more elements to scan
return false;
}
// read more elements
scanNext(cursor);
return !scanResult.isEmpty();
}
@Override
public Registration next() {
if (!hasNext()) {
throw new NoSuchElementException();
} }
return scanResult.remove(0);
}
@Override scans.forEach(scan -> {
public void remove() { scan.forEachRemaining(key -> {
throw new UnsupportedOperationException(); byte[] element = connection.get(key);
list.add(deserializeReg(element));
});
});
return list.iterator();
} }
} }
@Override @Override
public Deregistration removeRegistration(String registrationId) { public Deregistration removeRegistration(String registrationId) {
try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) { try (var connection = connectionFactory.getConnection()) {
return removeRegistration(j, registrationId, false); return removeRegistration(connection, registrationId, false);
} }
} }
private Deregistration removeRegistration(Jedis j, String registrationId, boolean removeOnlyIfNotAlive) { private Deregistration removeRegistration(RedisConnection connection, String registrationId, boolean removeOnlyIfNotAlive) {
// fetch the client ep by registration ID index // fetch the client ep by registration ID index
byte[] ep = j.get(toRegIdKey(registrationId)); byte[] ep = connection.get(toRegIdKey(registrationId));
if (ep == null) { if (ep == null) {
return null; return null;
} }
byte[] lockValue = null; Lock lock = null;
byte[] lockKey = toLockKey(ep); String lockKey = toLockKey(ep);
try { try {
lockValue = lock.acquire(j, lockKey); lock = redisLock.obtain(lockKey);
lock.lock();
// fetch the client // fetch the client
byte[] data = j.get(toEndpointKey(ep)); byte[] data = connection.get(toEndpointKey(ep));
if (data == null) { if (data == null) {
return null; return null;
} }
Registration r = deserializeReg(data); Registration r = deserializeReg(data);
if (!removeOnlyIfNotAlive || !r.isAlive(gracePeriod)) { if (!removeOnlyIfNotAlive || !r.isAlive(gracePeriod)) {
long nbRemoved = j.del(toRegIdKey(r.getId())); long nbRemoved = connection.del(toRegIdKey(r.getId()));
if (nbRemoved > 0) { if (nbRemoved > 0) {
j.del(toEndpointKey(r.getEndpoint())); connection.del(toEndpointKey(r.getEndpoint()));
Collection<Observation> obsRemoved = unsafeRemoveAllObservations(j, r.getId()); Collection<Observation> obsRemoved = unsafeRemoveAllObservations(connection, r.getId());
removeAddrIndex(j, r); removeAddrIndex(connection, r);
removeExpiration(j, r); removeExpiration(connection, r);
return new Deregistration(r, obsRemoved); return new Deregistration(r, obsRemoved);
} }
} }
return null; return null;
} finally { } finally {
lock.release(j, lockKey, lockValue); if (lock != null) {
lock.unlock();
}
} }
} }
private void removeAddrIndex(Jedis j, Registration registration) { private void removeAddrIndex(RedisConnection connection, Registration registration) {
// Watch the key to remove. // Watch the key to remove.
byte[] regAddrKey = toRegAddrKey(registration.getSocketAddress()); byte[] regAddrKey = toRegAddrKey(registration.getSocketAddress());
j.watch(regAddrKey); connection.watch(regAddrKey);
byte[] epFromAddr = j.get(regAddrKey); byte[] epFromAddr = connection.get(regAddrKey);
// Delete the key if needed. // Delete the key if needed.
if (Arrays.equals(epFromAddr, registration.getEndpoint().getBytes(UTF_8))) { if (Arrays.equals(epFromAddr, registration.getEndpoint().getBytes(UTF_8))) {
// Try to delete the key // Try to delete the key
Transaction transaction = j.multi(); connection.multi();
transaction.del(regAddrKey); connection.del(regAddrKey);
transaction.exec(); connection.exec();
// if transaction failed this is not an issue as the socket address is probably reused and we don't neeed to // if transaction failed this is not an issue as the socket address is probably reused and we don't neeed to
// delete it anymore. // delete it anymore.
} else { } else {
// the key must not be deleted. // the key must not be deleted.
j.unwatch(); connection.unwatch();
} }
} }
private void addOrUpdateExpiration(Jedis j, Registration registration) { private void addOrUpdateExpiration(RedisConnection connection, Registration registration) {
j.zadd(EXP_EP, registration.getExpirationTimeStamp(gracePeriod), registration.getEndpoint().getBytes(UTF_8)); connection.zAdd(EXP_EP, registration.getExpirationTimeStamp(gracePeriod), registration.getEndpoint().getBytes(UTF_8));
} }
private void removeExpiration(Jedis j, Registration registration) { private void removeExpiration(RedisConnection connection, Registration registration) {
j.zrem(EXP_EP, registration.getEndpoint().getBytes(UTF_8)); connection.zRem(EXP_EP, registration.getEndpoint().getBytes(UTF_8));
} }
private byte[] toRegIdKey(String registrationId) { private byte[] toRegIdKey(String registrationId) {
@ -441,33 +397,35 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto
*/ */
@Override @Override
public Collection<Observation> addObservation(String registrationId, Observation observation) { public Collection<Observation> addObservation(String registrationId, Observation observation) {
List<Observation> removed = new ArrayList<>(); List<Observation> removed = new ArrayList<>();
try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) { try (var connection = connectionFactory.getConnection()) {
// fetch the client ep by registration ID index // fetch the client ep by registration ID index
byte[] ep = j.get(toRegIdKey(registrationId)); byte[] ep = connection.get(toRegIdKey(registrationId));
if (ep == null) { if (ep == null) {
return null; return null;
} }
byte[] lockValue = null; Lock lock = null;
byte[] lockKey = toLockKey(ep); String lockKey = toLockKey(ep);
try { try {
lockValue = lock.acquire(j, lockKey); lock = redisLock.obtain(lockKey);
lock.lock();
// cancel existing observations for the same path and registration id. // cancel existing observations for the same path and registration id.
for (Observation obs : getObservations(j, registrationId)) { for (Observation obs : getObservations(connection, registrationId)) {
if (observation.getPath().equals(obs.getPath()) if (observation.getPath().equals(obs.getPath())
&& !Arrays.equals(observation.getId(), obs.getId())) { && !Arrays.equals(observation.getId(), obs.getId())) {
removed.add(obs); removed.add(obs);
unsafeRemoveObservation(j, registrationId, obs.getId()); unsafeRemoveObservation(connection, registrationId, obs.getId());
} }
} }
} finally { } finally {
lock.release(j, lockKey, lockValue); if (lock != null) {
lock.unlock();
}
} }
} }
return removed; return removed;
@ -475,29 +433,32 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto
@Override @Override
public Observation removeObservation(String registrationId, byte[] observationId) { public Observation removeObservation(String registrationId, byte[] observationId) {
try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) { try (var connection = connectionFactory.getConnection()) {
// fetch the client ep by registration ID index // fetch the client ep by registration ID index
byte[] ep = j.get(toRegIdKey(registrationId)); byte[] ep = connection.get(toRegIdKey(registrationId));
if (ep == null) { if (ep == null) {
return null; return null;
} }
// remove observation // remove observation
byte[] lockValue = null; Lock lock = null;
byte[] lockKey = toLockKey(ep); String lockKey = toLockKey(ep);
try { try {
lockValue = lock.acquire(j, lockKey); lock = redisLock.obtain(lockKey);
lock.lock();
Observation observation = build(get(new Token(observationId))); Observation observation = build(get(new Token(observationId)));
if (observation != null && registrationId.equals(observation.getRegistrationId())) { if (observation != null && registrationId.equals(observation.getRegistrationId())) {
unsafeRemoveObservation(j, registrationId, observationId); unsafeRemoveObservation(connection, registrationId, observationId);
return observation; return observation;
} }
return null; return null;
} finally { } finally {
lock.release(j, lockKey, lockValue); if (lock != null) {
lock.unlock();
}
} }
} }
} }
@ -509,15 +470,15 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto
@Override @Override
public Collection<Observation> getObservations(String registrationId) { public Collection<Observation> getObservations(String registrationId) {
try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) { try (var connection = connectionFactory.getConnection()) {
return getObservations(j, registrationId); return getObservations(connection, registrationId);
} }
} }
private Collection<Observation> getObservations(Jedis j, String registrationId) { private Collection<Observation> getObservations(RedisConnection connection, String registrationId) {
Collection<Observation> result = new ArrayList<>(); Collection<Observation> result = new ArrayList<>();
for (byte[] token : j.lrange(toKey(OBS_TKNS_REGID_IDX, registrationId), 0, -1)) { for (byte[] token : connection.lRange(toKey(OBS_TKNS_REGID_IDX, registrationId), 0, -1)) {
byte[] obs = j.get(toKey(OBS_TKN, token)); byte[] obs = connection.get(toKey(OBS_TKN, token));
if (obs != null) { if (obs != null) {
result.add(build(deserializeObs(obs))); result.add(build(deserializeObs(obs)));
} }
@ -527,22 +488,24 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto
@Override @Override
public Collection<Observation> removeObservations(String registrationId) { public Collection<Observation> removeObservations(String registrationId) {
try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) { try (var connection = connectionFactory.getConnection()) {
// check registration exists // check registration exists
Registration registration = getRegistration(j, registrationId); Registration registration = getRegistration(connection, registrationId);
if (registration == null) if (registration == null)
return Collections.emptyList(); return Collections.emptyList();
// get endpoint and create lock // get endpoint and create lock
String endpoint = registration.getEndpoint(); String endpoint = registration.getEndpoint();
byte[] lockValue = null; Lock lock = null;
byte[] lockKey = toKey(LOCK_EP, endpoint); String lockKey = toLockKey(endpoint);
try { try {
lockValue = lock.acquire(j, lockKey); lock = redisLock.obtain(lockKey);
lock.lock();
return unsafeRemoveAllObservations(j, registrationId); return unsafeRemoveAllObservations(connection, registrationId);
} finally { } finally {
lock.release(j, lockKey, lockValue); if (lock != null) {
lock.unlock();
}
} }
} }
} }
@ -565,31 +528,32 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto
String endpoint = ObserveUtil.validateCoapObservation(obs); String endpoint = ObserveUtil.validateCoapObservation(obs);
org.eclipse.californium.core.observe.Observation previousObservation = null; org.eclipse.californium.core.observe.Observation previousObservation = null;
try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) { try (var connection = connectionFactory.getConnection()) {
byte[] lockValue = null; Lock lock = null;
byte[] lockKey = toKey(LOCK_EP, endpoint); String lockKey = toLockKey(endpoint);
try { try {
lockValue = lock.acquire(j, lockKey); lock = redisLock.obtain(lockKey);
lock.lock();
String registrationId = ObserveUtil.extractRegistrationId(obs); String registrationId = ObserveUtil.extractRegistrationId(obs);
if (!j.exists(toRegIdKey(registrationId))) if (!connection.exists(toRegIdKey(registrationId)))
throw new ObservationStoreException("no registration for this Id"); throw new ObservationStoreException("no registration for this Id");
byte[] key = toKey(OBS_TKN, obs.getRequest().getToken().getBytes()); byte[] key = toKey(OBS_TKN, obs.getRequest().getToken().getBytes());
byte[] serializeObs = serializeObs(obs); byte[] serializeObs = serializeObs(obs);
byte[] previousValue; byte[] previousValue;
if (ifAbsent) { if (ifAbsent) {
previousValue = j.get(key); previousValue = connection.get(key);
if (previousValue == null || previousValue.length == 0) { if (previousValue == null || previousValue.length == 0) {
j.set(key, serializeObs); connection.set(key, serializeObs);
} else { } else {
return deserializeObs(previousValue); return deserializeObs(previousValue);
} }
} else { } else {
previousValue = j.getSet(key, serializeObs); previousValue = connection.getSet(key, serializeObs);
} }
// secondary index to get the list by registrationId // secondary index to get the list by registrationId
j.lpush(toKey(OBS_TKNS_REGID_IDX, registrationId), obs.getRequest().getToken().getBytes()); connection.lPush(toKey(OBS_TKNS_REGID_IDX, registrationId), obs.getRequest().getToken().getBytes());
// log any collisions // log any collisions
if (previousValue != null && previousValue.length != 0) { if (previousValue != null && previousValue.length != 0) {
@ -599,7 +563,9 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto
previousObservation.getRequest(), obs.getRequest()); previousObservation.getRequest(), obs.getRequest());
} }
} finally { } finally {
lock.release(j, lockKey, lockValue); if (lock != null) {
lock.unlock();
}
} }
} }
return previousObservation; return previousObservation;
@ -607,17 +573,17 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto
@Override @Override
public void remove(Token token) { public void remove(Token token) {
try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) { try (var connection = connectionFactory.getConnection()) {
byte[] tokenKey = toKey(OBS_TKN, token.getBytes()); byte[] tokenKey = toKey(OBS_TKN, token.getBytes());
// fetch the observation by token // fetch the observation by token
byte[] serializedObs = j.get(tokenKey); byte[] serializedObs = connection.get(tokenKey);
if (serializedObs == null) if (serializedObs == null)
return; return;
org.eclipse.californium.core.observe.Observation obs = deserializeObs(serializedObs); org.eclipse.californium.core.observe.Observation obs = deserializeObs(serializedObs);
String registrationId = ObserveUtil.extractRegistrationId(obs); String registrationId = ObserveUtil.extractRegistrationId(obs);
Registration registration = getRegistration(j, registrationId); Registration registration = getRegistration(connection, registrationId);
if (registration == null) { if (registration == null) {
LOG.warn("Unable to remove observation {}, registration {} does not exist anymore", obs.getRequest(), LOG.warn("Unable to remove observation {}, registration {} does not exist anymore", obs.getRequest(),
registrationId); registrationId);
@ -625,14 +591,17 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto
} }
String endpoint = registration.getEndpoint(); String endpoint = registration.getEndpoint();
byte[] lockValue = null; Lock lock = null;
byte[] lockKey = toKey(LOCK_EP, endpoint); String lockKey = toLockKey(endpoint);
try { try {
lockValue = lock.acquire(j, lockKey); lock = redisLock.obtain(lockKey);
lock.lock();
unsafeRemoveObservation(j, registrationId, token.getBytes()); unsafeRemoveObservation(connection, registrationId, token.getBytes());
} finally { } finally {
lock.release(j, lockKey, lockValue); if (lock != null) {
lock.unlock();
}
} }
} }
@ -640,8 +609,8 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto
@Override @Override
public org.eclipse.californium.core.observe.Observation get(Token token) { public org.eclipse.californium.core.observe.Observation get(Token token) {
try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) { try (var connection = connectionFactory.getConnection()) {
byte[] obs = j.get(toKey(OBS_TKN, token.getBytes())); byte[] obs = connection.get(toKey(OBS_TKN, token.getBytes()));
if (obs == null) { if (obs == null) {
return null; return null;
} else { } else {
@ -652,12 +621,12 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto
/* *************** Observation utility functions **************** */ /* *************** Observation utility functions **************** */
private Registration getRegistration(Jedis j, String registrationId) { private Registration getRegistration(RedisConnection connection, String registrationId) {
byte[] ep = j.get(toRegIdKey(registrationId)); byte[] ep = connection.get(toRegIdKey(registrationId));
if (ep == null) { if (ep == null) {
return null; return null;
} }
byte[] data = j.get(toEndpointKey(ep)); byte[] data = connection.get(toEndpointKey(ep));
if (data == null) { if (data == null) {
return null; return null;
} }
@ -665,25 +634,25 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto
return deserializeReg(data); return deserializeReg(data);
} }
private void unsafeRemoveObservation(Jedis j, String registrationId, byte[] observationId) { private void unsafeRemoveObservation(RedisConnection connection, String registrationId, byte[] observationId) {
if (j.del(toKey(OBS_TKN, observationId)) > 0L) { if (connection.del(toKey(OBS_TKN, observationId)) > 0L) {
j.lrem(toKey(OBS_TKNS_REGID_IDX, registrationId), 0, observationId); connection.lRem(toKey(OBS_TKNS_REGID_IDX, registrationId), 0, observationId);
} }
} }
private Collection<Observation> unsafeRemoveAllObservations(Jedis j, String registrationId) { private Collection<Observation> unsafeRemoveAllObservations(RedisConnection connection, String registrationId) {
Collection<Observation> removed = new ArrayList<>(); Collection<Observation> removed = new ArrayList<>();
byte[] regIdKey = toKey(OBS_TKNS_REGID_IDX, registrationId); byte[] regIdKey = toKey(OBS_TKNS_REGID_IDX, registrationId);
// fetch all observations by token // fetch all observations by token
for (byte[] token : j.lrange(regIdKey, 0, -1)) { for (byte[] token : connection.lRange(regIdKey, 0, -1)) {
byte[] obs = j.get(toKey(OBS_TKN, token)); byte[] obs = connection.get(toKey(OBS_TKN, token));
if (obs != null) { if (obs != null) {
removed.add(build(deserializeObs(obs))); removed.add(build(deserializeObs(obs)));
} }
j.del(toKey(OBS_TKN, token)); connection.del(toKey(OBS_TKN, token));
} }
j.del(regIdKey); connection.del(regIdKey);
return removed; return removed;
} }
@ -754,14 +723,14 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto
@Override @Override
public void run() { public void run() {
try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) { try (var connection = connectionFactory.getConnection()) {
Set<byte[]> endpointsExpired = j.zrangeByScore(EXP_EP, Double.NEGATIVE_INFINITY, Set<byte[]> endpointsExpired = connection.zRangeByScore(EXP_EP, Double.NEGATIVE_INFINITY,
System.currentTimeMillis(), 0, cleanLimit); System.currentTimeMillis(), 0, cleanLimit);
for (byte[] endpoint : endpointsExpired) { for (byte[] endpoint : endpointsExpired) {
Registration r = deserializeReg(j.get(toEndpointKey(endpoint))); Registration r = deserializeReg(connection.get(toEndpointKey(endpoint)));
if (!r.isAlive(gracePeriod)) { if (!r.isAlive(gracePeriod)) {
Deregistration dereg = removeRegistration(j, r.getId(), true); Deregistration dereg = removeRegistration(connection, r.getId(), true);
if (dereg != null) if (dereg != null)
expirationListener.registrationExpired(dereg.getRegistration(), dereg.getObservations()); expirationListener.registrationExpired(dereg.getRegistration(), dereg.getObservations());
} }

64
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisSecurityStore.java

@ -20,13 +20,15 @@ import org.eclipse.leshan.server.security.EditableSecurityStore;
import org.eclipse.leshan.server.security.NonUniqueSecurityInfoException; import org.eclipse.leshan.server.security.NonUniqueSecurityInfoException;
import org.eclipse.leshan.server.security.SecurityInfo; import org.eclipse.leshan.server.security.SecurityInfo;
import org.eclipse.leshan.server.security.SecurityStoreListener; import org.eclipse.leshan.server.security.SecurityStoreListener;
import org.springframework.data.redis.connection.RedisClusterConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.RedisConnectionFactory;
import redis.clients.jedis.Jedis; import org.springframework.data.redis.core.Cursor;
import redis.clients.jedis.ScanParams; import org.springframework.data.redis.core.ScanOptions;
import redis.clients.jedis.ScanResult;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List;
public class TbLwM2mRedisSecurityStore implements EditableSecurityStore { public class TbLwM2mRedisSecurityStore implements EditableSecurityStore {
private static final String SEC_EP = "SEC#EP#"; private static final String SEC_EP = "SEC#EP#";
@ -42,8 +44,8 @@ public class TbLwM2mRedisSecurityStore implements EditableSecurityStore {
@Override @Override
public SecurityInfo getByEndpoint(String endpoint) { public SecurityInfo getByEndpoint(String endpoint) {
try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) { try (var connection = connectionFactory.getConnection()) {
byte[] data = j.get((SEC_EP + endpoint).getBytes()); byte[] data = connection.get((SEC_EP + endpoint).getBytes());
if (data == null) { if (data == null) {
return null; return null;
} else { } else {
@ -54,12 +56,12 @@ public class TbLwM2mRedisSecurityStore implements EditableSecurityStore {
@Override @Override
public SecurityInfo getByIdentity(String identity) { public SecurityInfo getByIdentity(String identity) {
try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) { try (var connection = connectionFactory.getConnection()) {
String ep = j.hget(PSKID_SEC, identity); byte[] ep = connection.hGet(PSKID_SEC.getBytes(), identity.getBytes());
if (ep == null) { if (ep == null) {
return null; return null;
} else { } else {
byte[] data = j.get((SEC_EP + ep).getBytes()); byte[] data = connection.get((SEC_EP + new String(ep)).getBytes());
if (data == null) { if (data == null) {
return null; return null;
} else { } else {
@ -71,18 +73,24 @@ public class TbLwM2mRedisSecurityStore implements EditableSecurityStore {
@Override @Override
public Collection<SecurityInfo> getAll() { public Collection<SecurityInfo> getAll() {
try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) { try (var connection = connectionFactory.getConnection()) {
ScanParams params = new ScanParams().match(SEC_EP + "*").count(100);
Collection<SecurityInfo> list = new LinkedList<>(); Collection<SecurityInfo> list = new LinkedList<>();
String cursor = "0"; ScanOptions scanOptions = ScanOptions.scanOptions().count(100).match(SEC_EP + "*").build();
do { List<Cursor<byte[]>> scans = new ArrayList<>();
ScanResult<byte[]> res = j.scan(cursor.getBytes(), params); if (connection instanceof RedisClusterConnection) {
for (byte[] key : res.getResult()) { ((RedisClusterConnection) connection).clusterGetNodes().forEach(node -> {
byte[] element = j.get(key); scans.add(((RedisClusterConnection) connection).scan(node, scanOptions));
});
} else {
scans.add(connection.scan(scanOptions));
}
scans.forEach(scan -> {
scan.forEachRemaining(key -> {
byte[] element = connection.get(key);
list.add(deserialize(element)); list.add(deserialize(element));
} });
cursor = res.getCursor(); });
} while (!"0".equals(cursor));
return list; return list;
} }
} }
@ -90,21 +98,21 @@ public class TbLwM2mRedisSecurityStore implements EditableSecurityStore {
@Override @Override
public SecurityInfo add(SecurityInfo info) throws NonUniqueSecurityInfoException { public SecurityInfo add(SecurityInfo info) throws NonUniqueSecurityInfoException {
byte[] data = serialize(info); byte[] data = serialize(info);
try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) { try (var connection = connectionFactory.getConnection()) {
if (info.getIdentity() != null) { if (info.getIdentity() != null) {
// populate the secondary index (security info by PSK id) // populate the secondary index (security info by PSK id)
String oldEndpoint = j.hget(PSKID_SEC, info.getIdentity()); String oldEndpoint = new String(connection.hGet(PSKID_SEC.getBytes(), info.getIdentity().getBytes()));
if (oldEndpoint != null && !oldEndpoint.equals(info.getEndpoint())) { if (!oldEndpoint.equals(info.getEndpoint())) {
throw new NonUniqueSecurityInfoException("PSK Identity " + info.getIdentity() + " is already used"); throw new NonUniqueSecurityInfoException("PSK Identity " + info.getIdentity() + " is already used");
} }
j.hset(PSKID_SEC.getBytes(), info.getIdentity().getBytes(), info.getEndpoint().getBytes()); connection.hSet(PSKID_SEC.getBytes(), info.getIdentity().getBytes(), info.getEndpoint().getBytes());
} }
byte[] previousData = j.getSet((SEC_EP + info.getEndpoint()).getBytes(), data); byte[] previousData = connection.getSet((SEC_EP + info.getEndpoint()).getBytes(), data);
SecurityInfo previous = previousData == null ? null : deserialize(previousData); SecurityInfo previous = previousData == null ? null : deserialize(previousData);
String previousIdentity = previous == null ? null : previous.getIdentity(); String previousIdentity = previous == null ? null : previous.getIdentity();
if (previousIdentity != null && !previousIdentity.equals(info.getIdentity())) { if (previousIdentity != null && !previousIdentity.equals(info.getIdentity())) {
j.hdel(PSKID_SEC, previousIdentity); connection.hDel(PSKID_SEC.getBytes(), previousIdentity.getBytes());
} }
return previous; return previous;
@ -113,15 +121,15 @@ public class TbLwM2mRedisSecurityStore implements EditableSecurityStore {
@Override @Override
public SecurityInfo remove(String endpoint, boolean infosAreCompromised) { public SecurityInfo remove(String endpoint, boolean infosAreCompromised) {
try (Jedis j = (Jedis) connectionFactory.getConnection().getNativeConnection()) { try (var connection = connectionFactory.getConnection()) {
byte[] data = j.get((SEC_EP + endpoint).getBytes()); byte[] data = connection.get((SEC_EP + endpoint).getBytes());
if (data != null) { if (data != null) {
SecurityInfo info = deserialize(data); SecurityInfo info = deserialize(data);
if (info.getIdentity() != null) { if (info.getIdentity() != null) {
j.hdel(PSKID_SEC.getBytes(), info.getIdentity().getBytes()); connection.hDel(PSKID_SEC.getBytes(), info.getIdentity().getBytes());
} }
j.del((SEC_EP + endpoint).getBytes()); connection.del((SEC_EP + endpoint).getBytes());
if (listener != null) { if (listener != null) {
listener.securityInfoRemoved(infosAreCompromised, info); listener.securityInfoRemoved(infosAreCompromised, info);
} }

4
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mSecurityStore.java

@ -74,7 +74,7 @@ public class TbLwM2mSecurityStore implements EditableSecurityStore {
add(securityInfo); add(securityInfo);
} }
} catch (NonUniqueSecurityInfoException e) { } catch (NonUniqueSecurityInfoException e) {
log.warn("Failed to add security info: {}", securityInfo, e); log.trace("Failed to add security info: {}", securityInfo, e);
} }
} }
return securityInfo; return securityInfo;
@ -90,7 +90,7 @@ public class TbLwM2mSecurityStore implements EditableSecurityStore {
add(securityInfo); add(securityInfo);
} }
} catch (NonUniqueSecurityInfoException e) { } catch (NonUniqueSecurityInfoException e) {
log.warn("Failed to add security info: {}", securityInfo, e); log.trace("Failed to add security info: {}", securityInfo, e);
} }
} }
return securityInfo; return securityInfo;

6
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mStoreFactory.java

@ -26,6 +26,7 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.cache.TBRedisCacheConfiguration; import org.thingsboard.server.cache.TBRedisCacheConfiguration;
import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext;
import java.util.Optional; import java.util.Optional;
@ -37,6 +38,9 @@ public class TbLwM2mStoreFactory {
@Autowired(required = false) @Autowired(required = false)
private Optional<TBRedisCacheConfiguration> redisConfiguration; private Optional<TBRedisCacheConfiguration> redisConfiguration;
@Autowired
private LwM2MTransportServerConfig config;
@Autowired @Autowired
@Lazy @Lazy
private LwM2mClientContext clientContext; private LwM2mClientContext clientContext;
@ -47,7 +51,7 @@ public class TbLwM2mStoreFactory {
@Bean @Bean
private CaliforniumRegistrationStore registrationStore() { private CaliforniumRegistrationStore registrationStore() {
return redisConfiguration.isPresent() && useRedis ? return redisConfiguration.isPresent() && useRedis ?
new TbLwM2mRedisRegistrationStore(redisConfiguration.get().redisConnectionFactory()) : new InMemoryRegistrationStore(); new TbLwM2mRedisRegistrationStore(redisConfiguration.get().redisConnectionFactory()) : new InMemoryRegistrationStore(config.getCleanPeriodInSec());
} }
@Bean @Bean

8
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java

@ -121,9 +121,11 @@ public class LwM2mValueConverterImpl implements LwM2mValueConverter {
/** let's assume we received an ISO 8601 format date */ /** let's assume we received an ISO 8601 format date */
try { try {
return new Date(Long.decode(value.toString())); return new Date(Long.decode(value.toString()));
// DatatypeFactory datatypeFactory = DatatypeFactory.newInstance(); /**
// XMLGregorianCalendar cal = datatypeFactory.newXMLGregorianCalendar((String) value); DatatypeFactory datatypeFactory = DatatypeFactory.newInstance();
// return cal.toGregorianCalendar().getTime(); XMLGregorianCalendar cal = datatypeFactory.newXMLGregorianCalendar((String) value);
return cal.toGregorianCalendar().getTime();
**/
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
log.debug("Unable to convert string to date", e); log.debug("Unable to convert string to date", e);
throw new CodecException("Unable to convert string (%s) to date for resource %s", value, throw new CodecException("Unable to convert string (%s) to date for resource %s", value,

2
dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java

@ -48,6 +48,8 @@ public interface AlarmDao extends Dao<Alarm> {
PageData<AlarmInfo> findAlarms(TenantId tenantId, AlarmQuery query); PageData<AlarmInfo> findAlarms(TenantId tenantId, AlarmQuery query);
PageData<AlarmInfo> findCustomerAlarms(TenantId tenantId, CustomerId customerId, AlarmQuery query);
PageData<AlarmData> findAlarmDataByQueryForEntities(TenantId tenantId, CustomerId customerId, PageData<AlarmData> findAlarmDataByQueryForEntities(TenantId tenantId, CustomerId customerId,
AlarmDataQuery query, Collection<EntityId> orderedEntityIds); AlarmDataQuery query, Collection<EntityId> orderedEntityIds);

56
dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java

@ -41,7 +41,6 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.AlarmData; import org.thingsboard.server.common.data.query.AlarmData;
import org.thingsboard.server.common.data.query.AlarmDataQuery; import org.thingsboard.server.common.data.query.AlarmDataQuery;
import org.thingsboard.server.common.data.query.DeviceTypeFilter;
import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.EntityRelationsQuery; import org.thingsboard.server.common.data.relation.EntityRelationsQuery;
import org.thingsboard.server.common.data.relation.EntitySearchDirection; import org.thingsboard.server.common.data.relation.EntitySearchDirection;
@ -102,6 +101,11 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
@Override @Override
public AlarmOperationResult createOrUpdateAlarm(Alarm alarm) { public AlarmOperationResult createOrUpdateAlarm(Alarm alarm) {
return createOrUpdateAlarm(alarm, true);
}
@Override
public AlarmOperationResult createOrUpdateAlarm(Alarm alarm, boolean alarmCreationEnabled) {
alarmDataValidator.validate(alarm, Alarm::getTenantId); alarmDataValidator.validate(alarm, Alarm::getTenantId);
try { try {
if (alarm.getStartTs() == 0L) { if (alarm.getStartTs() == 0L) {
@ -110,9 +114,13 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
if (alarm.getEndTs() == 0L) { if (alarm.getEndTs() == 0L) {
alarm.setEndTs(alarm.getStartTs()); alarm.setEndTs(alarm.getStartTs());
} }
alarm.setCustomerId(entityService.fetchEntityCustomerId(alarm.getTenantId(), alarm.getOriginator()));
if (alarm.getId() == null) { if (alarm.getId() == null) {
Alarm existing = alarmDao.findLatestByOriginatorAndType(alarm.getTenantId(), alarm.getOriginator(), alarm.getType()).get(); Alarm existing = alarmDao.findLatestByOriginatorAndType(alarm.getTenantId(), alarm.getOriginator(), alarm.getType()).get();
if (existing == null || existing.getStatus().isCleared()) { if (existing == null || existing.getStatus().isCleared()) {
if (!alarmCreationEnabled) {
throw new IllegalStateException("Alarm creation is disabled");
}
return createAlarm(alarm); return createAlarm(alarm);
} else { } else {
return updateAlarm(existing, alarm); return updateAlarm(existing, alarm);
@ -158,7 +166,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
log.debug("New Alarm : {}", alarm); log.debug("New Alarm : {}", alarm);
Alarm saved = alarmDao.save(alarm.getTenantId(), alarm); Alarm saved = alarmDao.save(alarm.getTenantId(), alarm);
List<EntityId> propagatedEntitiesList = createAlarmRelations(saved); List<EntityId> propagatedEntitiesList = createAlarmRelations(saved);
return new AlarmOperationResult(saved, true, propagatedEntitiesList); return new AlarmOperationResult(saved, true, true, propagatedEntitiesList);
} }
private List<EntityId> createAlarmRelations(Alarm alarm) throws InterruptedException, ExecutionException { private List<EntityId> createAlarmRelations(Alarm alarm) throws InterruptedException, ExecutionException {
@ -292,25 +300,38 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
public ListenableFuture<PageData<AlarmInfo>> findAlarms(TenantId tenantId, AlarmQuery query) { public ListenableFuture<PageData<AlarmInfo>> findAlarms(TenantId tenantId, AlarmQuery query) {
PageData<AlarmInfo> alarms = alarmDao.findAlarms(tenantId, query); PageData<AlarmInfo> alarms = alarmDao.findAlarms(tenantId, query);
if (query.getFetchOriginator() != null && query.getFetchOriginator().booleanValue()) { if (query.getFetchOriginator() != null && query.getFetchOriginator().booleanValue()) {
List<ListenableFuture<AlarmInfo>> alarmFutures = new ArrayList<>(alarms.getData().size()); return fetchAlarmsOriginators(tenantId, alarms);
for (AlarmInfo alarmInfo : alarms.getData()) {
alarmFutures.add(Futures.transform(
entityService.fetchEntityNameAsync(tenantId, alarmInfo.getOriginator()), originatorName -> {
if (originatorName == null) {
originatorName = "Deleted";
}
alarmInfo.setOriginatorName(originatorName);
return alarmInfo;
}, MoreExecutors.directExecutor()
));
}
return Futures.transform(Futures.successfulAsList(alarmFutures),
alarmInfos -> new PageData<>(alarmInfos, alarms.getTotalPages(), alarms.getTotalElements(),
alarms.hasNext()), MoreExecutors.directExecutor());
} }
return Futures.immediateFuture(alarms); return Futures.immediateFuture(alarms);
} }
@Override
public ListenableFuture<PageData<AlarmInfo>> findCustomerAlarms(TenantId tenantId, CustomerId customerId, AlarmQuery query) {
PageData<AlarmInfo> alarms = alarmDao.findCustomerAlarms(tenantId, customerId, query);
if (query.getFetchOriginator() != null && query.getFetchOriginator().booleanValue()) {
return fetchAlarmsOriginators(tenantId, alarms);
}
return Futures.immediateFuture(alarms);
}
private ListenableFuture<PageData<AlarmInfo>> fetchAlarmsOriginators(TenantId tenantId, PageData<AlarmInfo> alarms) {
List<ListenableFuture<AlarmInfo>> alarmFutures = new ArrayList<>(alarms.getData().size());
for (AlarmInfo alarmInfo : alarms.getData()) {
alarmFutures.add(Futures.transform(
entityService.fetchEntityNameAsync(tenantId, alarmInfo.getOriginator()), originatorName -> {
if (originatorName == null) {
originatorName = "Deleted";
}
alarmInfo.setOriginatorName(originatorName);
return alarmInfo;
}, MoreExecutors.directExecutor()
));
}
return Futures.transform(Futures.successfulAsList(alarmFutures),
alarmInfos -> new PageData<>(alarmInfos, alarms.getTotalPages(), alarms.getTotalElements(),
alarms.hasNext()), MoreExecutors.directExecutor());
}
@Override @Override
public AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus, public AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus,
AlarmStatus alarmStatus) { AlarmStatus alarmStatus) {
@ -342,6 +363,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
existing.setStatus(alarm.getStatus()); existing.setStatus(alarm.getStatus());
existing.setSeverity(alarm.getSeverity()); existing.setSeverity(alarm.getSeverity());
existing.setDetails(alarm.getDetails()); existing.setDetails(alarm.getDetails());
existing.setCustomerId(alarm.getCustomerId());
existing.setPropagate(existing.isPropagate() || alarm.isPropagate()); existing.setPropagate(existing.isPropagate() || alarm.isPropagate());
List<String> existingPropagateRelationTypes = existing.getPropagateRelationTypes(); List<String> existingPropagateRelationTypes = existing.getPropagateRelationTypes();
List<String> newRelationTypes = alarm.getPropagateRelationTypes(); List<String> newRelationTypes = alarm.getPropagateRelationTypes();

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

@ -16,7 +16,6 @@
package org.thingsboard.server.dao.asset; package org.thingsboard.server.dao.asset;
import com.google.common.base.Function;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
@ -46,11 +45,10 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.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.page.TimePageLink;
import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.EntitySearchDirection; import org.thingsboard.server.common.data.relation.EntitySearchDirection;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.dao.customer.CustomerDao; import org.thingsboard.server.dao.customer.CustomerDao;
import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
@ -59,8 +57,8 @@ import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantDao; import org.thingsboard.server.dao.tenant.TenantDao;
import javax.annotation.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
@ -180,15 +178,16 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ
throw new RuntimeException("Exception while finding entity views for assetId [" + assetId + "]", e); throw new RuntimeException("Exception while finding entity views for assetId [" + assetId + "]", e);
} }
List<Object> list = new ArrayList<>(); removeAssetFromCacheByName(asset.getTenantId(), asset.getName());
list.add(asset.getTenantId());
list.add(asset.getName());
Cache cache = cacheManager.getCache(ASSET_CACHE);
cache.evict(list);
assetDao.removeById(tenantId, assetId.getId()); assetDao.removeById(tenantId, assetId.getId());
} }
private void removeAssetFromCacheByName(TenantId tenantId, String name) {
Cache cache = cacheManager.getCache(ASSET_CACHE);
cache.evict(Arrays.asList(tenantId, name));
}
@Override @Override
public PageData<Asset> findAssetsByTenantId(TenantId tenantId, PageLink pageLink) { public PageData<Asset> findAssetsByTenantId(TenantId tenantId, PageLink pageLink) {
log.trace("Executing findAssetsByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink); log.trace("Executing findAssetsByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink);
@ -397,6 +396,13 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ
@Override @Override
protected void validateUpdate(TenantId tenantId, Asset asset) { protected void validateUpdate(TenantId tenantId, Asset asset) {
Asset old = assetDao.findById(asset.getTenantId(), asset.getId().getId());
if (old == null) {
throw new DataValidationException("Can't update non existing asset!");
}
if (!old.getName().equals(asset.getName())) {
removeAssetFromCacheByName(tenantId, old.getName());
}
} }
@Override @Override

12
dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java

@ -18,6 +18,7 @@ package org.thingsboard.server.dao.dashboard;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.hibernate.exception.ConstraintViolationException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -166,7 +167,16 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
log.trace("Executing deleteDashboard [{}]", dashboardId); log.trace("Executing deleteDashboard [{}]", dashboardId);
Validator.validateId(dashboardId, INCORRECT_DASHBOARD_ID + dashboardId); Validator.validateId(dashboardId, INCORRECT_DASHBOARD_ID + dashboardId);
deleteEntityRelations(tenantId, dashboardId); deleteEntityRelations(tenantId, dashboardId);
dashboardDao.removeById(tenantId, dashboardId.getId()); try {
dashboardDao.removeById(tenantId, dashboardId.getId());
} catch (Exception t) {
ConstraintViolationException e = extractConstraintViolationException(t).orElse(null);
if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("fk_default_dashboard_device_profile")) {
throw new DataValidationException("The dashboard referenced by the device profiles cannot be deleted!");
} else {
throw t;
}
}
} }
@Override @Override

26
dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java

@ -36,6 +36,7 @@ import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import org.thingsboard.server.common.data.DashboardInfo;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceProfileInfo; import org.thingsboard.server.common.data.DeviceProfileInfo;
@ -61,9 +62,12 @@ import org.thingsboard.server.common.data.id.DeviceProfileId;
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.rule.RuleChain;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.firmware.FirmwareService; import org.thingsboard.server.dao.firmware.FirmwareService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.service.Validator; import org.thingsboard.server.dao.service.Validator;
@ -117,6 +121,12 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D
@Autowired @Autowired
private FirmwareService firmwareService; private FirmwareService firmwareService;
@Autowired
private RuleChainService ruleChainService;
@Autowired
private DashboardService dashboardService;
private final Lock findOrCreateLock = new ReentrantLock(); private final Lock findOrCreateLock = new ReentrantLock();
@Cacheable(cacheNames = DEVICE_PROFILE_CACHE, key = "{#deviceProfileId.id}") @Cacheable(cacheNames = DEVICE_PROFILE_CACHE, key = "{#deviceProfileId.id}")
@ -336,7 +346,7 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D
} }
private DataValidator<DeviceProfile> deviceProfileValidator = private DataValidator<DeviceProfile> deviceProfileValidator =
new DataValidator<DeviceProfile>() { new DataValidator<>() {
@Override @Override
protected void validateDataImpl(TenantId tenantId, DeviceProfile deviceProfile) { protected void validateDataImpl(TenantId tenantId, DeviceProfile deviceProfile) {
if (StringUtils.isEmpty(deviceProfile.getName())) { if (StringUtils.isEmpty(deviceProfile.getName())) {
@ -402,6 +412,20 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D
} }
} }
if (deviceProfile.getDefaultRuleChainId() != null) {
RuleChain ruleChain = ruleChainService.findRuleChainById(tenantId, deviceProfile.getDefaultRuleChainId());
if (ruleChain == null) {
throw new DataValidationException("Can't assign non-existent rule chain!");
}
}
if (deviceProfile.getDefaultDashboardId() != null) {
DashboardInfo dashboard = dashboardService.findDashboardInfoById(tenantId, deviceProfile.getDefaultDashboardId());
if (dashboard == null) {
throw new DataValidationException("Can't assign non-existent dashboard!");
}
}
if (deviceProfile.getFirmwareId() != null) { if (deviceProfile.getFirmwareId() != null) {
Firmware firmware = firmwareService.findFirmwareById(tenantId, deviceProfile.getFirmwareId()); Firmware firmware = firmwareService.findFirmwareById(tenantId, deviceProfile.getFirmwareId());
if (firmware == null) { if (firmware == null) {

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

@ -84,6 +84,7 @@ import org.thingsboard.common.util.JacksonUtil;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
@ -245,7 +246,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
ConstraintViolationException e = extractConstraintViolationException(t).orElse(null); ConstraintViolationException e = extractConstraintViolationException(t).orElse(null);
if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("device_name_unq_key")) { if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("device_name_unq_key")) {
// remove device from cache in case null value cached in the distributed redis. // remove device from cache in case null value cached in the distributed redis.
removeDeviceFromCache(device.getTenantId(), device.getName()); removeDeviceFromCacheByName(device.getTenantId(), device.getName());
throw new DataValidationException("Device with such name already exists!"); throw new DataValidationException("Device with such name already exists!");
} else { } else {
throw t; throw t;
@ -322,17 +323,14 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
} }
deleteEntityRelations(tenantId, deviceId); deleteEntityRelations(tenantId, deviceId);
removeDeviceFromCache(tenantId, device.getName()); removeDeviceFromCacheByName(tenantId, device.getName());
deviceDao.removeById(tenantId, deviceId.getId()); deviceDao.removeById(tenantId, deviceId.getId());
} }
private void removeDeviceFromCache(TenantId tenantId, String name) { private void removeDeviceFromCacheByName(TenantId tenantId, String name) {
List<Object> list = new ArrayList<>();
list.add(tenantId);
list.add(name);
Cache cache = cacheManager.getCache(DEVICE_CACHE); Cache cache = cacheManager.getCache(DEVICE_CACHE);
cache.evict(list); cache.evict(Arrays.asList(tenantId, name));
} }
@Override @Override
@ -671,6 +669,9 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
if (old == null) { if (old == null) {
throw new DataValidationException("Can't update non existing device!"); throw new DataValidationException("Can't update non existing device!");
} }
if (!old.getName().equals(device.getName())) {
removeDeviceFromCacheByName(tenantId, old.getName());
}
} }
@Override @Override

19
dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java

@ -58,7 +58,6 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.UserId;
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.page.TimePageLink;
import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.EntitySearchDirection; import org.thingsboard.server.common.data.relation.EntitySearchDirection;
import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.relation.RelationTypeGroup;
@ -80,6 +79,7 @@ import javax.annotation.PostConstruct;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.Proxy; import java.net.Proxy;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
@ -222,17 +222,18 @@ public class EdgeServiceImpl extends AbstractEntityService implements EdgeServic
Edge edge = edgeDao.findById(tenantId, edgeId.getId()); Edge edge = edgeDao.findById(tenantId, edgeId.getId());
List<Object> list = new ArrayList<>();
list.add(edge.getTenantId());
list.add(edge.getName());
Cache cache = cacheManager.getCache(EDGE_CACHE);
cache.evict(list);
deleteEntityRelations(tenantId, edgeId); deleteEntityRelations(tenantId, edgeId);
removeEdgeFromCacheByName(edge.getTenantId(), edge.getName());
edgeDao.removeById(tenantId, edgeId.getId()); edgeDao.removeById(tenantId, edgeId.getId());
} }
private void removeEdgeFromCacheByName(TenantId tenantId, String name) {
Cache cache = cacheManager.getCache(EDGE_CACHE);
cache.evict(Arrays.asList(tenantId, name));
}
@Override @Override
public PageData<Edge> findEdgesByTenantId(TenantId tenantId, PageLink pageLink) { public PageData<Edge> findEdgesByTenantId(TenantId tenantId, PageLink pageLink) {
log.trace("Executing findEdgesByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink); log.trace("Executing findEdgesByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink);
@ -423,6 +424,10 @@ public class EdgeServiceImpl extends AbstractEntityService implements EdgeServic
@Override @Override
protected void validateUpdate(TenantId tenantId, Edge edge) { protected void validateUpdate(TenantId tenantId, Edge edge) {
Edge old = edgeDao.findById(edge.getTenantId(), edge.getId().getId());
if (!old.getName().equals(edge.getName())) {
removeEdgeFromCacheByName(tenantId, old.getName());
}
} }
@Override @Override

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

@ -22,6 +22,7 @@ import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.HasCustomerId;
import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.AssetId;
@ -54,6 +55,7 @@ import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.user.UserService;
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
import static org.thingsboard.server.dao.service.Validator.validateId; import static org.thingsboard.server.dao.service.Validator.validateId;
/** /**
@ -175,6 +177,50 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe
return entityName; return entityName;
} }
@Override
public CustomerId fetchEntityCustomerId(TenantId tenantId, EntityId entityId) {
log.trace("Executing fetchEntityCustomerId [{}]", entityId);
HasCustomerId hasCustomerId = null;
switch (entityId.getEntityType()) {
case TENANT:
case RULE_CHAIN:
case RULE_NODE:
case DASHBOARD:
case WIDGETS_BUNDLE:
case WIDGET_TYPE:
case TENANT_PROFILE:
case DEVICE_PROFILE:
case API_USAGE_STATE:
case TB_RESOURCE:
case FIRMWARE:
break;
case CUSTOMER:
hasCustomerId = () -> new CustomerId(entityId.getId());
break;
case USER:
hasCustomerId = userService.findUserById(tenantId, new UserId(entityId.getId()));
break;
case ASSET:
hasCustomerId = assetService.findAssetById(tenantId, new AssetId(entityId.getId()));
break;
case DEVICE:
hasCustomerId = deviceService.findDeviceById(tenantId, new DeviceId(entityId.getId()));
break;
case ALARM:
try {
hasCustomerId = alarmService.findAlarmByIdAsync(tenantId, new AlarmId(entityId.getId())).get();
} catch (Exception e) {}
break;
case ENTITY_VIEW:
hasCustomerId = entityViewService.findEntityViewById(tenantId, new EntityViewId(entityId.getId()));
break;
case EDGE:
hasCustomerId = edgeService.findEdgeById(tenantId, new EdgeId(entityId.getId()));
break;
}
return hasCustomerId != null ? hasCustomerId.getCustomerId() : new CustomerId(NULL_UUID);
}
private static void validateEntityCountQuery(EntityCountQuery query) { private static void validateEntityCountQuery(EntityCountQuery query) {
if (query == null) { if (query == null) {
throw new IncorrectParameterException("Query must be specified."); throw new IncorrectParameterException("Query must be specified.");

5
dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java

@ -170,12 +170,14 @@ public class ModelConstants {
public static final String DEVICE_PROFILE_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY; public static final String DEVICE_PROFILE_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY;
public static final String DEVICE_PROFILE_NAME_PROPERTY = "name"; public static final String DEVICE_PROFILE_NAME_PROPERTY = "name";
public static final String DEVICE_PROFILE_TYPE_PROPERTY = "type"; public static final String DEVICE_PROFILE_TYPE_PROPERTY = "type";
public static final String DEVICE_PROFILE_IMAGE_PROPERTY = "image";
public static final String DEVICE_PROFILE_TRANSPORT_TYPE_PROPERTY = "transport_type"; public static final String DEVICE_PROFILE_TRANSPORT_TYPE_PROPERTY = "transport_type";
public static final String DEVICE_PROFILE_PROVISION_TYPE_PROPERTY = "provision_type"; public static final String DEVICE_PROFILE_PROVISION_TYPE_PROPERTY = "provision_type";
public static final String DEVICE_PROFILE_PROFILE_DATA_PROPERTY = "profile_data"; public static final String DEVICE_PROFILE_PROFILE_DATA_PROPERTY = "profile_data";
public static final String DEVICE_PROFILE_DESCRIPTION_PROPERTY = "description"; public static final String DEVICE_PROFILE_DESCRIPTION_PROPERTY = "description";
public static final String DEVICE_PROFILE_IS_DEFAULT_PROPERTY = "is_default"; public static final String DEVICE_PROFILE_IS_DEFAULT_PROPERTY = "is_default";
public static final String DEVICE_PROFILE_DEFAULT_RULE_CHAIN_ID_PROPERTY = "default_rule_chain_id"; public static final String DEVICE_PROFILE_DEFAULT_RULE_CHAIN_ID_PROPERTY = "default_rule_chain_id";
public static final String DEVICE_PROFILE_DEFAULT_DASHBOARD_ID_PROPERTY = "default_dashboard_id";
public static final String DEVICE_PROFILE_DEFAULT_QUEUE_NAME_PROPERTY = "default_queue_name"; public static final String DEVICE_PROFILE_DEFAULT_QUEUE_NAME_PROPERTY = "default_queue_name";
public static final String DEVICE_PROFILE_PROVISION_DEVICE_KEY = "provision_device_key"; public static final String DEVICE_PROFILE_PROVISION_DEVICE_KEY = "provision_device_key";
public static final String DEVICE_PROFILE_FIRMWARE_ID_PROPERTY = "firmware_id"; public static final String DEVICE_PROFILE_FIRMWARE_ID_PROPERTY = "firmware_id";
@ -257,6 +259,7 @@ public class ModelConstants {
*/ */
public static final String ALARM_COLUMN_FAMILY_NAME = "alarm"; public static final String ALARM_COLUMN_FAMILY_NAME = "alarm";
public static final String ALARM_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY; public static final String ALARM_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY;
public static final String ALARM_CUSTOMER_ID_PROPERTY = CUSTOMER_ID_PROPERTY;
public static final String ALARM_TYPE_PROPERTY = "type"; public static final String ALARM_TYPE_PROPERTY = "type";
public static final String ALARM_DETAILS_PROPERTY = "details"; public static final String ALARM_DETAILS_PROPERTY = "details";
public static final String ALARM_ORIGINATOR_ID_PROPERTY = "originator_id"; public static final String ALARM_ORIGINATOR_ID_PROPERTY = "originator_id";
@ -333,6 +336,7 @@ public class ModelConstants {
public static final String DASHBOARD_COLUMN_FAMILY_NAME = "dashboard"; public static final String DASHBOARD_COLUMN_FAMILY_NAME = "dashboard";
public static final String DASHBOARD_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY; public static final String DASHBOARD_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY;
public static final String DASHBOARD_TITLE_PROPERTY = TITLE_PROPERTY; public static final String DASHBOARD_TITLE_PROPERTY = TITLE_PROPERTY;
public static final String DASHBOARD_IMAGE_PROPERTY = "image";
public static final String DASHBOARD_CONFIGURATION_PROPERTY = "configuration"; public static final String DASHBOARD_CONFIGURATION_PROPERTY = "configuration";
public static final String DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY = "assigned_customers"; public static final String DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY = "assigned_customers";
@ -461,6 +465,7 @@ public class ModelConstants {
public static final String API_USAGE_STATE_JS_EXEC_COLUMN = "js_exec"; public static final String API_USAGE_STATE_JS_EXEC_COLUMN = "js_exec";
public static final String API_USAGE_STATE_EMAIL_EXEC_COLUMN = "email_exec"; public static final String API_USAGE_STATE_EMAIL_EXEC_COLUMN = "email_exec";
public static final String API_USAGE_STATE_SMS_EXEC_COLUMN = "sms_exec"; public static final String API_USAGE_STATE_SMS_EXEC_COLUMN = "sms_exec";
public static final String API_USAGE_STATE_ALARM_EXEC_COLUMN = "alarm_exec";
/** /**
* Resource constants. * Resource constants.

12
dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmEntity.java

@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.model.BaseEntity; import org.thingsboard.server.dao.model.BaseEntity;
@ -44,6 +45,7 @@ import java.util.UUID;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ACK_TS_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ACK_TS_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_CLEAR_TS_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_CLEAR_TS_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_CUSTOMER_ID_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_END_TS_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_END_TS_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ORIGINATOR_ID_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ORIGINATOR_ID_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ORIGINATOR_TYPE_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ORIGINATOR_TYPE_PROPERTY;
@ -64,6 +66,9 @@ public abstract class AbstractAlarmEntity<T extends Alarm> extends BaseSqlEntity
@Column(name = ALARM_TENANT_ID_PROPERTY) @Column(name = ALARM_TENANT_ID_PROPERTY)
private UUID tenantId; private UUID tenantId;
@Column(name = ALARM_CUSTOMER_ID_PROPERTY)
private UUID customerId;
@Column(name = ALARM_ORIGINATOR_ID_PROPERTY) @Column(name = ALARM_ORIGINATOR_ID_PROPERTY)
private UUID originatorId; private UUID originatorId;
@ -115,6 +120,9 @@ public abstract class AbstractAlarmEntity<T extends Alarm> extends BaseSqlEntity
if (alarm.getTenantId() != null) { if (alarm.getTenantId() != null) {
this.tenantId = alarm.getTenantId().getId(); this.tenantId = alarm.getTenantId().getId();
} }
if (alarm.getCustomerId() != null) {
this.customerId = alarm.getCustomerId().getId();
}
this.type = alarm.getType(); this.type = alarm.getType();
this.originatorId = alarm.getOriginator().getId(); this.originatorId = alarm.getOriginator().getId();
this.originatorType = alarm.getOriginator().getEntityType(); this.originatorType = alarm.getOriginator().getEntityType();
@ -138,6 +146,7 @@ public abstract class AbstractAlarmEntity<T extends Alarm> extends BaseSqlEntity
this.setId(alarmEntity.getId()); this.setId(alarmEntity.getId());
this.setCreatedTime(alarmEntity.getCreatedTime()); this.setCreatedTime(alarmEntity.getCreatedTime());
this.tenantId = alarmEntity.getTenantId(); this.tenantId = alarmEntity.getTenantId();
this.customerId = alarmEntity.getCustomerId();
this.type = alarmEntity.getType(); this.type = alarmEntity.getType();
this.originatorId = alarmEntity.getOriginatorId(); this.originatorId = alarmEntity.getOriginatorId();
this.originatorType = alarmEntity.getOriginatorType(); this.originatorType = alarmEntity.getOriginatorType();
@ -159,6 +168,9 @@ public abstract class AbstractAlarmEntity<T extends Alarm> extends BaseSqlEntity
if (tenantId != null) { if (tenantId != null) {
alarm.setTenantId(new TenantId(tenantId)); alarm.setTenantId(new TenantId(tenantId));
} }
if (customerId != null) {
alarm.setCustomerId(new CustomerId(customerId));
}
alarm.setOriginator(EntityIdFactory.getByTypeAndUuid(originatorType, originatorId)); alarm.setOriginator(EntityIdFactory.getByTypeAndUuid(originatorType, originatorId));
alarm.setType(type); alarm.setType(type);
alarm.setSeverity(severity); alarm.setSeverity(severity);

5
dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmEntity.java

@ -19,6 +19,7 @@ import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import org.hibernate.annotations.TypeDef; import org.hibernate.annotations.TypeDef;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.dao.util.mapping.JsonStringType; import org.thingsboard.server.dao.util.mapping.JsonStringType;
import javax.persistence.Entity; import javax.persistence.Entity;
@ -37,6 +38,10 @@ public final class AlarmEntity extends AbstractAlarmEntity<Alarm> {
super(); super();
} }
public AlarmEntity(AlarmInfo alarmInfo) {
super(alarmInfo);
}
public AlarmEntity(Alarm alarm) { public AlarmEntity(Alarm alarm) {
super(alarm); super(alarm);
} }

5
dao/src/main/java/org/thingsboard/server/dao/model/sql/ApiUsageStateEntity.java

@ -69,6 +69,9 @@ public class ApiUsageStateEntity extends BaseSqlEntity<ApiUsageState> implements
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Column(name = ModelConstants.API_USAGE_STATE_SMS_EXEC_COLUMN) @Column(name = ModelConstants.API_USAGE_STATE_SMS_EXEC_COLUMN)
private ApiUsageStateValue smsExecState = ApiUsageStateValue.ENABLED; private ApiUsageStateValue smsExecState = ApiUsageStateValue.ENABLED;
@Enumerated(EnumType.STRING)
@Column(name = ModelConstants.API_USAGE_STATE_ALARM_EXEC_COLUMN)
private ApiUsageStateValue alarmExecState = ApiUsageStateValue.ENABLED;
public ApiUsageStateEntity() { public ApiUsageStateEntity() {
} }
@ -91,6 +94,7 @@ public class ApiUsageStateEntity extends BaseSqlEntity<ApiUsageState> implements
this.jsExecState = ur.getJsExecState(); this.jsExecState = ur.getJsExecState();
this.emailExecState = ur.getEmailExecState(); this.emailExecState = ur.getEmailExecState();
this.smsExecState = ur.getSmsExecState(); this.smsExecState = ur.getSmsExecState();
this.alarmExecState = ur.getAlarmExecState();
} }
@Override @Override
@ -109,6 +113,7 @@ public class ApiUsageStateEntity extends BaseSqlEntity<ApiUsageState> implements
ur.setJsExecState(jsExecState); ur.setJsExecState(jsExecState);
ur.setEmailExecState(emailExecState); ur.setEmailExecState(emailExecState);
ur.setSmsExecState(smsExecState); ur.setSmsExecState(smsExecState);
ur.setAlarmExecState(alarmExecState);
return ur; return ur;
} }

7
dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardEntity.java

@ -58,7 +58,10 @@ public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements S
@Column(name = ModelConstants.DASHBOARD_TITLE_PROPERTY) @Column(name = ModelConstants.DASHBOARD_TITLE_PROPERTY)
private String title; private String title;
@Column(name = ModelConstants.DASHBOARD_IMAGE_PROPERTY)
private String image;
@Column(name = ModelConstants.SEARCH_TEXT_PROPERTY) @Column(name = ModelConstants.SEARCH_TEXT_PROPERTY)
private String searchText; private String searchText;
@ -82,6 +85,7 @@ public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements S
this.tenantId = dashboard.getTenantId().getId(); this.tenantId = dashboard.getTenantId().getId();
} }
this.title = dashboard.getTitle(); this.title = dashboard.getTitle();
this.image = dashboard.getImage();
if (dashboard.getAssignedCustomers() != null) { if (dashboard.getAssignedCustomers() != null) {
try { try {
this.assignedCustomers = objectMapper.writeValueAsString(dashboard.getAssignedCustomers()); this.assignedCustomers = objectMapper.writeValueAsString(dashboard.getAssignedCustomers());
@ -110,6 +114,7 @@ public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements S
dashboard.setTenantId(new TenantId(tenantId)); dashboard.setTenantId(new TenantId(tenantId));
} }
dashboard.setTitle(title); dashboard.setTitle(title);
dashboard.setImage(image);
if (!StringUtils.isEmpty(assignedCustomers)) { if (!StringUtils.isEmpty(assignedCustomers)) {
try { try {
dashboard.setAssignedCustomers(objectMapper.readValue(assignedCustomers, assignedCustomersType)); dashboard.setAssignedCustomers(objectMapper.readValue(assignedCustomers, assignedCustomersType));

5
dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardInfoEntity.java

@ -54,6 +54,9 @@ public class DashboardInfoEntity extends BaseSqlEntity<DashboardInfo> implements
@Column(name = ModelConstants.DASHBOARD_TITLE_PROPERTY) @Column(name = ModelConstants.DASHBOARD_TITLE_PROPERTY)
private String title; private String title;
@Column(name = ModelConstants.DASHBOARD_IMAGE_PROPERTY)
private String image;
@Column(name = ModelConstants.SEARCH_TEXT_PROPERTY) @Column(name = ModelConstants.SEARCH_TEXT_PROPERTY)
private String searchText; private String searchText;
@ -73,6 +76,7 @@ public class DashboardInfoEntity extends BaseSqlEntity<DashboardInfo> implements
this.tenantId = dashboardInfo.getTenantId().getId(); this.tenantId = dashboardInfo.getTenantId().getId();
} }
this.title = dashboardInfo.getTitle(); this.title = dashboardInfo.getTitle();
this.image = dashboardInfo.getImage();
if (dashboardInfo.getAssignedCustomers() != null) { if (dashboardInfo.getAssignedCustomers() != null) {
try { try {
this.assignedCustomers = objectMapper.writeValueAsString(dashboardInfo.getAssignedCustomers()); this.assignedCustomers = objectMapper.writeValueAsString(dashboardInfo.getAssignedCustomers());
@ -104,6 +108,7 @@ public class DashboardInfoEntity extends BaseSqlEntity<DashboardInfo> implements
dashboardInfo.setTenantId(new TenantId(tenantId)); dashboardInfo.setTenantId(new TenantId(tenantId));
} }
dashboardInfo.setTitle(title); dashboardInfo.setTitle(title);
dashboardInfo.setImage(image);
if (!StringUtils.isEmpty(assignedCustomers)) { if (!StringUtils.isEmpty(assignedCustomers)) {
try { try {
dashboardInfo.setAssignedCustomers(objectMapper.readValue(assignedCustomers, assignedCustomersType)); dashboardInfo.setAssignedCustomers(objectMapper.readValue(assignedCustomers, assignedCustomersType));

13
dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java

@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.DeviceProfileProvisionType;
import org.thingsboard.server.common.data.DeviceProfileType; import org.thingsboard.server.common.data.DeviceProfileType;
import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData; import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.FirmwareId; import org.thingsboard.server.common.data.id.FirmwareId;
import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleChainId;
@ -60,6 +61,9 @@ public final class DeviceProfileEntity extends BaseSqlEntity<DeviceProfile> impl
@Column(name = ModelConstants.DEVICE_PROFILE_TYPE_PROPERTY) @Column(name = ModelConstants.DEVICE_PROFILE_TYPE_PROPERTY)
private DeviceProfileType type; private DeviceProfileType type;
@Column(name = ModelConstants.DEVICE_PROFILE_IMAGE_PROPERTY)
private String image;
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Column(name = ModelConstants.DEVICE_PROFILE_TRANSPORT_TYPE_PROPERTY) @Column(name = ModelConstants.DEVICE_PROFILE_TRANSPORT_TYPE_PROPERTY)
private DeviceTransportType transportType; private DeviceTransportType transportType;
@ -80,6 +84,9 @@ public final class DeviceProfileEntity extends BaseSqlEntity<DeviceProfile> impl
@Column(name = ModelConstants.DEVICE_PROFILE_DEFAULT_RULE_CHAIN_ID_PROPERTY, columnDefinition = "uuid") @Column(name = ModelConstants.DEVICE_PROFILE_DEFAULT_RULE_CHAIN_ID_PROPERTY, columnDefinition = "uuid")
private UUID defaultRuleChainId; private UUID defaultRuleChainId;
@Column(name = ModelConstants.DEVICE_PROFILE_DEFAULT_DASHBOARD_ID_PROPERTY)
private UUID defaultDashboardId;
@Column(name = ModelConstants.DEVICE_PROFILE_DEFAULT_QUEUE_NAME_PROPERTY) @Column(name = ModelConstants.DEVICE_PROFILE_DEFAULT_QUEUE_NAME_PROPERTY)
private String defaultQueueName; private String defaultQueueName;
@ -124,7 +131,7 @@ public final class DeviceProfileEntity extends BaseSqlEntity<DeviceProfile> impl
this.firmwareId = deviceProfile.getFirmwareId().getId(); this.firmwareId = deviceProfile.getFirmwareId().getId();
} }
if (deviceProfile.getSoftwareId() != null) { if (deviceProfile.getSoftwareId() != null) {
this.firmwareId = deviceProfile.getSoftwareId().getId(); this.softwareId = deviceProfile.getSoftwareId().getId();
} }
} }
@ -151,6 +158,7 @@ public final class DeviceProfileEntity extends BaseSqlEntity<DeviceProfile> impl
} }
deviceProfile.setName(name); deviceProfile.setName(name);
deviceProfile.setType(type); deviceProfile.setType(type);
deviceProfile.setImage(image);
deviceProfile.setTransportType(transportType); deviceProfile.setTransportType(transportType);
deviceProfile.setProvisionType(provisionType); deviceProfile.setProvisionType(provisionType);
deviceProfile.setDescription(description); deviceProfile.setDescription(description);
@ -159,6 +167,9 @@ public final class DeviceProfileEntity extends BaseSqlEntity<DeviceProfile> impl
if (defaultRuleChainId != null) { if (defaultRuleChainId != null) {
deviceProfile.setDefaultRuleChainId(new RuleChainId(defaultRuleChainId)); deviceProfile.setDefaultRuleChainId(new RuleChainId(defaultRuleChainId));
} }
if (defaultDashboardId != null) {
deviceProfile.setDefaultDashboardId(new DashboardId(defaultDashboardId));
}
deviceProfile.setDefaultQueueName(defaultQueueName); deviceProfile.setDefaultQueueName(defaultQueueName);
deviceProfile.setProvisionDeviceKey(provisionDeviceKey); deviceProfile.setProvisionDeviceKey(provisionDeviceKey);

136
dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java

@ -100,12 +100,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
@Transactional @Transactional
public RuleChain saveRuleChain(RuleChain ruleChain) { public RuleChain saveRuleChain(RuleChain ruleChain) {
ruleChainValidator.validate(ruleChain, RuleChain::getTenantId); ruleChainValidator.validate(ruleChain, RuleChain::getTenantId);
RuleChain savedRuleChain = ruleChainDao.save(ruleChain.getTenantId(), ruleChain); return ruleChainDao.save(ruleChain.getTenantId(), ruleChain);
if (ruleChain.isRoot() && ruleChain.getId() == null) {
createRelation(ruleChain.getTenantId(), new EntityRelation(savedRuleChain.getTenantId(), savedRuleChain.getId(),
EntityRelation.CONTAINS_TYPE, RelationTypeGroup.RULE_CHAIN));
}
return savedRuleChain;
} }
@Override @Override
@ -114,29 +109,20 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
RuleChain ruleChain = ruleChainDao.findById(tenantId, ruleChainId.getId()); RuleChain ruleChain = ruleChainDao.findById(tenantId, ruleChainId.getId());
if (!ruleChain.isRoot()) { if (!ruleChain.isRoot()) {
RuleChain previousRootRuleChain = getRootTenantRuleChain(ruleChain.getTenantId()); RuleChain previousRootRuleChain = getRootTenantRuleChain(ruleChain.getTenantId());
try { if (previousRootRuleChain == null) {
if (previousRootRuleChain == null) { setRootAndSave(tenantId, ruleChain);
setRootAndSave(tenantId, ruleChain); return true;
return true; } else if (!previousRootRuleChain.getId().equals(ruleChain.getId())) {
} else if (!previousRootRuleChain.getId().equals(ruleChain.getId())) { previousRootRuleChain.setRoot(false);
deleteRelation(tenantId, new EntityRelation(previousRootRuleChain.getTenantId(), previousRootRuleChain.getId(), ruleChainDao.save(tenantId, previousRootRuleChain);
EntityRelation.CONTAINS_TYPE, RelationTypeGroup.RULE_CHAIN)); setRootAndSave(tenantId, ruleChain);
previousRootRuleChain.setRoot(false); return true;
ruleChainDao.save(tenantId, previousRootRuleChain);
setRootAndSave(tenantId, ruleChain);
return true;
}
} catch (ExecutionException | InterruptedException e) {
log.warn("[{}] Failed to set root rule chain, ruleChainId: [{}]", ruleChainId);
throw new RuntimeException(e);
} }
} }
return false; return false;
} }
private void setRootAndSave(TenantId tenantId, RuleChain ruleChain) throws ExecutionException, InterruptedException { private void setRootAndSave(TenantId tenantId, RuleChain ruleChain) {
createRelation(tenantId, new EntityRelation(ruleChain.getTenantId(), ruleChain.getId(),
EntityRelation.CONTAINS_TYPE, RelationTypeGroup.RULE_CHAIN));
ruleChain.setRoot(true); ruleChain.setRoot(true);
ruleChainDao.save(tenantId, ruleChain); ruleChainDao.save(tenantId, ruleChain);
} }
@ -179,41 +165,45 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
toDelete.add(existingNode); toDelete.add(existingNode);
} }
} }
for (RuleNode node : toAddOrUpdate) { if (nodes != null) {
node.setRuleChainId(ruleChain.getId()); for (RuleNode node : toAddOrUpdate) {
RuleNode savedNode = ruleNodeDao.save(tenantId, node); node.setRuleChainId(ruleChain.getId());
createRelation(tenantId, new EntityRelation(ruleChainMetaData.getRuleChainId(), savedNode.getId(), RuleNode savedNode = ruleNodeDao.save(tenantId, node);
EntityRelation.CONTAINS_TYPE, RelationTypeGroup.RULE_CHAIN)); createRelation(tenantId, new EntityRelation(ruleChainMetaData.getRuleChainId(), savedNode.getId(),
int index = nodes.indexOf(node); EntityRelation.CONTAINS_TYPE, RelationTypeGroup.RULE_CHAIN));
nodes.set(index, savedNode); int index = nodes.indexOf(node);
ruleNodeIndexMap.put(savedNode.getId(), index); nodes.set(index, savedNode);
ruleNodeIndexMap.put(savedNode.getId(), index);
}
} }
for (RuleNode node : toDelete) { for (RuleNode node : toDelete) {
deleteRuleNode(tenantId, node.getId()); deleteRuleNode(tenantId, node.getId());
} }
RuleNodeId firstRuleNodeId = null; RuleNodeId firstRuleNodeId = null;
if (ruleChainMetaData.getFirstNodeIndex() != null) { if (nodes != null) {
firstRuleNodeId = nodes.get(ruleChainMetaData.getFirstNodeIndex()).getId(); if (ruleChainMetaData.getFirstNodeIndex() != null) {
} firstRuleNodeId = nodes.get(ruleChainMetaData.getFirstNodeIndex()).getId();
if ((ruleChain.getFirstRuleNodeId() != null && !ruleChain.getFirstRuleNodeId().equals(firstRuleNodeId))
|| (ruleChain.getFirstRuleNodeId() == null && firstRuleNodeId != null)) {
ruleChain.setFirstRuleNodeId(firstRuleNodeId);
ruleChainDao.save(tenantId, ruleChain);
}
if (ruleChainMetaData.getConnections() != null) {
for (NodeConnectionInfo nodeConnection : ruleChainMetaData.getConnections()) {
EntityId from = nodes.get(nodeConnection.getFromIndex()).getId();
EntityId to = nodes.get(nodeConnection.getToIndex()).getId();
String type = nodeConnection.getType();
createRelation(tenantId, new EntityRelation(from, to, type, RelationTypeGroup.RULE_NODE));
} }
} if ((ruleChain.getFirstRuleNodeId() != null && !ruleChain.getFirstRuleNodeId().equals(firstRuleNodeId))
if (ruleChainMetaData.getRuleChainConnections() != null) { || (ruleChain.getFirstRuleNodeId() == null && firstRuleNodeId != null)) {
for (RuleChainConnectionInfo nodeToRuleChainConnection : ruleChainMetaData.getRuleChainConnections()) { ruleChain.setFirstRuleNodeId(firstRuleNodeId);
EntityId from = nodes.get(nodeToRuleChainConnection.getFromIndex()).getId(); ruleChainDao.save(tenantId, ruleChain);
EntityId to = nodeToRuleChainConnection.getTargetRuleChainId(); }
String type = nodeToRuleChainConnection.getType(); if (ruleChainMetaData.getConnections() != null) {
createRelation(tenantId, new EntityRelation(from, to, type, RelationTypeGroup.RULE_NODE, nodeToRuleChainConnection.getAdditionalInfo())); for (NodeConnectionInfo nodeConnection : ruleChainMetaData.getConnections()) {
EntityId from = nodes.get(nodeConnection.getFromIndex()).getId();
EntityId to = nodes.get(nodeConnection.getToIndex()).getId();
String type = nodeConnection.getType();
createRelation(tenantId, new EntityRelation(from, to, type, RelationTypeGroup.RULE_NODE));
}
}
if (ruleChainMetaData.getRuleChainConnections() != null) {
for (RuleChainConnectionInfo nodeToRuleChainConnection : ruleChainMetaData.getRuleChainConnections()) {
EntityId from = nodes.get(nodeToRuleChainConnection.getFromIndex()).getId();
EntityId to = nodeToRuleChainConnection.getTargetRuleChainId();
String type = nodeToRuleChainConnection.getType();
createRelation(tenantId, new EntityRelation(from, to, type, RelationTypeGroup.RULE_NODE, nodeToRuleChainConnection.getAdditionalInfo()));
}
} }
} }
@ -307,24 +297,8 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
@Override @Override
public RuleChain getRootTenantRuleChain(TenantId tenantId) { public RuleChain getRootTenantRuleChain(TenantId tenantId) {
return getRootRuleChainByType(tenantId, RuleChainType.CORE);
}
private RuleChain getRootRuleChainByType(TenantId tenantId, RuleChainType type) {
Validator.validateId(tenantId, "Incorrect tenant id for search request."); Validator.validateId(tenantId, "Incorrect tenant id for search request.");
List<EntityRelation> relations = relationService.findByFrom(tenantId, tenantId, RelationTypeGroup.RULE_CHAIN); return ruleChainDao.findRootRuleChainByTenantIdAndType(tenantId.getId(), RuleChainType.CORE);
if (relations != null && !relations.isEmpty()) {
for (EntityRelation relation : relations) {
RuleChainId ruleChainId = new RuleChainId(relation.getTo().getId());
RuleChain ruleChainById = findRuleChainById(tenantId, ruleChainId);
if (type.equals(ruleChainById.getType())) {
return ruleChainById;
}
}
return null;
} else {
return null;
}
} }
@Override @Override
@ -366,7 +340,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
boolean valid = true; boolean valid = true;
EntityType toType = relation.getTo().getEntityType(); EntityType toType = relation.getTo().getEntityType();
if (toType == EntityType.RULE_NODE || toType == EntityType.RULE_CHAIN) { if (toType == EntityType.RULE_NODE || toType == EntityType.RULE_CHAIN) {
BaseData entity; BaseData<?> entity;
if (relation.getTo().getEntityType() == EntityType.RULE_NODE) { if (relation.getTo().getEntityType() == EntityType.RULE_NODE) {
entity = ruleNodeDao.findById(tenantId, relation.getTo().getId()); entity = ruleNodeDao.findById(tenantId, relation.getTo().getId());
} else { } else {
@ -501,9 +475,8 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
ObjectNode objNode = (ObjectNode) node; ObjectNode objNode = (ObjectNode) node;
objNode.put("id", tenantId.getId().toString()); objNode.put("id", tenantId.getId().toString());
} else { } else {
Iterator<JsonNode> childIter = node.iterator(); for (JsonNode jsonNode : node) {
while (childIter.hasNext()) { searchTenantIdRecursive(tenantId, jsonNode);
searchTenantIdRecursive(tenantId, childIter.next());
} }
} }
} }
@ -608,7 +581,8 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
@Override @Override
public RuleChain getEdgeTemplateRootRuleChain(TenantId tenantId) { public RuleChain getEdgeTemplateRootRuleChain(TenantId tenantId) {
return getRootRuleChainByType(tenantId, RuleChainType.EDGE); Validator.validateId(tenantId, "Incorrect tenant id for search request.");
return ruleChainDao.findRootRuleChainByTenantIdAndType(tenantId.getId(), RuleChainType.EDGE);
} }
@Override @Override
@ -618,13 +592,9 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
if (previousEdgeTemplateRootRuleChain == null || !previousEdgeTemplateRootRuleChain.getId().equals(ruleChain.getId())) { if (previousEdgeTemplateRootRuleChain == null || !previousEdgeTemplateRootRuleChain.getId().equals(ruleChain.getId())) {
try { try {
if (previousEdgeTemplateRootRuleChain != null) { if (previousEdgeTemplateRootRuleChain != null) {
deleteRelation(tenantId, new EntityRelation(previousEdgeTemplateRootRuleChain.getTenantId(), previousEdgeTemplateRootRuleChain.getId(),
EntityRelation.CONTAINS_TYPE, RelationTypeGroup.RULE_CHAIN));
previousEdgeTemplateRootRuleChain.setRoot(false); previousEdgeTemplateRootRuleChain.setRoot(false);
ruleChainDao.save(tenantId, previousEdgeTemplateRootRuleChain); ruleChainDao.save(tenantId, previousEdgeTemplateRootRuleChain);
} }
createRelation(tenantId, new EntityRelation(ruleChain.getTenantId(), ruleChain.getId(),
EntityRelation.CONTAINS_TYPE, RelationTypeGroup.RULE_CHAIN));
ruleChain.setRoot(true); ruleChain.setRoot(true);
ruleChainDao.save(tenantId, ruleChain); ruleChainDao.save(tenantId, ruleChain);
return true; return true;
@ -699,8 +669,8 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
ruleNodeDao.removeById(tenantId, entityId.getId()); ruleNodeDao.removeById(tenantId, entityId.getId());
} }
private DataValidator<RuleChain> ruleChainValidator = private final DataValidator<RuleChain> ruleChainValidator =
new DataValidator<RuleChain>() { new DataValidator<>() {
@Override @Override
protected void validateCreate(TenantId tenantId, RuleChain data) { protected void validateCreate(TenantId tenantId, RuleChain data) {
DefaultTenantProfileConfiguration profileConfiguration = DefaultTenantProfileConfiguration profileConfiguration =
@ -739,8 +709,8 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
} }
}; };
private PaginatedRemover<TenantId, RuleChain> tenantRuleChainsRemover = private final PaginatedRemover<TenantId, RuleChain> tenantRuleChainsRemover =
new PaginatedRemover<TenantId, RuleChain>() { new PaginatedRemover<>() {
@Override @Override
protected PageData<RuleChain> findEntities(TenantId tenantId, TenantId id, PageLink pageLink) { protected PageData<RuleChain> findEntities(TenantId tenantId, TenantId id, PageLink pageLink) {

9
dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java

@ -50,6 +50,15 @@ public interface RuleChainDao extends Dao<RuleChain>, TenantEntityDao {
*/ */
PageData<RuleChain> findRuleChainsByTenantIdAndType(UUID tenantId, RuleChainType type, PageLink pageLink); PageData<RuleChain> findRuleChainsByTenantIdAndType(UUID tenantId, RuleChainType type, PageLink pageLink);
/**
* Find root rule chain by tenantId and type
*
* @param tenantId the tenantId
* @param type the type
* @return the rule chain object
*/
RuleChain findRootRuleChainByTenantIdAndType(UUID tenantId, RuleChainType type);
/** /**
* Find rule chains by tenantId, edgeId and page link. * Find rule chains by tenantId, edgeId and page link.
* *

52
dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java

@ -94,6 +94,58 @@ public interface AlarmRepository extends CrudRepository<AlarmEntity, UUID> {
@Param("searchText") String searchText, @Param("searchText") String searchText,
Pageable pageable); Pageable pageable);
@Query(value = "SELECT new org.thingsboard.server.dao.model.sql.AlarmInfoEntity(a) FROM AlarmEntity a " +
"WHERE a.tenantId = :tenantId " +
"AND (:startTime IS NULL OR a.createdTime >= :startTime) " +
"AND (:endTime IS NULL OR a.createdTime <= :endTime) " +
"AND ((:alarmStatuses) IS NULL OR a.status in (:alarmStatuses)) " +
"AND (LOWER(a.type) LIKE LOWER(CONCAT(:searchText, '%')) " +
" OR LOWER(a.severity) LIKE LOWER(CONCAT(:searchText, '%')) " +
" OR LOWER(a.status) LIKE LOWER(CONCAT(:searchText, '%'))) ",
countQuery = "" +
"SELECT count(a) " +
"FROM AlarmEntity a " +
"WHERE a.tenantId = :tenantId " +
"AND (:startTime IS NULL OR a.createdTime >= :startTime) " +
"AND (:endTime IS NULL OR a.createdTime <= :endTime) " +
"AND ((:alarmStatuses) IS NULL OR a.status in (:alarmStatuses)) " +
"AND (LOWER(a.type) LIKE LOWER(CONCAT(:searchText, '%')) " +
" OR LOWER(a.severity) LIKE LOWER(CONCAT(:searchText, '%')) " +
" OR LOWER(a.status) LIKE LOWER(CONCAT(:searchText, '%'))) ")
Page<AlarmInfoEntity> findAllAlarms(@Param("tenantId") UUID tenantId,
@Param("startTime") Long startTime,
@Param("endTime") Long endTime,
@Param("alarmStatuses") Set<AlarmStatus> alarmStatuses,
@Param("searchText") String searchText,
Pageable pageable);
@Query(value = "SELECT new org.thingsboard.server.dao.model.sql.AlarmInfoEntity(a) FROM AlarmEntity a " +
"WHERE a.tenantId = :tenantId AND a.customerId = :customerId " +
"AND (:startTime IS NULL OR a.createdTime >= :startTime) " +
"AND (:endTime IS NULL OR a.createdTime <= :endTime) " +
"AND ((:alarmStatuses) IS NULL OR a.status in (:alarmStatuses)) " +
"AND (LOWER(a.type) LIKE LOWER(CONCAT(:searchText, '%')) " +
" OR LOWER(a.severity) LIKE LOWER(CONCAT(:searchText, '%')) " +
" OR LOWER(a.status) LIKE LOWER(CONCAT(:searchText, '%'))) "
,
countQuery = "" +
"SELECT count(a) " +
"FROM AlarmEntity a " +
"WHERE a.tenantId = :tenantId AND a.customerId = :customerId " +
"AND (:startTime IS NULL OR a.createdTime >= :startTime) " +
"AND (:endTime IS NULL OR a.createdTime <= :endTime) " +
"AND ((:alarmStatuses) IS NULL OR a.status in (:alarmStatuses)) " +
"AND (LOWER(a.type) LIKE LOWER(CONCAT(:searchText, '%')) " +
" OR LOWER(a.severity) LIKE LOWER(CONCAT(:searchText, '%')) " +
" OR LOWER(a.status) LIKE LOWER(CONCAT(:searchText, '%'))) ")
Page<AlarmInfoEntity> findCustomerAlarms(@Param("tenantId") UUID tenantId,
@Param("customerId") UUID customerId,
@Param("startTime") Long startTime,
@Param("endTime") Long endTime,
@Param("alarmStatuses") Set<AlarmStatus> alarmStatuses,
@Param("searchText") String searchText,
Pageable pageable);
@Query(value = "SELECT a.severity FROM AlarmEntity a " + @Query(value = "SELECT a.severity FROM AlarmEntity a " +
"LEFT JOIN RelationEntity re ON a.id = re.toId " + "LEFT JOIN RelationEntity re ON a.id = re.toId " +
"AND re.relationTypeGroup = 'ALARM' " + "AND re.relationTypeGroup = 'ALARM' " +

41
dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java

@ -103,11 +103,46 @@ public class JpaAlarmDao extends JpaAbstractDao<AlarmEntity, Alarm> implements A
} else if (query.getStatus() != null) { } else if (query.getStatus() != null) {
statusSet = Collections.singleton(query.getStatus()); statusSet = Collections.singleton(query.getStatus());
} }
if (affectedEntity != null) {
return DaoUtil.toPageData(
alarmRepository.findAlarms(
tenantId.getId(),
affectedEntity.getId(),
affectedEntity.getEntityType().name(),
query.getPageLink().getStartTime(),
query.getPageLink().getEndTime(),
statusSet,
Objects.toString(query.getPageLink().getTextSearch(), ""),
DaoUtil.toPageable(query.getPageLink())
)
);
} else {
return DaoUtil.toPageData(
alarmRepository.findAllAlarms(
tenantId.getId(),
query.getPageLink().getStartTime(),
query.getPageLink().getEndTime(),
statusSet,
Objects.toString(query.getPageLink().getTextSearch(), ""),
DaoUtil.toPageable(query.getPageLink())
)
);
}
}
@Override
public PageData<AlarmInfo> findCustomerAlarms(TenantId tenantId, CustomerId customerId, AlarmQuery query) {
log.trace("Try to find customer alarms by status [{}] and pageLink [{}]", query.getStatus(), query.getPageLink());
Set<AlarmStatus> statusSet = null;
if (query.getSearchStatus() != null) {
statusSet = query.getSearchStatus().getStatuses();
} else if (query.getStatus() != null) {
statusSet = Collections.singleton(query.getStatus());
}
return DaoUtil.toPageData( return DaoUtil.toPageData(
alarmRepository.findAlarms( alarmRepository.findCustomerAlarms(
tenantId.getId(), tenantId.getId(),
affectedEntity.getId(), customerId.getId(),
affectedEntity.getEntityType().name(),
query.getPageLink().getStartTime(), query.getPageLink().getStartTime(),
query.getPageLink().getEndTime(), query.getPageLink().getEndTime(),
statusSet, statusSet,

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

@ -28,7 +28,7 @@ import java.util.UUID;
public interface DeviceProfileRepository extends PagingAndSortingRepository<DeviceProfileEntity, UUID> { public interface DeviceProfileRepository extends PagingAndSortingRepository<DeviceProfileEntity, UUID> {
@Query("SELECT new org.thingsboard.server.common.data.DeviceProfileInfo(d.id, d.name, d.type, d.transportType) " + @Query("SELECT new org.thingsboard.server.common.data.DeviceProfileInfo(d.id, d.name, d.image, d.defaultDashboardId, d.type, d.transportType) " +
"FROM DeviceProfileEntity d " + "FROM DeviceProfileEntity d " +
"WHERE d.id = :deviceProfileId") "WHERE d.id = :deviceProfileId")
DeviceProfileInfo findDeviceProfileInfoById(@Param("deviceProfileId") UUID deviceProfileId); DeviceProfileInfo findDeviceProfileInfoById(@Param("deviceProfileId") UUID deviceProfileId);
@ -39,14 +39,14 @@ public interface DeviceProfileRepository extends PagingAndSortingRepository<Devi
@Param("textSearch") String textSearch, @Param("textSearch") String textSearch,
Pageable pageable); Pageable pageable);
@Query("SELECT new org.thingsboard.server.common.data.DeviceProfileInfo(d.id, d.name, d.type, d.transportType) " + @Query("SELECT new org.thingsboard.server.common.data.DeviceProfileInfo(d.id, d.name, d.image, d.defaultDashboardId, d.type, d.transportType) " +
"FROM DeviceProfileEntity d WHERE " + "FROM DeviceProfileEntity d WHERE " +
"d.tenantId = :tenantId AND LOWER(d.searchText) LIKE LOWER(CONCAT(:textSearch, '%'))") "d.tenantId = :tenantId AND LOWER(d.searchText) LIKE LOWER(CONCAT(:textSearch, '%'))")
Page<DeviceProfileInfo> findDeviceProfileInfos(@Param("tenantId") UUID tenantId, Page<DeviceProfileInfo> findDeviceProfileInfos(@Param("tenantId") UUID tenantId,
@Param("textSearch") String textSearch, @Param("textSearch") String textSearch,
Pageable pageable); Pageable pageable);
@Query("SELECT new org.thingsboard.server.common.data.DeviceProfileInfo(d.id, d.name, d.type, d.transportType) " + @Query("SELECT new org.thingsboard.server.common.data.DeviceProfileInfo(d.id, d.name, d.image, d.defaultDashboardId, d.type, d.transportType) " +
"FROM DeviceProfileEntity d WHERE " + "FROM DeviceProfileEntity d WHERE " +
"d.tenantId = :tenantId AND d.transportType = :transportType AND LOWER(d.searchText) LIKE LOWER(CONCAT(:textSearch, '%'))") "d.tenantId = :tenantId AND d.transportType = :transportType AND LOWER(d.searchText) LIKE LOWER(CONCAT(:textSearch, '%'))")
Page<DeviceProfileInfo> findDeviceProfileInfos(@Param("tenantId") UUID tenantId, Page<DeviceProfileInfo> findDeviceProfileInfos(@Param("tenantId") UUID tenantId,
@ -58,7 +58,7 @@ public interface DeviceProfileRepository extends PagingAndSortingRepository<Devi
"WHERE d.tenantId = :tenantId AND d.isDefault = true") "WHERE d.tenantId = :tenantId AND d.isDefault = true")
DeviceProfileEntity findByDefaultTrueAndTenantId(@Param("tenantId") UUID tenantId); DeviceProfileEntity findByDefaultTrueAndTenantId(@Param("tenantId") UUID tenantId);
@Query("SELECT new org.thingsboard.server.common.data.DeviceProfileInfo(d.id, d.name, d.type, d.transportType) " + @Query("SELECT new org.thingsboard.server.common.data.DeviceProfileInfo(d.id, d.name, d.image, d.defaultDashboardId, d.type, d.transportType) " +
"FROM DeviceProfileEntity d " + "FROM DeviceProfileEntity d " +
"WHERE d.tenantId = :tenantId AND d.isDefault = true") "WHERE d.tenantId = :tenantId AND d.isDefault = true")
DeviceProfileInfo findDefaultDeviceProfileInfo(@Param("tenantId") UUID tenantId); DeviceProfileInfo findDefaultDeviceProfileInfo(@Param("tenantId") UUID tenantId);

4
dao/src/main/java/org/thingsboard/server/dao/sql/query/AlarmDataAdapter.java

@ -24,6 +24,7 @@ import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
@ -86,6 +87,9 @@ public class AlarmDataAdapter {
alarm.setSeverity(AlarmSeverity.valueOf(row.get(ModelConstants.ALARM_SEVERITY_PROPERTY).toString())); alarm.setSeverity(AlarmSeverity.valueOf(row.get(ModelConstants.ALARM_SEVERITY_PROPERTY).toString()));
alarm.setStatus(AlarmStatus.valueOf(row.get(ModelConstants.ALARM_STATUS_PROPERTY).toString())); alarm.setStatus(AlarmStatus.valueOf(row.get(ModelConstants.ALARM_STATUS_PROPERTY).toString()));
alarm.setTenantId(new TenantId((UUID) row.get(ModelConstants.TENANT_ID_PROPERTY))); alarm.setTenantId(new TenantId((UUID) row.get(ModelConstants.TENANT_ID_PROPERTY)));
Object customerIdObj = row.get(ModelConstants.CUSTOMER_ID_PROPERTY);
CustomerId customerId = customerIdObj != null ? new CustomerId((UUID) customerIdObj) : null;
alarm.setCustomerId(customerId);
if (row.get(ModelConstants.ALARM_PROPAGATE_RELATION_TYPES) != null) { if (row.get(ModelConstants.ALARM_PROPAGATE_RELATION_TYPES) != null) {
String propagateRelationTypes = row.get(ModelConstants.ALARM_PROPAGATE_RELATION_TYPES).toString(); String propagateRelationTypes = row.get(ModelConstants.ALARM_PROPAGATE_RELATION_TYPES).toString();
if (!StringUtils.isEmpty(propagateRelationTypes)) { if (!StringUtils.isEmpty(propagateRelationTypes)) {

1
dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java

@ -101,6 +101,7 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository {
" a.start_ts as start_ts," + " a.start_ts as start_ts," +
" a.status as status, " + " a.status as status, " +
" a.tenant_id as tenant_id, " + " a.tenant_id as tenant_id, " +
" a.customer_id as customer_id, " +
" a.propagate_relation_types as propagate_relation_types, " + " a.propagate_relation_types as propagate_relation_types, " +
" a.type as type," + SELECT_ORIGINATOR_NAME + ", "; " a.type as type," + SELECT_ORIGINATOR_NAME + ", ";

6
dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java

@ -82,6 +82,12 @@ public class JpaRuleChainDao extends JpaAbstractSearchTextDao<RuleChainEntity, R
DaoUtil.toPageable(pageLink))); DaoUtil.toPageable(pageLink)));
} }
@Override
public RuleChain findRootRuleChainByTenantIdAndType(UUID tenantId, RuleChainType type) {
log.debug("Try to find root rule chain by tenantId [{}] and type [{}]", tenantId, type);
return DaoUtil.getData(ruleChainRepository.findByTenantIdAndTypeAndRootIsTrue(tenantId, type));
}
@Override @Override
public PageData<RuleChain> findRuleChainsByTenantIdAndEdgeId(UUID tenantId, UUID edgeId, PageLink pageLink) { public PageData<RuleChain> findRuleChainsByTenantIdAndEdgeId(UUID tenantId, UUID edgeId, PageLink pageLink) {
log.debug("Try to find rule chains by tenantId [{}], edgeId [{}] and pageLink [{}]", tenantId, edgeId, pageLink); log.debug("Try to find rule chains by tenantId [{}], edgeId [{}] and pageLink [{}]", tenantId, edgeId, pageLink);

3
dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleChainRepository.java

@ -50,5 +50,8 @@ public interface RuleChainRepository extends PagingAndSortingRepository<RuleChai
@Param("edgeId") UUID edgeId, @Param("edgeId") UUID edgeId,
@Param("searchText") String searchText, @Param("searchText") String searchText,
Pageable pageable); Pageable pageable);
RuleChainEntity findByTenantIdAndTypeAndRootIsTrue(UUID tenantId, RuleChainType ruleChainType);
Long countByTenantId(UUID tenantId); Long countByTenantId(UUID tenantId);
} }

2
dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java

@ -140,11 +140,11 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe
Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId); Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
customerService.deleteCustomersByTenantId(tenantId); customerService.deleteCustomersByTenantId(tenantId);
widgetsBundleService.deleteWidgetsBundlesByTenantId(tenantId); widgetsBundleService.deleteWidgetsBundlesByTenantId(tenantId);
dashboardService.deleteDashboardsByTenantId(tenantId);
entityViewService.deleteEntityViewsByTenantId(tenantId); entityViewService.deleteEntityViewsByTenantId(tenantId);
assetService.deleteAssetsByTenantId(tenantId); assetService.deleteAssetsByTenantId(tenantId);
deviceService.deleteDevicesByTenantId(tenantId); deviceService.deleteDevicesByTenantId(tenantId);
deviceProfileService.deleteDeviceProfilesByTenantId(tenantId); deviceProfileService.deleteDeviceProfilesByTenantId(tenantId);
dashboardService.deleteDashboardsByTenantId(tenantId);
edgeService.deleteEdgesByTenantId(tenantId); edgeService.deleteEdgesByTenantId(tenantId);
userService.deleteTenantAdmins(tenantId); userService.deleteTenantAdmins(tenantId);
ruleChainService.deleteRuleChainsByTenantId(tenantId); ruleChainService.deleteRuleChainsByTenantId(tenantId);

3
dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java

@ -90,6 +90,7 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A
apiUsageState.setDbStorageState(ApiUsageStateValue.ENABLED); apiUsageState.setDbStorageState(ApiUsageStateValue.ENABLED);
apiUsageState.setSmsExecState(ApiUsageStateValue.ENABLED); apiUsageState.setSmsExecState(ApiUsageStateValue.ENABLED);
apiUsageState.setEmailExecState(ApiUsageStateValue.ENABLED); apiUsageState.setEmailExecState(ApiUsageStateValue.ENABLED);
apiUsageState.setAlarmExecState(ApiUsageStateValue.ENABLED);
apiUsageStateValidator.validate(apiUsageState, ApiUsageState::getTenantId); apiUsageStateValidator.validate(apiUsageState, ApiUsageState::getTenantId);
ApiUsageState saved = apiUsageStateDao.save(apiUsageState.getTenantId(), apiUsageState); ApiUsageState saved = apiUsageStateDao.save(apiUsageState.getTenantId(), apiUsageState);
@ -107,6 +108,8 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A
new StringDataEntry(ApiFeature.EMAIL.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); new StringDataEntry(ApiFeature.EMAIL.getApiStateKey(), ApiUsageStateValue.ENABLED.name())));
apiUsageStates.add(new BasicTsKvEntry(saved.getCreatedTime(), apiUsageStates.add(new BasicTsKvEntry(saved.getCreatedTime(),
new StringDataEntry(ApiFeature.SMS.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); new StringDataEntry(ApiFeature.SMS.getApiStateKey(), ApiUsageStateValue.ENABLED.name())));
apiUsageStates.add(new BasicTsKvEntry(saved.getCreatedTime(),
new StringDataEntry(ApiFeature.ALARM.getApiStateKey(), ApiUsageStateValue.ENABLED.name())));
tsService.save(tenantId, saved.getId(), apiUsageStates, 0L); tsService.save(tenantId, saved.getId(), apiUsageStates, 0L);
if (entityId.getEntityType() == EntityType.TENANT && !entityId.equals(TenantId.SYS_TENANT_ID)) { if (entityId.getEntityType() == EntityType.TENANT && !entityId.equals(TenantId.SYS_TENANT_ID)) {

8
dao/src/main/resources/sql/schema-entities-hsql.sql

@ -35,6 +35,7 @@ CREATE TABLE IF NOT EXISTS alarm (
start_ts bigint, start_ts bigint,
status varchar(255), status varchar(255),
tenant_id uuid, tenant_id uuid,
customer_id uuid,
propagate_relation_types varchar, propagate_relation_types varchar,
type varchar(255) type varchar(255)
); );
@ -118,7 +119,8 @@ CREATE TABLE IF NOT EXISTS dashboard (
assigned_customers varchar(1000000), assigned_customers varchar(1000000),
search_text varchar(255), search_text varchar(255),
tenant_id uuid, tenant_id uuid,
title varchar(255) title varchar(255),
image varchar(1000000)
); );
CREATE TABLE IF NOT EXISTS rule_chain ( CREATE TABLE IF NOT EXISTS rule_chain (
@ -182,6 +184,7 @@ CREATE TABLE IF NOT EXISTS device_profile (
created_time bigint NOT NULL, created_time bigint NOT NULL,
name varchar(255), name varchar(255),
type varchar(255), type varchar(255),
image varchar(1000000),
transport_type varchar(255), transport_type varchar(255),
provision_type varchar(255), provision_type varchar(255),
profile_data jsonb, profile_data jsonb,
@ -192,11 +195,13 @@ CREATE TABLE IF NOT EXISTS device_profile (
firmware_id uuid, firmware_id uuid,
software_id uuid, software_id uuid,
default_rule_chain_id uuid, default_rule_chain_id uuid,
default_dashboard_id uuid,
default_queue_name varchar(255), default_queue_name varchar(255),
provision_device_key varchar, provision_device_key varchar,
CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name), CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name),
CONSTRAINT device_provision_key_unq_key UNIQUE (provision_device_key), CONSTRAINT device_provision_key_unq_key UNIQUE (provision_device_key),
CONSTRAINT fk_default_rule_chain_device_profile FOREIGN KEY (default_rule_chain_id) REFERENCES rule_chain(id), CONSTRAINT fk_default_rule_chain_device_profile FOREIGN KEY (default_rule_chain_id) REFERENCES rule_chain(id),
CONSTRAINT fk_default_dashboard_device_profile FOREIGN KEY (default_dashboard_id) REFERENCES dashboard(id),
CONSTRAINT fk_firmware_device_profile FOREIGN KEY (firmware_id) REFERENCES firmware(id), CONSTRAINT fk_firmware_device_profile FOREIGN KEY (firmware_id) REFERENCES firmware(id),
CONSTRAINT fk_software_device_profile FOREIGN KEY (software_id) REFERENCES firmware(id) CONSTRAINT fk_software_device_profile FOREIGN KEY (software_id) REFERENCES firmware(id)
); );
@ -449,6 +454,7 @@ CREATE TABLE IF NOT EXISTS api_usage_state (
js_exec varchar(32), js_exec varchar(32),
email_exec varchar(32), email_exec varchar(32),
sms_exec varchar(32), sms_exec varchar(32),
alarm_exec varchar(32),
CONSTRAINT api_usage_state_unq_key UNIQUE (tenant_id, entity_id) CONSTRAINT api_usage_state_unq_key UNIQUE (tenant_id, entity_id)
); );

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

@ -53,6 +53,7 @@ CREATE TABLE IF NOT EXISTS alarm (
start_ts bigint, start_ts bigint,
status varchar(255), status varchar(255),
tenant_id uuid, tenant_id uuid,
customer_id uuid,
propagate_relation_types varchar, propagate_relation_types varchar,
type varchar(255) type varchar(255)
); );
@ -136,7 +137,8 @@ CREATE TABLE IF NOT EXISTS dashboard (
assigned_customers varchar(1000000), assigned_customers varchar(1000000),
search_text varchar(255), search_text varchar(255),
tenant_id uuid, tenant_id uuid,
title varchar(255) title varchar(255),
image varchar(1000000)
); );
CREATE TABLE IF NOT EXISTS rule_chain ( CREATE TABLE IF NOT EXISTS rule_chain (
@ -201,6 +203,7 @@ CREATE TABLE IF NOT EXISTS device_profile (
created_time bigint NOT NULL, created_time bigint NOT NULL,
name varchar(255), name varchar(255),
type varchar(255), type varchar(255),
image varchar(1000000),
transport_type varchar(255), transport_type varchar(255),
provision_type varchar(255), provision_type varchar(255),
profile_data jsonb, profile_data jsonb,
@ -211,11 +214,13 @@ CREATE TABLE IF NOT EXISTS device_profile (
firmware_id uuid, firmware_id uuid,
software_id uuid, software_id uuid,
default_rule_chain_id uuid, default_rule_chain_id uuid,
default_dashboard_id uuid,
default_queue_name varchar(255), default_queue_name varchar(255),
provision_device_key varchar, provision_device_key varchar,
CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name), CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name),
CONSTRAINT device_provision_key_unq_key UNIQUE (provision_device_key), CONSTRAINT device_provision_key_unq_key UNIQUE (provision_device_key),
CONSTRAINT fk_default_rule_chain_device_profile FOREIGN KEY (default_rule_chain_id) REFERENCES rule_chain(id), CONSTRAINT fk_default_rule_chain_device_profile FOREIGN KEY (default_rule_chain_id) REFERENCES rule_chain(id),
CONSTRAINT fk_default_dashboard_device_profile FOREIGN KEY (default_dashboard_id) REFERENCES dashboard(id),
CONSTRAINT fk_firmware_device_profile FOREIGN KEY (firmware_id) REFERENCES firmware(id), CONSTRAINT fk_firmware_device_profile FOREIGN KEY (firmware_id) REFERENCES firmware(id),
CONSTRAINT fk_software_device_profile FOREIGN KEY (software_id) REFERENCES firmware(id) CONSTRAINT fk_software_device_profile FOREIGN KEY (software_id) REFERENCES firmware(id)
); );
@ -486,6 +491,7 @@ CREATE TABLE IF NOT EXISTS api_usage_state (
js_exec varchar(32), js_exec varchar(32),
email_exec varchar(32), email_exec varchar(32),
sms_exec varchar(32), sms_exec varchar(32),
alarm_exec varchar(32),
CONSTRAINT api_usage_state_unq_key UNIQUE (tenant_id, entity_id) CONSTRAINT api_usage_state_unq_key UNIQUE (tenant_id, entity_id)
); );

21
dao/src/test/java/org/thingsboard/server/dao/service/BaseAssetServiceTest.java

@ -632,4 +632,25 @@ public abstract class BaseAssetServiceTest extends AbstractServiceTest {
customerService.deleteCustomer(tenantId, customerId); customerService.deleteCustomer(tenantId, customerId);
} }
@Test
public void testCleanCacheIfAssetRenamed() {
String assetNameBeforeRename = RandomStringUtils.randomAlphanumeric(15);
String assetNameAfterRename = RandomStringUtils.randomAlphanumeric(15);
Asset asset = new Asset();
asset.setTenantId(tenantId);
asset.setName(assetNameBeforeRename);
asset.setType("default");
assetService.saveAsset(asset);
Asset savedAsset = assetService.findAssetByTenantIdAndName(tenantId, assetNameBeforeRename);
savedAsset.setName(assetNameAfterRename);
assetService.saveAsset(savedAsset);
Asset renamedAsset = assetService.findAssetByTenantIdAndName(tenantId, assetNameBeforeRename);
Assert.assertNull("Can't find asset by name in cache if it was renamed", renamedAsset);
assetService.deleteAsset(tenantId, savedAsset.getId());
}
} }

3
dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceProfileServiceTest.java

@ -333,7 +333,8 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest {
List<DeviceProfileInfo> deviceProfileInfos = deviceProfiles.stream() List<DeviceProfileInfo> deviceProfileInfos = deviceProfiles.stream()
.map(deviceProfile -> new DeviceProfileInfo(deviceProfile.getId(), .map(deviceProfile -> new DeviceProfileInfo(deviceProfile.getId(),
deviceProfile.getName(), deviceProfile.getType(), deviceProfile.getTransportType())).collect(Collectors.toList()); deviceProfile.getName(), deviceProfile.getImage(), deviceProfile.getDefaultDashboardId(),
deviceProfile.getType(), deviceProfile.getTransportType())).collect(Collectors.toList());
Assert.assertEquals(deviceProfileInfos, loadedDeviceProfileInfos); Assert.assertEquals(deviceProfileInfos, loadedDeviceProfileInfos);

21
dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceServiceTest.java

@ -793,4 +793,25 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest {
customerService.deleteCustomer(tenantId, customerId); customerService.deleteCustomer(tenantId, customerId);
} }
@Test
public void testCleanCacheIfDeviceRenamed() {
String deviceNameBeforeRename = RandomStringUtils.randomAlphanumeric(15);
String deviceNameAfterRename = RandomStringUtils.randomAlphanumeric(15);
Device device = new Device();
device.setTenantId(tenantId);
device.setName(deviceNameBeforeRename);
device.setType("default");
deviceService.saveDevice(device);
Device savedDevice = deviceService.findDeviceByTenantIdAndName(tenantId, deviceNameBeforeRename);
savedDevice.setName(deviceNameAfterRename);
deviceService.saveDevice(savedDevice);
Device renamedDevice = deviceService.findDeviceByTenantIdAndName(tenantId, deviceNameBeforeRename);
Assert.assertNull("Can't find device by name in cache if it was renamed", renamedDevice);
deviceService.deleteDevice(tenantId, savedDevice.getId());
}
} }

19
dao/src/test/java/org/thingsboard/server/dao/service/BaseEdgeServiceTest.java

@ -595,4 +595,23 @@ public abstract class BaseEdgeServiceTest extends AbstractServiceTest {
return edge; return edge;
} }
@Test
public void testCleanCacheIfEdgeRenamed() {
String edgeNameBeforeRename = RandomStringUtils.randomAlphanumeric(15);
String edgeNameAfterRename = RandomStringUtils.randomAlphanumeric(15);
Edge edge = constructEdge(tenantId, edgeNameBeforeRename, "default");
edgeService.saveEdge(edge);
Edge savedEdge = edgeService.findEdgeByTenantIdAndName(tenantId, edgeNameBeforeRename);
savedEdge.setName(edgeNameAfterRename);
edgeService.saveEdge(savedEdge);
Edge renamedEdge = edgeService.findEdgeByTenantIdAndName(tenantId, edgeNameBeforeRename);
Assert.assertNull("Can't find edge by name in cache if it was renamed", renamedEdge);
edgeService.deleteEdge(tenantId, savedEdge.getId());
}
} }

2
dao/src/test/resources/sql/hsql/drop-all-tables.sql

@ -5,7 +5,6 @@ DROP TABLE IF EXISTS audit_log;
DROP TABLE IF EXISTS attribute_kv; DROP TABLE IF EXISTS attribute_kv;
DROP TABLE IF EXISTS component_descriptor; DROP TABLE IF EXISTS component_descriptor;
DROP TABLE IF EXISTS customer; DROP TABLE IF EXISTS customer;
DROP TABLE IF EXISTS dashboard;
DROP TABLE IF EXISTS device; DROP TABLE IF EXISTS device;
DROP TABLE IF EXISTS device_credentials; DROP TABLE IF EXISTS device_credentials;
DROP TABLE IF EXISTS event; DROP TABLE IF EXISTS event;
@ -21,6 +20,7 @@ DROP TABLE IF EXISTS widgets_bundle;
DROP TABLE IF EXISTS entity_view; DROP TABLE IF EXISTS entity_view;
DROP TABLE IF EXISTS device_profile; DROP TABLE IF EXISTS device_profile;
DROP TABLE IF EXISTS tenant_profile; DROP TABLE IF EXISTS tenant_profile;
DROP TABLE IF EXISTS dashboard;
DROP TABLE IF EXISTS rule_node_state; DROP TABLE IF EXISTS rule_node_state;
DROP TABLE IF EXISTS rule_node; DROP TABLE IF EXISTS rule_node;
DROP TABLE IF EXISTS rule_chain; DROP TABLE IF EXISTS rule_chain;

2
dao/src/test/resources/sql/psql/drop-all-tables.sql

@ -5,7 +5,6 @@ DROP TABLE IF EXISTS audit_log;
DROP TABLE IF EXISTS attribute_kv; DROP TABLE IF EXISTS attribute_kv;
DROP TABLE IF EXISTS component_descriptor; DROP TABLE IF EXISTS component_descriptor;
DROP TABLE IF EXISTS customer; DROP TABLE IF EXISTS customer;
DROP TABLE IF EXISTS dashboard;
DROP TABLE IF EXISTS device; DROP TABLE IF EXISTS device;
DROP TABLE IF EXISTS device_credentials; DROP TABLE IF EXISTS device_credentials;
DROP TABLE IF EXISTS event; DROP TABLE IF EXISTS event;
@ -21,6 +20,7 @@ DROP TABLE IF EXISTS widgets_bundle;
DROP TABLE IF EXISTS entity_view; DROP TABLE IF EXISTS entity_view;
DROP TABLE IF EXISTS device_profile; DROP TABLE IF EXISTS device_profile;
DROP TABLE IF EXISTS tenant_profile; DROP TABLE IF EXISTS tenant_profile;
DROP TABLE IF EXISTS dashboard;
DROP TABLE IF EXISTS rule_node_state; DROP TABLE IF EXISTS rule_node_state;
DROP TABLE IF EXISTS rule_node; DROP TABLE IF EXISTS rule_node;
DROP TABLE IF EXISTS rule_chain; DROP TABLE IF EXISTS rule_chain;

2
dao/src/test/resources/sql/timescale/drop-all-tables.sql

@ -5,7 +5,6 @@ DROP TABLE IF EXISTS audit_log;
DROP TABLE IF EXISTS attribute_kv; DROP TABLE IF EXISTS attribute_kv;
DROP TABLE IF EXISTS component_descriptor; DROP TABLE IF EXISTS component_descriptor;
DROP TABLE IF EXISTS customer; DROP TABLE IF EXISTS customer;
DROP TABLE IF EXISTS dashboard;
DROP TABLE IF EXISTS device; DROP TABLE IF EXISTS device;
DROP TABLE IF EXISTS device_credentials; DROP TABLE IF EXISTS device_credentials;
DROP TABLE IF EXISTS event; DROP TABLE IF EXISTS event;
@ -24,6 +23,7 @@ DROP TABLE IF EXISTS rule_chain;
DROP TABLE IF EXISTS entity_view; DROP TABLE IF EXISTS entity_view;
DROP TABLE IF EXISTS device_profile; DROP TABLE IF EXISTS device_profile;
DROP TABLE IF EXISTS tenant_profile; DROP TABLE IF EXISTS tenant_profile;
DROP TABLE IF EXISTS dashboard;
DROP TABLE IF EXISTS edge; DROP TABLE IF EXISTS edge;
DROP TABLE IF EXISTS edge_event; DROP TABLE IF EXISTS edge_event;
DROP TABLE IF EXISTS tb_schema_settings; DROP TABLE IF EXISTS tb_schema_settings;

5
pom.xml

@ -1430,6 +1430,11 @@
<artifactId>spring-data-redis</artifactId> <artifactId>spring-data-redis</artifactId>
<version>${spring-data-redis.version}</version> <version>${spring-data-redis.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-redis</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency> <dependency>
<groupId>redis.clients</groupId> <groupId>redis.clients</groupId>
<artifactId>jedis</artifactId> <artifactId>jedis</artifactId>

2
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineAlarmService.java

@ -57,6 +57,8 @@ public interface RuleEngineAlarmService {
ListenableFuture<PageData<AlarmInfo>> findAlarms(TenantId tenantId, AlarmQuery query); ListenableFuture<PageData<AlarmInfo>> findAlarms(TenantId tenantId, AlarmQuery query);
ListenableFuture<PageData<AlarmInfo>> findCustomerAlarms(TenantId tenantId, CustomerId customerId, AlarmQuery query);
AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus, AlarmStatus alarmStatus); AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus, AlarmStatus alarmStatus);
PageData<AlarmData> findAlarmDataByQueryForEntities(TenantId tenantId, CustomerId customerId, AlarmDataQuery query, Collection<EntityId> orderedEntityIds); PageData<AlarmData> findAlarmDataByQueryForEntities(TenantId tenantId, CustomerId customerId, AlarmDataQuery query, Collection<EntityId> orderedEntityIds);

63
transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml

@ -101,38 +101,14 @@ transport:
lwm2m: lwm2m:
# Enable/disable lvm2m transport protocol. # Enable/disable lvm2m transport protocol.
enabled: "${LWM2M_ENABLED:true}" enabled: "${LWM2M_ENABLED:true}"
# We choose a default timeout a bit higher to the MAX_TRANSMIT_WAIT(62-93s) which is the time from starting to
# send a Confirmable message to the time when an acknowledgement is no longer expected.
# DEFAULT_TIMEOUT = 2 * 60 * 1000l; 2 min in ms
timeout: "${LWM2M_TIMEOUT:120000}"
recommended_ciphers: "${LWM2M_RECOMMENDED_CIPHERS:false}"
recommended_supported_groups: "${LWM2M_RECOMMENDED_SUPPORTED_GROUPS:true}"
response_pool_size: "${LWM2M_RESPONSE_POOL_SIZE:100}"
registered_pool_size: "${LWM2M_REGISTERED_POOL_SIZE:10}"
update_registered_pool_size: "${LWM2M_UPDATE_REGISTERED_POOL_SIZE:10}"
un_registered_pool_size: "${LWM2M_UN_REGISTERED_POOL_SIZE:10}"
secure:
# Certificate_x509:
# To get helps about files format and how to generate it, see: https://github.com/eclipse/leshan/wiki/Credential-files-format
# Create new X509 Certificates: common/transport/lwm2m/src/main/resources/credentials/shell/lwM2M_credentials.sh
key_store_type: "${LWM2M_KEYSTORE_TYPE:JKS}"
# key_store_path_file: "${KEY_STORE_PATH_FILE:/transport/lwm2m/src/main/data/credentials/serverKeyStore.jks}"
key_store_path_file: "${KEY_STORE_PATH_FILE:}"
key_store_password: "${LWM2M_KEYSTORE_PASSWORD_SERVER:server_ks_password}"
root_alias: "${LWM2M_SERVER_ROOT_CA:rootca}"
enable_gen_new_key_psk_rpk: "${ENABLE_GEN_NEW_KEY_PSK_RPK:false}"
server: server:
id: "${LWM2M_SERVER_ID:123}" id: "${LWM2M_SERVER_ID:123}"
bind_address: "${LWM2M_BIND_ADDRESS:0.0.0.0}" bind_address: "${LWM2M_BIND_ADDRESS:0.0.0.0}"
bind_port_no_sec: "${LWM2M_BIND_PORT_NO_SEC:5685}" bind_port: "${LWM2M_BIND_PORT:5685}"
secure: security:
bind_address_security: "${LWM2M_BIND_ADDRESS_SECURITY:0.0.0.0}" bind_address: "${LWM2M_BIND_ADDRESS_SECURITY:0.0.0.0}"
bind_port_security: "${LWM2M_BIND_PORT_SECURITY:5686}" bind_port: "${LWM2M_BIND_PORT_SECURITY:5686}"
# create_rpk: "${CREATE_RPK:}"
# Only for RPK: Public & Private Key. If the keystore file is missing or not working # Only for RPK: Public & Private Key. If the keystore file is missing or not working
# - Public Key (Hex): [3059301306072a8648ce3d020106082a8648ce3d0301070342000405064b9e6762dd8d8b8a52355d7b4d8b9a3d64e6d2ee277d76c248861353f3585eeb1838e4f9e37b31fa347aef5ce3431eb54e0a2506910c5e0298817445721b]
# - Private Key (Hex): [308193020100301306072a8648ce3d020106082a8648ce3d030107047930770201010420dc774b309e547ceb48fee547e104ce201a9c48c449dc5414cd04e7f5cf05f67ba00a06082a8648ce3d030107a1440342000405064b9e6762dd8d8b8a52355d7b4d8b9a3d64e6d2ee277d76c248861353f3585eeb1838e4f9e37b31fa347aef5ce3431eb54e0a2506910c5e0298817445721b],
# - Elliptic Curve parameters : [secp256r1 [NIST P-256, X9.62 prime256v1] (1.2.840.10045.3.1.7)]
public_x: "${LWM2M_SERVER_PUBLIC_X:05064b9e6762dd8d8b8a52355d7b4d8b9a3d64e6d2ee277d76c248861353f358}" public_x: "${LWM2M_SERVER_PUBLIC_X:05064b9e6762dd8d8b8a52355d7b4d8b9a3d64e6d2ee277d76c248861353f358}"
public_y: "${LWM2M_SERVER_PUBLIC_Y:5eeb1838e4f9e37b31fa347aef5ce3431eb54e0a2506910c5e0298817445721b}" public_y: "${LWM2M_SERVER_PUBLIC_Y:5eeb1838e4f9e37b31fa347aef5ce3431eb54e0a2506910c5e0298817445721b}"
private_encoded: "${LWM2M_SERVER_PRIVATE_ENCODED:308193020100301306072a8648ce3d020106082a8648ce3d030107047930770201010420dc774b309e547ceb48fee547e104ce201a9c48c449dc5414cd04e7f5cf05f67ba00a06082a8648ce3d030107a1440342000405064b9e6762dd8d8b8a52355d7b4d8b9a3d64e6d2ee277d76c248861353f3585eeb1838e4f9e37b31fa347aef5ce3431eb54e0a2506910c5e0298817445721b}" private_encoded: "${LWM2M_SERVER_PRIVATE_ENCODED:308193020100301306072a8648ce3d020106082a8648ce3d030107047930770201010420dc774b309e547ceb48fee547e104ce201a9c48c449dc5414cd04e7f5cf05f67ba00a06082a8648ce3d030107a1440342000405064b9e6762dd8d8b8a52355d7b4d8b9a3d64e6d2ee277d76c248861353f3585eeb1838e4f9e37b31fa347aef5ce3431eb54e0a2506910c5e0298817445721b}"
@ -142,19 +118,36 @@ transport:
enable: "${LWM2M_ENABLED_BS:true}" enable: "${LWM2M_ENABLED_BS:true}"
id: "${LWM2M_SERVER_ID_BS:111}" id: "${LWM2M_SERVER_ID_BS:111}"
bind_address: "${LWM2M_BIND_ADDRESS_BS:0.0.0.0}" bind_address: "${LWM2M_BIND_ADDRESS_BS:0.0.0.0}"
bind_port_no_sec: "${LWM2M_BIND_PORT_NO_SEC_BS:5687}" bind_port: "${LWM2M_BIND_PORT_BS:5687}"
secure: security:
bind_address_security: "${LWM2M_BIND_ADDRESS_BS:0.0.0.0}" bind_address: "${LWM2M_BIND_ADDRESS_BS:0.0.0.0}"
bind_port_security: "${LWM2M_BIND_PORT_SECURITY_BS:5688}" bind_port: "${LWM2M_BIND_PORT_SECURITY_BS:5688}"
# Only for RPK: Public & Private Key. If the keystore file is missing or not working # Only for RPK: Public & Private Key. If the keystore file is missing or not working
# - Elliptic Curve parameters : [secp256r1 [NIST P-256, X9.62 prime256v1] (1.2.840.10045.3.1.7)]
# - Public Key (Hex): [3059301306072a8648ce3d020106082a8648ce3d030107034200045017c87a1c1768264656b3b355434b0def6edb8b9bf166a4762d9930cd730f913fc4e61bcd8901ec27c424114c3e887ed372497f0c2cf85839b8443e76988b34]
# - Private Key (Hex): [308193020100301306072a8648ce3d020106082a8648ce3d0301070479307702010104205ecafd90caa7be45c42e1f3f32571632b8409e6e6249d7124f4ba56fab3c8083a00a06082a8648ce3d030107a144034200045017c87a1c1768264656b3b355434b0def6edb8b9bf166a4762d9930cd730f913fc4e61bcd8901ec27c424114c3e887ed372497f0c2cf85839b8443e76988b34],
public_x: "${LWM2M_SERVER_PUBLIC_X_BS:5017c87a1c1768264656b3b355434b0def6edb8b9bf166a4762d9930cd730f91}" public_x: "${LWM2M_SERVER_PUBLIC_X_BS:5017c87a1c1768264656b3b355434b0def6edb8b9bf166a4762d9930cd730f91}"
public_y: "${LWM2M_SERVER_PUBLIC_Y_BS:3fc4e61bcd8901ec27c424114c3e887ed372497f0c2cf85839b8443e76988b34}" public_y: "${LWM2M_SERVER_PUBLIC_Y_BS:3fc4e61bcd8901ec27c424114c3e887ed372497f0c2cf85839b8443e76988b34}"
private_encoded: "${LWM2M_SERVER_PRIVATE_ENCODED_BS:308193020100301306072a8648ce3d020106082a8648ce3d0301070479307702010104205ecafd90caa7be45c42e1f3f32571632b8409e6e6249d7124f4ba56fab3c8083a00a06082a8648ce3d030107a144034200045017c87a1c1768264656b3b355434b0def6edb8b9bf166a4762d9930cd730f913fc4e61bcd8901ec27c424114c3e887ed372497f0c2cf85839b8443e76988b34}" private_encoded: "${LWM2M_SERVER_PRIVATE_ENCODED_BS:308193020100301306072a8648ce3d020106082a8648ce3d0301070479307702010104205ecafd90caa7be45c42e1f3f32571632b8409e6e6249d7124f4ba56fab3c8083a00a06082a8648ce3d030107a144034200045017c87a1c1768264656b3b355434b0def6edb8b9bf166a4762d9930cd730f913fc4e61bcd8901ec27c424114c3e887ed372497f0c2cf85839b8443e76988b34}"
# Only Certificate_x509: # Only Certificate_x509:
alias: "${LWM2M_KEYSTORE_ALIAS_BS:bootstrap}" alias: "${LWM2M_KEYSTORE_ALIAS_BS:bootstrap}"
security:
# Certificate_x509:
# To get helps about files format and how to generate it, see: https://github.com/eclipse/leshan/wiki/Credential-files-format
# Create new X509 Certificates: common/transport/lwm2m/src/main/resources/credentials/shell/lwM2M_credentials.sh
key_store_type: "${LWM2M_KEYSTORE_TYPE:JKS}"
# key_store_path_file: "${KEY_STORE_PATH_FILE:/common/transport/lwm2m/src/main/resources/credentials/serverKeyStore.jks"
key_store: "${LWM2M_KEY_STORE:lwm2mserver.jks}"
key_store_password: "${LWM2M_KEY_STORE_PASSWORD:server_ks_password}"
root_alias: "${LWM2M_SERVER_ROOT_CA:rootca}"
enable_gen_new_key_psk_rpk: "${ENABLE_GEN_NEW_KEY_PSK_RPK:false}"
timeout: "${LWM2M_TIMEOUT:120000}"
recommended_ciphers: "${LWM2M_RECOMMENDED_CIPHERS:false}"
recommended_supported_groups: "${LWM2M_RECOMMENDED_SUPPORTED_GROUPS:true}"
response_pool_size: "${LWM2M_RESPONSE_POOL_SIZE:100}"
registered_pool_size: "${LWM2M_REGISTERED_POOL_SIZE:10}"
registration_store_pool_size: "${LWM2M_REGISTRATION_STORE_POOL_SIZE:100}"
clean_period_in_sec: "${LWM2M_CLEAN_PERIOD_IN_SEC:2}"
update_registered_pool_size: "${LWM2M_UPDATE_REGISTERED_POOL_SIZE:10}"
un_registered_pool_size: "${LWM2M_UN_REGISTERED_POOL_SIZE:10}"
log_max_length: "${LWM2M_LOG_MAX_LENGTH:100}"
# Use redis for Security and Registration stores # Use redis for Security and Registration stores
redis.enabled: "${LWM2M_REDIS_ENABLED:false}" redis.enabled: "${LWM2M_REDIS_ENABLED:false}"

2
transport/snmp/src/main/java/org/thingsboard/server/snmp/ThingsboardSnmpTransportApplication.java

@ -26,7 +26,7 @@ import java.util.Arrays;
@SpringBootConfiguration @SpringBootConfiguration
@EnableAsync @EnableAsync
@EnableScheduling @EnableScheduling
@ComponentScan({"org.thingsboard.server.snmp", "org.thingsboard.server.common", "org.thingsboard.server.transport.snmp", "org.thingsboard.server.queue"}) @ComponentScan({"org.thingsboard.server.snmp", "org.thingsboard.server.common", "org.thingsboard.server.transport.snmp", "org.thingsboard.server.queue", "org.thingsboard.server.cache"})
public class ThingsboardSnmpTransportApplication { public class ThingsboardSnmpTransportApplication {
private static final String SPRING_CONFIG_NAME_KEY = "--spring.config.name"; private static final String SPRING_CONFIG_NAME_KEY = "--spring.config.name";

1
ui-ngx/package.json

@ -47,6 +47,7 @@
"flot": "git://github.com/thingsboard/flot.git#0.9-work", "flot": "git://github.com/thingsboard/flot.git#0.9-work",
"flot.curvedlines": "git://github.com/MichaelZinsmaier/CurvedLines.git#master", "flot.curvedlines": "git://github.com/MichaelZinsmaier/CurvedLines.git#master",
"font-awesome": "^4.7.0", "font-awesome": "^4.7.0",
"html2canvas": "^1.0.0-rc.7",
"jquery": "^3.5.1", "jquery": "^3.5.1",
"jquery.terminal": "^2.18.3", "jquery.terminal": "^2.18.3",
"js-beautify": "^1.13.0", "js-beautify": "^1.13.0",

5
ui-ngx/src/app/core/auth/auth.service.ts

@ -294,10 +294,11 @@ export class AuthService {
}) })
); );
} else if (accessToken) { } else if (accessToken) {
this.utils.updateQueryParam('accessToken', null); const queryParamsToRemove = ['accessToken'];
if (refreshToken) { if (refreshToken) {
this.utils.updateQueryParam('refreshToken', null); queryParamsToRemove.push('refreshToken');
} }
this.utils.removeQueryParams(queryParamsToRemove);
try { try {
this.updateAndValidateToken(accessToken, 'jwt_token', false); this.updateAndValidateToken(accessToken, 'jwt_token', false);
if (refreshToken) { if (refreshToken) {

2
ui-ngx/src/app/core/http/alarm.service.ts

@ -72,7 +72,7 @@ export class AlarmService {
public getHighestAlarmSeverity(entityId: EntityId, alarmSearchStatus: AlarmSearchStatus, alarmStatus: AlarmStatus, public getHighestAlarmSeverity(entityId: EntityId, alarmSearchStatus: AlarmSearchStatus, alarmStatus: AlarmStatus,
config?: RequestConfig): Observable<AlarmSeverity> { config?: RequestConfig): Observable<AlarmSeverity> {
let url = `/api/alarm/highestSeverity/${entityId.entityType}/${entityId.entityType}`; let url = `/api/alarm/highestSeverity/${entityId.entityType}/${entityId.id}`;
if (alarmSearchStatus) { if (alarmSearchStatus) {
url += `?searchStatus=${alarmSearchStatus}`; url += `?searchStatus=${alarmSearchStatus}`;
} else if (alarmStatus) { } else if (alarmStatus) {

19
ui-ngx/src/app/core/services/utils.service.ts

@ -410,9 +410,23 @@ export class UtilsService {
return decodeURIComponent(results[2].replace(/\+/g, ' ')); return decodeURIComponent(results[2].replace(/\+/g, ' '));
} }
public removeQueryParams(keys: Array<string>) {
let params = this.window.location.search;
for (const key of keys) {
params = this.updateUrlQueryString(params, key, null);
}
const baseUrlPart = [baseUrl(), this.window.location.pathname].join('');
this.window.history.replaceState({}, '', baseUrlPart + params);
}
public updateQueryParam(name: string, value: string | null) { public updateQueryParam(name: string, value: string | null) {
const baseUrlPart = [baseUrl(), this.window.location.pathname].join(''); const baseUrlPart = [baseUrl(), this.window.location.pathname].join('');
const urlQueryString = this.window.location.search; const urlQueryString = this.window.location.search;
const params = this.updateUrlQueryString(urlQueryString, name, value);
this.window.history.replaceState({}, '', baseUrlPart + params);
}
private updateUrlQueryString(urlQueryString: string, name: string, value: string | null): string {
let newParam = ''; let newParam = '';
let params = ''; let params = '';
if (value !== null) { if (value !== null) {
@ -425,13 +439,16 @@ export class UtilsService {
newParam = '$1' + newParam; newParam = '$1' + newParam;
} }
params = urlQueryString.replace(keyRegex, newParam); params = urlQueryString.replace(keyRegex, newParam);
if (params.startsWith('&')) {
params = '?' + params.substring(1);
}
} else if (newParam) { } else if (newParam) {
params = urlQueryString + '&' + newParam; params = urlQueryString + '&' + newParam;
} }
} else if (newParam) { } else if (newParam) {
params = '?' + newParam; params = '?' + newParam;
} }
this.window.history.replaceState({}, '', baseUrlPart + params); return params;
} }
public baseUrl(): string { public baseUrl(): string {

2
ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts

@ -108,7 +108,7 @@ export class AlarmTableConfig extends EntityTableConfig<AlarmInfo, TimePageLink>
} }
fetchAlarms(pageLink: TimePageLink): Observable<PageData<AlarmInfo>> { fetchAlarms(pageLink: TimePageLink): Observable<PageData<AlarmInfo>> {
const query = new AlarmQuery(this.entityId, pageLink, this.searchStatus, null, true, null); const query = new AlarmQuery(this.entityId, pageLink, this.searchStatus, null, true);
return this.alarmService.getAlarms(query); return this.alarmService.getAlarms(query);
} }

3
ui-ngx/src/app/modules/home/components/alias/aliases-entity-select.component.ts

@ -15,6 +15,7 @@
/// ///
import { import {
ChangeDetectorRef,
Component, Component,
Injector, Injector,
Input, Input,
@ -73,6 +74,7 @@ export class AliasesEntitySelectComponent implements OnInit, OnDestroy {
constructor(private translate: TranslateService, constructor(private translate: TranslateService,
private overlay: Overlay, private overlay: Overlay,
private cd: ChangeDetectorRef,
private breakpointObserver: BreakpointObserver, private breakpointObserver: BreakpointObserver,
private viewContainerRef: ViewContainerRef) { private viewContainerRef: ViewContainerRef) {
} }
@ -179,6 +181,7 @@ export class AliasesEntitySelectComponent implements OnInit, OnDestroy {
displayValue = this.translate.instant('entity.entities'); displayValue = this.translate.instant('entity.entities');
} }
this.displayValue = displayValue; this.displayValue = displayValue;
this.cd.detectChanges();
} }
private updateEntityAliasesInfo() { private updateEntityAliasesInfo() {

88
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-image-dialog.component.html

@ -0,0 +1,88 @@
<!--
Copyright © 2016-2021 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.
-->
<form (ngSubmit)="save()">
<mat-toolbar color="primary">
<h2 translate>dashboard.update-image</h2>
<span fxFlex></span>
<button mat-button mat-icon-button
(click)="cancel()"
type="button">
<mat-icon class="material-icons">close</mat-icon>
</button>
</mat-toolbar>
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
</mat-progress-bar>
<div mat-dialog-content style="position: relative;">
<fieldset [disabled]="(isLoading$ | async) || (takingScreenshot$ | async)" fxLayout="column" fxLayoutAlign="center center" fxLayoutGap="8px">
<div [formGroup]="dashboardRectFormGroup" fxLayout="column" fxLayoutGap="8px" fxLayoutAlign="center center">
<mat-form-field class="rect-field">
<mat-label>Top %</mat-label>
<input matInput formControlName="top" type="number" step="1" min="0" max="100">
</mat-form-field>
<div fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="center center">
<mat-form-field class="rect-field">
<mat-label>Left %</mat-label>
<input matInput formControlName="left" type="number" step="1" min="0" max="100">
</mat-form-field>
<div class="tb-image-preview-container">
<div *ngIf="!safeImageUrl; else elseBlock">{{ 'dashboard.no-image' | translate }}</div>
<ng-template #elseBlock><img class="tb-image-preview" [src]="safeImageUrl" /></ng-template>
</div>
<mat-form-field class="rect-field">
<mat-label>Right %</mat-label>
<input matInput formControlName="right" type="number" step="1" min="0" max="100">
</mat-form-field>
</div>
<mat-form-field class="rect-field">
<mat-label>Bottom %</mat-label>
<input matInput formControlName="bottom" type="number" step="1" min="0" max="100">
</mat-form-field>
</div>
<div [formGroup]="dashboardRectFormGroup" fxFlex fxLayout="row" fxLayoutGap="8px">
</div>
<button mat-raised-button color="accent"
type="button"
[disabled]="(isLoading$ | async) || (takingScreenshot$ | async)"
(click)="takeScreenShot()">
{{ 'dashboard.take-screenshot' | translate }}
</button>
<div [formGroup]="dashboardImageFormGroup">
<tb-image-input [showPreview]="false" label="{{'dashboard.image' | translate}}"
formControlName="dashboardImage">
</tb-image-input>
</div>
</fieldset>
<div *ngIf="takingScreenshot$ | async" class="taking-screenshot-progress tb-absolute-fill" fxLayout="column"
fxLayoutAlign="center center">
<mat-progress-spinner color="accent" mode="indeterminate"></mat-progress-spinner>
</div>
</div>
<div mat-dialog-actions fxLayoutAlign="end center">
<button mat-button color="primary"
type="button"
[disabled]="(isLoading$ | async) || (takingScreenshot$ | async)"
(click)="cancel()" cdkFocusInitial>
{{ 'action.cancel' | translate }}
</button>
<button mat-raised-button color="primary"
type="submit"
[disabled]="(isLoading$ | async) || (takingScreenshot$ | async) || !dashboardImageFormGroup.dirty">
{{ 'action.update' | translate }}
</button>
</div>
</form>

59
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-image-dialog.component.scss

@ -0,0 +1,59 @@
/**
* Copyright © 2016-2021 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.
*/
$previewSize: 300px !default;
:host {
mat-form-field.rect-field {
max-width: 60px;
}
.tb-image-preview {
width: auto;
max-width: $previewSize - 2;
height: auto;
max-height: $previewSize - 2;
}
.tb-image-preview-container {
position: relative;
float: left;
width: $previewSize;
height: $previewSize;
margin-right: 12px;
vertical-align: top;
border: solid 1px;
div {
width: 100%;
font-size: 18px;
text-align: center;
}
div,
.tb-image-preview {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
.taking-screenshot-progress {
background-color: rgba(255, 255, 255, 0.65);
}
}

174
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-image-dialog.component.ts

@ -0,0 +1,174 @@
///
/// Copyright © 2016-2021 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 { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { DialogComponent } from '@app/shared/components/dialog.component';
import { DashboardId } from '@shared/models/id/dashboard-id';
import { DashboardService } from '@core/http/dashboard.service';
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
import html2canvas from 'html2canvas';
import { map, share } from 'rxjs/operators';
import { BehaviorSubject, from } from 'rxjs';
import { isNumber } from '@core/utils';
export interface DashboardImageDialogData {
dashboardId: DashboardId;
currentImage?: string;
dashboardElement: HTMLElement;
}
export interface DashboardImageDialogResult {
image?: string;
}
@Component({
selector: 'tb-dashboard-image-dialog',
templateUrl: './dashboard-image-dialog.component.html',
styleUrls: ['./dashboard-image-dialog.component.scss']
})
export class DashboardImageDialogComponent extends DialogComponent<DashboardImageDialogComponent, DashboardImageDialogResult> {
takingScreenshotSubject = new BehaviorSubject(false);
takingScreenshot$ = this.takingScreenshotSubject.asObservable().pipe(
share()
);
dashboardId: DashboardId;
safeImageUrl?: SafeUrl;
dashboardElement: HTMLElement;
dashboardRectFormGroup: FormGroup;
dashboardImageFormGroup: FormGroup;
constructor(protected store: Store<AppState>,
protected router: Router,
@Inject(MAT_DIALOG_DATA) public data: DashboardImageDialogData,
public dialogRef: MatDialogRef<DashboardImageDialogComponent, DashboardImageDialogResult>,
private dashboardService: DashboardService,
private sanitizer: DomSanitizer,
private fb: FormBuilder) {
super(store, router, dialogRef);
this.dashboardId = this.data.dashboardId;
this.updateImage(this.data.currentImage);
this.dashboardElement = this.data.dashboardElement;
const clientRect = this.dashboardElement.getBoundingClientRect();
this.dashboardRectFormGroup = this.fb.group({
left: [0, [Validators.min(0), Validators.max(100)]],
top: [0, [Validators.min(0), Validators.max(100)]],
right: [100, [Validators.min(0), Validators.max(100)]],
bottom: [100, [Validators.min(0), Validators.max(100)]]
});
this.dashboardImageFormGroup = this.fb.group({
dashboardImage: [this.data.currentImage]
});
this.dashboardImageFormGroup.get('dashboardImage').valueChanges.subscribe(
(newImage) => {
this.updateImage(newImage);
}
);
}
private convertUserPercent(percent: any, defaultValue: number): number {
let result: number;
if (isNumber(percent)) {
result = Math.max(0, Math.min(100, percent)) / 100;
} else {
result = defaultValue;
}
return result;
}
takeScreenShot() {
this.takingScreenshotSubject.next(true);
const rect = this.dashboardElement.getBoundingClientRect();
const leftVal = this.convertUserPercent(this.dashboardRectFormGroup.get('left').value, 0);
const topVal = this.convertUserPercent(this.dashboardRectFormGroup.get('top').value, 0);
const rightVal = this.convertUserPercent(this.dashboardRectFormGroup.get('right').value, 100);
const bottomVal = this.convertUserPercent(this.dashboardRectFormGroup.get('bottom').value, 100);
const left = leftVal * rect.width;
const top = topVal * rect.height;
const right = rightVal * rect.width;
const bottom = bottomVal * rect.height;
const x = rect.left + left;
const y = rect.top + top;
let width = right - left;
let height = bottom - top;
width = Math.max(1, width);
height = Math.max(1, height);
from(html2canvas(this.dashboardElement, {
logging: false,
useCORS: true,
foreignObjectRendering: false,
scale: 512 / width,
x,
y,
width,
height
})).pipe(
map(canvas => canvas.toDataURL())).subscribe(
(image) => {
this.updateImage(image);
this.dashboardImageFormGroup.patchValue({dashboardImage: image}, {emitEvent: false});
this.dashboardImageFormGroup.markAsDirty();
this.takingScreenshotSubject.next(false);
},
(e) => {
this.takingScreenshotSubject.next(false);
}
);
}
cancel(): void {
this.dialogRef.close(null);
}
save(): void {
this.dashboardService.getDashboard(this.dashboardId.id).subscribe(
(dashboard) => {
const newImage: string = this.dashboardImageFormGroup.get('dashboardImage').value;
dashboard.image = newImage;
this.dashboardService.saveDashboard(dashboard).subscribe(
() => {
this.dialogRef.close({
image: newImage
});
}
);
}
);
}
private updateImage(imageUrl: string) {
if (imageUrl) {
this.safeImageUrl = this.sanitizer.bypassSecurityTrustUrl(imageUrl);
} else {
this.safeImageUrl = null;
}
}
}

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

Loading…
Cancel
Save