Browse Source

Merge remote-tracking branch 'upstream/develop/3.3' into develop/3.3-edge

pull/3811/head
Volodymyr Babak 5 years ago
parent
commit
0f64b3764f
  1. 4
      application/src/main/data/json/system/widget_bundles/cards.json
  2. 11
      application/src/main/data/upgrade/2.4.3/schema_update_psql_drop_partitions.sql
  3. 0
      application/src/main/data/upgrade/3.2.2/schema_update.sql
  4. 0
      application/src/main/data/upgrade/3.2.2/schema_update_ttl.sql
  5. 40
      application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
  6. 2
      application/src/main/java/org/thingsboard/server/config/JwtSettings.java
  7. 8
      application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java
  8. 55
      application/src/main/java/org/thingsboard/server/controller/AuthController.java
  9. 90
      application/src/main/java/org/thingsboard/server/controller/ResourceController.java
  10. 48
      application/src/main/java/org/thingsboard/server/controller/UserController.java
  11. 46
      application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java
  12. 14
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  13. 3
      application/src/main/java/org/thingsboard/server/service/install/CassandraTsDatabaseUpgradeService.java
  14. 32
      application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java
  15. 84
      application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java
  16. 10
      application/src/main/java/org/thingsboard/server/service/install/PsqlTsDatabaseUpgradeService.java
  17. 25
      application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
  18. 2
      application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java
  19. 3
      application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java
  20. 4
      application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MModelsRepository.java
  21. 31
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java
  22. 6
      application/src/main/java/org/thingsboard/server/service/queue/TbClusterService.java
  23. 43
      application/src/main/java/org/thingsboard/server/service/queue/TbCoreConsumerStats.java
  24. 85
      application/src/main/java/org/thingsboard/server/service/security/auth/TokenOutdatingService.java
  25. 17
      application/src/main/java/org/thingsboard/server/service/security/auth/jwt/JwtAuthenticationProvider.java
  26. 21
      application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenAuthenticationProvider.java
  27. 2
      application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenRepository.java
  28. 72
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CookieUtils.java
  29. 55
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/HttpCookieOAuth2AuthorizationRequestRepository.java
  30. 6
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java
  31. 16
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java
  32. 2
      application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAwareAuthenticationSuccessHandler.java
  33. 2
      application/src/main/java/org/thingsboard/server/service/security/exception/JwtExpiredTokenException.java
  34. 1
      application/src/main/java/org/thingsboard/server/service/security/model/token/AccessJwtToken.java
  35. 26
      application/src/main/java/org/thingsboard/server/service/security/model/token/JwtTokenFactory.java
  36. 33
      application/src/main/java/org/thingsboard/server/service/security/model/token/RawAccessJwtToken.java
  37. 2
      application/src/main/java/org/thingsboard/server/service/sms/AbstractSmsSender.java
  38. 15
      application/src/main/java/org/thingsboard/server/service/sms/twilio/TwilioSmsSender.java
  39. 54
      application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java
  40. 267
      application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractDataSubCtx.java
  41. 315
      application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractSubCtx.java
  42. 3
      application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java
  43. 55
      application/src/main/java/org/thingsboard/server/service/subscription/TbEntityCountSubCtx.java
  44. 3
      application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubscriptionService.java
  45. 65
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java
  46. 2
      application/src/main/java/org/thingsboard/server/service/telemetry/TelemetryWebSocketMsgEndpoint.java
  47. 3
      application/src/main/java/org/thingsboard/server/service/telemetry/TelemetryWebSocketService.java
  48. 6
      application/src/main/java/org/thingsboard/server/service/telemetry/cmd/TelemetryPluginCmdsWrapper.java
  49. 8
      application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/AlarmDataUpdate.java
  50. 33
      application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/CmdUpdate.java
  51. 5
      application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/CmdUpdateType.java
  52. 21
      application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/DataUpdate.java
  53. 35
      application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityCountCmd.java
  54. 25
      application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityCountUnsubscribeCmd.java
  55. 57
      application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityCountUpdate.java
  56. 8
      application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityDataUpdate.java
  57. 2
      application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/UnsubscribeCmd.java
  58. 37
      application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java
  59. 2
      application/src/main/resources/logback.xml
  60. 11
      application/src/main/resources/thingsboard.yml
  61. 44
      application/src/test/java/org/thingsboard/server/controller/BaseEntityQueryControllerTest.java
  62. 99
      application/src/test/java/org/thingsboard/server/controller/BaseWebsocketApiTest.java
  63. 2
      application/src/test/java/org/thingsboard/server/controller/ControllerSqlTestSuite.java
  64. 184
      application/src/test/java/org/thingsboard/server/service/security/auth/TokenOutdatingTest.java
  65. 39
      common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ResourceService.java
  66. 2
      common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java
  67. 2
      common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java
  68. 41
      common/data/src/main/java/org/thingsboard/server/common/data/Resource.java
  69. 20
      common/data/src/main/java/org/thingsboard/server/common/data/ResourceType.java
  70. 4
      common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetSearchQuery.java
  71. 4
      common/data/src/main/java/org/thingsboard/server/common/data/device/DeviceSearchQuery.java
  72. 2
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmCondition.java
  73. 30
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmConditionFilter.java
  74. 26
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmConditionFilterKey.java
  75. 23
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmConditionKeyType.java
  76. 4
      common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeSearchQuery.java
  77. 4
      common/data/src/main/java/org/thingsboard/server/common/data/entityview/EntityViewSearchQuery.java
  78. 9
      common/data/src/main/java/org/thingsboard/server/common/data/query/AbstractDataQuery.java
  79. 4
      common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmDataQuery.java
  80. 14
      common/data/src/main/java/org/thingsboard/server/common/data/query/EntityCountQuery.java
  81. 4
      common/data/src/main/java/org/thingsboard/server/common/data/query/EntityDataQuery.java
  82. 1
      common/data/src/main/java/org/thingsboard/server/common/data/query/EntityFilter.java
  83. 1
      common/data/src/main/java/org/thingsboard/server/common/data/query/EntityFilterType.java
  84. 30
      common/data/src/main/java/org/thingsboard/server/common/data/query/EntityTypeFilter.java
  85. 5
      common/data/src/main/java/org/thingsboard/server/common/data/query/RelationsQueryFilter.java
  86. 2
      common/data/src/main/java/org/thingsboard/server/common/data/relation/EntityRelationsQuery.java
  87. 2
      common/data/src/main/java/org/thingsboard/server/common/data/relation/RelationEntityTypeFilter.java
  88. 30
      common/data/src/main/java/org/thingsboard/server/common/data/security/event/UserAuthDataChangedEvent.java
  89. 2
      common/data/src/main/java/org/thingsboard/server/common/data/security/model/JwtToken.java
  90. 37
      common/data/src/main/java/org/thingsboard/server/common/data/transport/resource/Resource.java
  91. 20
      common/data/src/main/java/org/thingsboard/server/common/data/transport/resource/ResourceType.java
  92. 2
      common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java
  93. 2
      common/queue/src/main/java/org/thingsboard/server/queue/util/TbLwM2mTransportComponent.java
  94. 29
      common/queue/src/main/proto/queue.proto
  95. 18
      common/transport/http/src/main/java/org/thingsboard/server/transport/http/HttpTransportContext.java
  96. 18
      common/transport/lwm2m/pom.xml
  97. 3
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerConfiguration.java
  98. 11
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java
  99. 48
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportContextServer.java
  100. 13
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportHandler.java

4
application/src/main/data/json/system/widget_bundles/cards.json

@ -47,7 +47,7 @@
"resources": [],
"templateHtml": "<tb-timeseries-table-widget \n [ctx]=\"ctx\">\n</tb-timeseries-table-widget>",
"templateCss": "",
"controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.timeseriesTableWidget.onDataUpdated();\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}",
"controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.timeseriesTableWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n ignoreDataUpdateOnIntervalTick: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}",
"settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"TimeseriesTableSettings\",\n \"properties\": {\n \"showTimestamp\": {\n \"title\": \"Display timestamp column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"showMilliseconds\": {\n \"title\": \"Display timestamp milliseconds\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n }, \n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"hideEmptyLines\": {\n \"title\": \"Hide empty lines\",\n \"type\": \"boolean\",\n \"default\": false\n }\n },\n \"required\": []\n },\n \"form\": [\n \"showTimestamp\",\n \"showMilliseconds\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"hideEmptyLines\"\n ]\n}",
"dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, rowData, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\"\n }\n ]\n}",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature °C\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = (value + 60)/120 * 100;\\n var color = tinycolor.mix('blue', 'red', amount = percent);\\n color.setAlpha(.5);\\n return {\\n paddingLeft: '20px',\\n color: '#ffffff',\\n background: color.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\"},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Humidity, %\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = value;\\n var backgroundColor = tinycolor('blue');\\n backgroundColor.setAlpha(value/100);\\n var color = 'blue';\\n if (value > 50) {\\n color = 'white';\\n }\\n \\n return {\\n paddingLeft: '20px',\\n color: color,\\n background: backgroundColor.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\",\"useCellContentFunction\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 5) {\\n\\tvalue = 5;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":60000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"showTimestamp\":true,\"displayPagination\":true,\"defaultPageSize\":10},\"title\":\"Timeseries table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{},\"showTitleIcon\":false,\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\"}"
@ -134,4 +134,4 @@
}
}
]
}
}

11
application/src/main/data/upgrade/2.4.3/schema_update_psql_drop_partitions.sql

@ -84,11 +84,12 @@ BEGIN
END IF;
END IF;
END IF;
END IF;
IF partition_to_delete IS NOT NULL THEN
RAISE NOTICE 'Partition to delete by max ttl: %', partition_to_delete;
EXECUTE format('DROP TABLE %I', partition_to_delete);
deleted := deleted + 1;
IF partition_to_delete IS NOT NULL THEN
RAISE NOTICE 'Partition to delete by max ttl: %', partition_to_delete;
EXECUTE format('DROP TABLE IF EXISTS %I', partition_to_delete);
partition_to_delete := NULL;
deleted := deleted + 1;
END IF;
END IF;
END LOOP;
END IF;

0
application/src/main/data/upgrade/3.2.1/schema_update.sql → application/src/main/data/upgrade/3.2.2/schema_update.sql

0
application/src/main/data/upgrade/3.2.1/schema_update_ttl.sql → application/src/main/data/upgrade/3.2.2/schema_update_ttl.sql

40
application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java

@ -37,6 +37,7 @@ import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.asset.Asset;
@ -281,7 +282,21 @@ class DefaultTbContext implements TbContext {
}
public TbMsg deviceCreatedMsg(Device device, RuleNodeId ruleNodeId) {
return entityActionMsg(device, device.getId(), ruleNodeId, DataConstants.ENTITY_CREATED);
RuleChainId ruleChainId = null;
String queueName = ServiceQueue.MAIN;
if (device.getDeviceProfileId() != null) {
DeviceProfile deviceProfile = mainCtx.getDeviceProfileCache().find(device.getDeviceProfileId());
if (deviceProfile == null) {
log.warn("[{}] Device profile is null!", device.getDeviceProfileId());
ruleChainId = null;
queueName = ServiceQueue.MAIN;
} else {
ruleChainId = deviceProfile.getDefaultRuleChainId();
String defaultQueueName = deviceProfile.getDefaultQueueName();
queueName = defaultQueueName != null ? defaultQueueName : ServiceQueue.MAIN;
}
}
return entityActionMsg(device, device.getId(), ruleNodeId, DataConstants.ENTITY_CREATED, queueName, ruleChainId);
}
public TbMsg assetCreatedMsg(Asset asset, RuleNodeId ruleNodeId) {
@ -289,7 +304,22 @@ class DefaultTbContext implements TbContext {
}
public TbMsg alarmActionMsg(Alarm alarm, RuleNodeId ruleNodeId, String action) {
return entityActionMsg(alarm, alarm.getId(), ruleNodeId, action);
RuleChainId ruleChainId = null;
String queueName = ServiceQueue.MAIN;
if (EntityType.DEVICE.equals(alarm.getOriginator().getEntityType())) {
DeviceId deviceId = new DeviceId(alarm.getOriginator().getId());
DeviceProfile deviceProfile = mainCtx.getDeviceProfileCache().get(getTenantId(), deviceId);
if (deviceProfile == null) {
log.warn("[{}] Device profile is null!", deviceId);
ruleChainId = null;
queueName = ServiceQueue.MAIN;
} else {
ruleChainId = deviceProfile.getDefaultRuleChainId();
String defaultQueueName = deviceProfile.getDefaultQueueName();
queueName = defaultQueueName != null ? defaultQueueName : ServiceQueue.MAIN;
}
}
return entityActionMsg(alarm, alarm.getId(), ruleNodeId, action, queueName, ruleChainId);
}
@Override
@ -298,8 +328,12 @@ class DefaultTbContext implements TbContext {
}
public <E, I extends EntityId> TbMsg entityActionMsg(E entity, I id, RuleNodeId ruleNodeId, String action) {
return entityActionMsg(entity, id, ruleNodeId, action, ServiceQueue.MAIN, null);
}
public <E, I extends EntityId> TbMsg entityActionMsg(E entity, I id, RuleNodeId ruleNodeId, String action, String queueName, RuleChainId ruleChainId) {
try {
return TbMsg.newMsg(action, id, getActionMetaData(ruleNodeId), mapper.writeValueAsString(mapper.valueToTree(entity)));
return TbMsg.newMsg(queueName, action, id, getActionMetaData(ruleNodeId), mapper.writeValueAsString(mapper.valueToTree(entity)), ruleChainId, null);
} catch (JsonProcessingException | IllegalArgumentException e) {
throw new RuntimeException("Failed to process " + id.getEntityType().name().toLowerCase() + " " + action + " msg: " + e);
}

2
application/src/main/java/org/thingsboard/server/config/JwtSettings.java

@ -17,7 +17,7 @@ package org.thingsboard.server.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.thingsboard.server.service.security.model.token.JwtToken;
import org.thingsboard.server.common.data.security.model.JwtToken;
@Configuration
@ConfigurationProperties(prefix = "security.jwt")

8
application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java

@ -48,6 +48,7 @@ import org.thingsboard.server.service.security.auth.jwt.RefreshTokenAuthenticati
import org.thingsboard.server.service.security.auth.jwt.RefreshTokenProcessingFilter;
import org.thingsboard.server.service.security.auth.jwt.SkipPathRequestMatcher;
import org.thingsboard.server.service.security.auth.jwt.extractor.TokenExtractor;
import org.thingsboard.server.service.security.auth.oauth2.HttpCookieOAuth2AuthorizationRequestRepository;
import org.thingsboard.server.service.security.auth.rest.RestAuthenticationProvider;
import org.thingsboard.server.service.security.auth.rest.RestLoginProcessingFilter;
import org.thingsboard.server.service.security.auth.rest.RestPublicLoginProcessingFilter;
@ -84,6 +85,9 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt
@Qualifier("oauth2AuthenticationFailureHandler")
private AuthenticationFailureHandler oauth2AuthenticationFailureHandler;
@Autowired(required = false)
private HttpCookieOAuth2AuthorizationRequestRepository httpCookieOAuth2AuthorizationRequestRepository;
@Autowired
@Qualifier("defaultAuthenticationSuccessHandler")
private AuthenticationSuccessHandler successHandler;
@ -213,7 +217,9 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt
.addFilterAfter(rateLimitProcessingFilter, UsernamePasswordAuthenticationFilter.class);
if (oauth2Configuration != null) {
http.oauth2Login()
.authorizationEndpoint().authorizationRequestResolver(oAuth2AuthorizationRequestResolver)
.authorizationEndpoint()
.authorizationRequestRepository(httpCookieOAuth2AuthorizationRequestRepository)
.authorizationRequestResolver(oAuth2AuthorizationRequestResolver)
.and()
.loginPage("/oauth2Login")
.loginProcessingUrl(oauth2Configuration.getLoginProcessingUrl())

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

@ -18,8 +18,9 @@ package org.thingsboard.server.controller;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
@ -32,6 +33,7 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.audit.ActionType;
@ -40,6 +42,8 @@ import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.security.UserCredentials;
import org.thingsboard.server.common.data.security.event.UserAuthDataChangedEvent;
import org.thingsboard.server.common.data.security.model.JwtToken;
import org.thingsboard.server.common.data.security.model.SecuritySettings;
import org.thingsboard.server.common.data.security.model.UserPasswordPolicy;
import org.thingsboard.server.dao.audit.AuditLogService;
@ -48,7 +52,6 @@ import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository;
import org.thingsboard.server.service.security.auth.rest.RestAuthenticationDetails;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.UserPrincipal;
import org.thingsboard.server.service.security.model.token.JwtToken;
import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
import org.thingsboard.server.service.security.system.SystemSecurityService;
import ua_parser.Client;
@ -61,25 +64,15 @@ import java.net.URISyntaxException;
@TbCoreComponent
@RequestMapping("/api")
@Slf4j
@RequiredArgsConstructor
public class AuthController extends BaseController {
@Autowired
private BCryptPasswordEncoder passwordEncoder;
@Autowired
private JwtTokenFactory tokenFactory;
@Autowired
private RefreshTokenRepository refreshTokenRepository;
@Autowired
private MailService mailService;
@Autowired
private SystemSecurityService systemSecurityService;
@Autowired
private AuditLogService auditLogService;
private final BCryptPasswordEncoder passwordEncoder;
private final JwtTokenFactory tokenFactory;
private final RefreshTokenRepository refreshTokenRepository;
private final MailService mailService;
private final SystemSecurityService systemSecurityService;
private final AuditLogService auditLogService;
private final ApplicationEventPublisher eventPublisher;
@PreAuthorize("isAuthenticated()")
@RequestMapping(value = "/auth/user", method = RequestMethod.GET)
@ -102,8 +95,7 @@ public class AuthController extends BaseController {
@PreAuthorize("isAuthenticated()")
@RequestMapping(value = "/auth/changePassword", method = RequestMethod.POST)
@ResponseStatus(value = HttpStatus.OK)
public void changePassword (
@RequestBody JsonNode changePasswordRequest) throws ThingsboardException {
public ObjectNode changePassword(@RequestBody JsonNode changePasswordRequest) throws ThingsboardException {
try {
String currentPassword = changePasswordRequest.get("currentPassword").asText();
String newPassword = changePasswordRequest.get("newPassword").asText();
@ -121,6 +113,11 @@ public class AuthController extends BaseController {
sendEntityNotificationMsg(getTenantId(), userCredentials.getUserId(), EdgeEventActionType.CREDENTIALS_UPDATED);
eventPublisher.publishEvent(new UserAuthDataChangedEvent(securityUser.getId()));
ObjectNode response = JacksonUtil.newObjectNode();
response.put("token", tokenFactory.createAccessJwtToken(securityUser).getToken());
response.put("refreshToken", tokenFactory.createRefreshToken(securityUser).getToken());
return response;
} catch (Exception e) {
throw handleException(e);
}
@ -137,7 +134,7 @@ public class AuthController extends BaseController {
throw handleException(e);
}
}
@RequestMapping(value = "/noauth/activate", params = { "activateToken" }, method = RequestMethod.GET)
public ResponseEntity<String> checkActivateToken(
@RequestParam(value = "activateToken") String activateToken) {
@ -159,7 +156,7 @@ public class AuthController extends BaseController {
}
return new ResponseEntity<>(headers, responseStatus);
}
@RequestMapping(value = "/noauth/resetPasswordByEmail", method = RequestMethod.POST)
@ResponseStatus(value = HttpStatus.OK)
public void requestResetPasswordByEmail (
@ -172,13 +169,13 @@ public class AuthController extends BaseController {
String baseUrl = systemSecurityService.getBaseUrl(user.getTenantId(), user.getCustomerId(), request);
String resetUrl = String.format("%s/api/noauth/resetPassword?resetToken=%s", baseUrl,
userCredentials.getResetToken());
mailService.sendResetPasswordEmail(resetUrl, email);
} catch (Exception e) {
throw handleException(e);
}
}
@RequestMapping(value = "/noauth/resetPassword", params = { "resetToken" }, method = RequestMethod.GET)
public ResponseEntity<String> checkResetToken(
@RequestParam(value = "resetToken") String resetToken) {
@ -200,7 +197,7 @@ public class AuthController extends BaseController {
}
return new ResponseEntity<>(headers, responseStatus);
}
@RequestMapping(value = "/noauth/activate", method = RequestMethod.POST)
@ResponseStatus(value = HttpStatus.OK)
@ResponseBody
@ -217,6 +214,7 @@ public class AuthController extends BaseController {
User user = userService.findUserById(TenantId.SYS_TENANT_ID, credentials.getUserId());
UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.USER_NAME, user.getEmail());
SecurityUser securityUser = new SecurityUser(user, credentials.isEnabled(), principal);
userService.setUserCredentialsEnabled(user.getTenantId(), user.getId(), true);
String baseUrl = systemSecurityService.getBaseUrl(user.getTenantId(), user.getCustomerId(), request);
String loginUrl = String.format("%s/login", baseUrl);
String email = user.getEmail();
@ -243,7 +241,7 @@ public class AuthController extends BaseController {
throw handleException(e);
}
}
@RequestMapping(value = "/noauth/resetPassword", method = RequestMethod.POST)
@ResponseStatus(value = HttpStatus.OK)
@ResponseBody
@ -271,6 +269,7 @@ public class AuthController extends BaseController {
String email = user.getEmail();
mailService.sendPasswordWasResetEmail(loginUrl, email);
eventPublisher.publishEvent(new UserAuthDataChangedEvent(securityUser.getId()));
JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser);
JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser);

90
application/src/main/java/org/thingsboard/server/controller/ResourceController.java

@ -0,0 +1,90 @@
/**
* 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.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.Resource;
import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.queue.util.TbCoreComponent;
@Slf4j
@RestController
@TbCoreComponent
@RequestMapping("/api")
public class ResourceController extends BaseController {
private final ResourceService resourceService;
public ResourceController(ResourceService resourceService) {
this.resourceService = resourceService;
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/resource", method = RequestMethod.POST)
@ResponseBody
public Resource saveResource(Resource resource) throws ThingsboardException {
try {
resource.setTenantId(getTenantId());
Resource savedResource = checkNotNull(resourceService.saveResource(resource));
tbClusterService.onResourceChange(savedResource, null);
return savedResource;
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/resource", method = RequestMethod.GET)
@ResponseBody
public PageData<Resource> getResources(@RequestParam(required = false) boolean system,
@RequestParam int pageSize,
@RequestParam int page,
@RequestParam(required = false) String sortProperty,
@RequestParam(required = false) String sortOrder) throws ThingsboardException {
try {
PageLink pageLink = createPageLink(pageSize, page, null, sortProperty, sortOrder);
return checkNotNull(resourceService.findResourcesByTenantId(system ? TenantId.SYS_TENANT_ID : getTenantId(), pageLink));
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/resource/{resourceType}/{resourceId}", method = RequestMethod.DELETE)
@ResponseBody
public void deleteResource(@PathVariable("resourceType") ResourceType resourceType,
@PathVariable("resourceId") String resourceId) throws ThingsboardException {
try {
Resource resource = checkNotNull(resourceService.getResource(getTenantId(), resourceType, resourceId));
resourceService.deleteResource(getTenantId(), resourceType, resourceId);
tbClusterService.onResourceDeleted(resource, null);
} catch (Exception e) {
throw handleException(e);
}
}
}

48
application/src/main/java/org/thingsboard/server/controller/UserController.java

@ -19,8 +19,9 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.Getter;
import org.springframework.beans.factory.annotation.Autowired;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
@ -46,11 +47,12 @@ import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.UserCredentials;
import org.thingsboard.server.common.data.security.event.UserAuthDataChangedEvent;
import org.thingsboard.server.common.data.security.model.JwtToken;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.UserPrincipal;
import org.thingsboard.server.service.security.model.token.JwtToken;
import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
@ -59,6 +61,7 @@ import org.thingsboard.server.service.security.system.SystemSecurityService;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
@RequiredArgsConstructor
@RestController
@TbCoreComponent
@RequestMapping("/api")
@ -72,18 +75,11 @@ public class UserController extends BaseController {
@Getter
private boolean userTokenAccessEnabled;
@Autowired
private MailService mailService;
@Autowired
private JwtTokenFactory tokenFactory;
@Autowired
private RefreshTokenRepository refreshTokenRepository;
@Autowired
private SystemSecurityService systemSecurityService;
private final MailService mailService;
private final JwtTokenFactory tokenFactory;
private final RefreshTokenRepository refreshTokenRepository;
private final SystemSecurityService systemSecurityService;
private final ApplicationEventPublisher eventPublisher;
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/user/{userId}", method = RequestMethod.GET)
@ -97,12 +93,24 @@ public class UserController extends BaseController {
processDashboardIdFromAdditionalInfo((ObjectNode) user.getAdditionalInfo(), DEFAULT_DASHBOARD);
processDashboardIdFromAdditionalInfo((ObjectNode) user.getAdditionalInfo(), HOME_DASHBOARD);
}
UserCredentials userCredentials = userService.findUserCredentialsByUserId(user.getTenantId(), user.getId());
if(userCredentials.isEnabled()) {
addUserCredentialsEnabled((ObjectNode) user.getAdditionalInfo());
}
return user;
} catch (Exception e) {
throw handleException(e);
}
}
private void addUserCredentialsEnabled(ObjectNode additionalInfo) {
if(!additionalInfo.isNull()) {
if(!additionalInfo.has("userCredentialsEnabled")) {
additionalInfo.put("userCredentialsEnabled", true);
}
}
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/user/tokenAccessEnabled", method = RequestMethod.GET)
@ResponseBody
@ -199,13 +207,13 @@ public class UserController extends BaseController {
user.getId(), user);
UserCredentials userCredentials = userService.findUserCredentialsByUserId(getCurrentUser().getTenantId(), user.getId());
if (!userCredentials.isEnabled()) {
if (!userCredentials.isEnabled() && userCredentials.getActivateToken() != null) {
String baseUrl = systemSecurityService.getBaseUrl(getTenantId(), getCurrentUser().getCustomerId(), request);
String activateUrl = String.format(ACTIVATE_URL_PATTERN, baseUrl,
userCredentials.getActivateToken());
mailService.sendActivationEmail(activateUrl, email);
} else {
throw new ThingsboardException("User is already active!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
throw new ThingsboardException("User is already activated!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
}
} catch (Exception e) {
throw handleException(e);
@ -224,13 +232,13 @@ public class UserController extends BaseController {
User user = checkUserId(userId, Operation.READ);
SecurityUser authUser = getCurrentUser();
UserCredentials userCredentials = userService.findUserCredentialsByUserId(authUser.getTenantId(), user.getId());
if (!userCredentials.isEnabled()) {
if (!userCredentials.isEnabled() && userCredentials.getActivateToken() != null) {
String baseUrl = systemSecurityService.getBaseUrl(getTenantId(), getCurrentUser().getCustomerId(), request);
String activateUrl = String.format(ACTIVATE_URL_PATTERN, baseUrl,
userCredentials.getActivateToken());
return activateUrl;
} else {
throw new ThingsboardException("User is already active!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
throw new ThingsboardException("User is already activated!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
}
} catch (Exception e) {
throw handleException(e);
@ -340,6 +348,10 @@ public class UserController extends BaseController {
User user = checkUserId(userId, Operation.WRITE);
TenantId tenantId = getCurrentUser().getTenantId();
userService.setUserCredentialsEnabled(tenantId, userId, userCredentialsEnabled);
if (!userCredentialsEnabled) {
eventPublisher.publishEvent(new UserAuthDataChangedEvent(userId));
}
} catch (Exception e) {
throw handleException(e);
}

46
application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java

@ -41,9 +41,13 @@ import org.thingsboard.server.service.telemetry.TelemetryWebSocketMsgEndpoint;
import org.thingsboard.server.service.telemetry.TelemetryWebSocketService;
import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef;
import javax.websocket.*;
import javax.websocket.RemoteEndpoint;
import javax.websocket.SendHandler;
import javax.websocket.SendResult;
import javax.websocket.Session;
import java.io.IOException;
import java.net.URI;
import java.nio.ByteBuffer;
import java.security.InvalidParameterException;
import java.util.Queue;
import java.util.Set;
@ -79,6 +83,9 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
@Value("${server.ws.limits.max_updates_per_session:}")
private String perSessionUpdatesConfiguration;
@Value("${server.ws.ping_timeout:30000}")
private long pingTimeout;
private ConcurrentMap<String, TelemetryWebSocketSessionRef> blacklistedSessions = new ConcurrentHashMap<>();
private ConcurrentMap<String, TbRateLimits> perSessionUpdateLimits = new ConcurrentHashMap<>();
@ -120,6 +127,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
return;
}
internalSessionMap.put(internalSessionId, new SessionMetaData(session, sessionRef, maxMsgQueuePerSession));
externalSessionMap.put(externalSessionId, internalSessionId);
processInWebSocketService(sessionRef, SessionEvent.onEstablished());
log.info("[{}][{}][{}] Session is opened", sessionRef.getSecurityCtx().getTenantId(), externalSessionId, session.getId());
@ -189,6 +197,8 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
private volatile boolean isSending = false;
private final Queue<String> msgQueue;
private volatile long lastActivityTime;
SessionMetaData(WebSocketSession session, TelemetryWebSocketSessionRef sessionRef, int maxMsgQueuePerSession) {
super();
this.session = session;
@ -196,6 +206,23 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
this.asyncRemote = nativeSession.getAsyncRemote();
this.sessionRef = sessionRef;
this.msgQueue = new LinkedBlockingQueue<>(maxMsgQueuePerSession);
this.lastActivityTime = System.currentTimeMillis();
}
synchronized void sendPing(long currentTime) {
try {
if (currentTime - lastActivityTime >= pingTimeout) {
this.asyncRemote.sendPing(ByteBuffer.wrap(new byte[]{}));
lastActivityTime = currentTime;
}
} catch (Exception e) {
log.trace("[{}] Failed to send ping msg", session.getId(), e);
try {
close(this.sessionRef, CloseStatus.SESSION_NOT_RELIABLE);
} catch (IOException ioe) {
log.trace("[{}] Session transport error", session.getId(), ioe);
}
}
}
synchronized void sendMsg(String msg) {
@ -243,6 +270,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
log.trace("[{}] Session transport error", session.getId(), ioe);
}
} else {
lastActivityTime = System.currentTimeMillis();
String msg = msgQueue.poll();
if (msg != null) {
sendMsgInternal(msg);
@ -284,6 +312,22 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
}
}
@Override
public void sendPing(TelemetryWebSocketSessionRef sessionRef, long currentTime) throws IOException {
String externalId = sessionRef.getSessionId();
String internalId = externalSessionMap.get(externalId);
if (internalId != null) {
SessionMetaData sessionMd = internalSessionMap.get(internalId);
if (sessionMd != null) {
sessionMd.sendPing(currentTime);
} else {
log.warn("[{}][{}] Failed to find session by internal id", externalId, internalId);
}
} else {
log.warn("[{}] Failed to find session by external id", externalId);
}
}
@Override
public void close(TelemetryWebSocketSessionRef sessionRef, CloseStatus reason) throws IOException {
String externalId = sessionRef.getSessionId();

14
application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java

@ -186,15 +186,22 @@ public class ThingsboardInstallService {
log.info("Upgrading ThingsBoard from version 3.2.0 to 3.2.1 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.2.0");
case "3.2.1":
log.info("Upgrading ThingsBoard from version 3.2.1 to 3.3.0 ...");
log.info("Upgrading ThingsBoard from version 3.2.1 to 3.2.2 ...");
if (databaseTsUpgradeService != null) {
databaseTsUpgradeService.upgradeDatabase("3.2.1");
}
databaseEntitiesUpgradeService.upgradeDatabase("3.2.1");
dataUpdateService.updateData("3.2.1");
log.info("Updating system data...");
systemDataLoaderService.updateSystemWidgets();
break;
case "3.2.2":
log.info("Upgrading ThingsBoard from version 3.2.2 to 3.3.0 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.2.2");
dataUpdateService.updateData("3.2.2");
log.info("Updating system data...");
break;
default:
throw new RuntimeException("Unable to upgrade ThingsBoard, unsupported fromVersion: " + upgradeFromVersion);
@ -227,6 +234,7 @@ public class ThingsboardInstallService {
systemDataLoaderService.createAdminSettings();
systemDataLoaderService.loadSystemWidgets();
systemDataLoaderService.createOAuth2Templates();
systemDataLoaderService.loadSystemLwm2mResources();
// systemDataLoaderService.loadSystemPlugins();
// systemDataLoaderService.loadSystemRules();

3
application/src/main/java/org/thingsboard/server/service/install/CassandraTsDatabaseUpgradeService.java

@ -49,10 +49,7 @@ public class CassandraTsDatabaseUpgradeService extends AbstractCassandraDatabase
log.info("Schema updated.");
break;
case "2.5.0":
case "2.5.4":
case "2.5.5":
case "3.1.1":
case "3.2.0":
case "3.2.1":
break;
default:

32
application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java

@ -36,6 +36,9 @@ import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.device.profile.AlarmCondition;
import org.thingsboard.server.common.data.device.profile.AlarmConditionFilter;
import org.thingsboard.server.common.data.device.profile.AlarmConditionFilterKey;
import org.thingsboard.server.common.data.device.profile.AlarmConditionKeyType;
import org.thingsboard.server.common.data.device.profile.AlarmRule;
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration;
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration;
@ -291,16 +294,16 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
AlarmCondition temperatureCondition = new AlarmCondition();
temperatureCondition.setSpec(new SimpleAlarmConditionSpec());
KeyFilter temperatureAlarmFlagAttributeFilter = new KeyFilter();
temperatureAlarmFlagAttributeFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "temperatureAlarmFlag"));
AlarmConditionFilter temperatureAlarmFlagAttributeFilter = new AlarmConditionFilter();
temperatureAlarmFlagAttributeFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.ATTRIBUTE, "temperatureAlarmFlag"));
temperatureAlarmFlagAttributeFilter.setValueType(EntityKeyValueType.BOOLEAN);
BooleanFilterPredicate temperatureAlarmFlagAttributePredicate = new BooleanFilterPredicate();
temperatureAlarmFlagAttributePredicate.setOperation(BooleanFilterPredicate.BooleanOperation.EQUAL);
temperatureAlarmFlagAttributePredicate.setValue(new FilterPredicateValue<>(Boolean.TRUE));
temperatureAlarmFlagAttributeFilter.setPredicate(temperatureAlarmFlagAttributePredicate);
KeyFilter temperatureTimeseriesFilter = new KeyFilter();
temperatureTimeseriesFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "temperature"));
AlarmConditionFilter temperatureTimeseriesFilter = new AlarmConditionFilter();
temperatureTimeseriesFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, "temperature"));
temperatureTimeseriesFilter.setValueType(EntityKeyValueType.NUMERIC);
NumericFilterPredicate temperatureTimeseriesFilterPredicate = new NumericFilterPredicate();
temperatureTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
@ -318,8 +321,8 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
AlarmCondition clearTemperatureCondition = new AlarmCondition();
clearTemperatureCondition.setSpec(new SimpleAlarmConditionSpec());
KeyFilter clearTemperatureTimeseriesFilter = new KeyFilter();
clearTemperatureTimeseriesFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "temperature"));
AlarmConditionFilter clearTemperatureTimeseriesFilter = new AlarmConditionFilter();
clearTemperatureTimeseriesFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, "temperature"));
clearTemperatureTimeseriesFilter.setValueType(EntityKeyValueType.NUMERIC);
NumericFilterPredicate clearTemperatureTimeseriesFilterPredicate = new NumericFilterPredicate();
clearTemperatureTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.LESS_OR_EQUAL);
@ -341,16 +344,16 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
AlarmCondition humidityCondition = new AlarmCondition();
humidityCondition.setSpec(new SimpleAlarmConditionSpec());
KeyFilter humidityAlarmFlagAttributeFilter = new KeyFilter();
humidityAlarmFlagAttributeFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "humidityAlarmFlag"));
AlarmConditionFilter humidityAlarmFlagAttributeFilter = new AlarmConditionFilter();
humidityAlarmFlagAttributeFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.ATTRIBUTE, "humidityAlarmFlag"));
humidityAlarmFlagAttributeFilter.setValueType(EntityKeyValueType.BOOLEAN);
BooleanFilterPredicate humidityAlarmFlagAttributePredicate = new BooleanFilterPredicate();
humidityAlarmFlagAttributePredicate.setOperation(BooleanFilterPredicate.BooleanOperation.EQUAL);
humidityAlarmFlagAttributePredicate.setValue(new FilterPredicateValue<>(Boolean.TRUE));
humidityAlarmFlagAttributeFilter.setPredicate(humidityAlarmFlagAttributePredicate);
KeyFilter humidityTimeseriesFilter = new KeyFilter();
humidityTimeseriesFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "humidity"));
AlarmConditionFilter humidityTimeseriesFilter = new AlarmConditionFilter();
humidityTimeseriesFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, "humidity"));
humidityTimeseriesFilter.setValueType(EntityKeyValueType.NUMERIC);
NumericFilterPredicate humidityTimeseriesFilterPredicate = new NumericFilterPredicate();
humidityTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.LESS);
@ -369,8 +372,8 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
AlarmCondition clearHumidityCondition = new AlarmCondition();
clearHumidityCondition.setSpec(new SimpleAlarmConditionSpec());
KeyFilter clearHumidityTimeseriesFilter = new KeyFilter();
clearHumidityTimeseriesFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "humidity"));
AlarmConditionFilter clearHumidityTimeseriesFilter = new AlarmConditionFilter();
clearHumidityTimeseriesFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, "humidity"));
clearHumidityTimeseriesFilter.setValueType(EntityKeyValueType.NUMERIC);
NumericFilterPredicate clearHumidityTimeseriesFilterPredicate = new NumericFilterPredicate();
clearHumidityTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER_OR_EQUAL);
@ -444,6 +447,11 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
installScripts.loadSystemWidgets();
}
@Override
public void loadSystemLwm2mResources() throws Exception {
installScripts.loadSystemLwm2mResources();
}
private User createUser(Authority authority,
TenantId tenantId,
CustomerId customerId,

84
application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java

@ -17,14 +17,19 @@ package org.thingsboard.server.service.install;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.model.DDFFileParser;
import org.eclipse.leshan.core.model.DefaultDDFFileValidator;
import org.eclipse.leshan.core.model.InvalidDDFFileException;
import org.eclipse.leshan.core.model.ObjectModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.Resource;
import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate;
import org.thingsboard.server.common.data.rule.RuleChain;
@ -33,15 +38,19 @@ import org.thingsboard.server.common.data.widget.WidgetType;
import org.thingsboard.server.common.data.widget.WidgetsBundle;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService;
import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.widget.WidgetTypeService;
import org.thingsboard.server.dao.widget.WidgetsBundleService;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.Optional;
import static org.thingsboard.server.service.install.DatabaseHelper.objectMapper;
@ -66,10 +75,13 @@ public class InstallScripts {
public static final String WIDGET_BUNDLES_DIR = "widget_bundles";
public static final String OAUTH2_CONFIG_TEMPLATES_DIR = "oauth2_config_templates";
public static final String DASHBOARDS_DIR = "dashboards";
public static final String MODELS_DIR = "models";
public static final String CREDENTIALS_DIR = "credentials";
public static final String EDGE_MANAGEMENT = "edge_management";
public static final String JSON_EXT = ".json";
public static final String XML_EXT = ".xml";
@Value("${install.data_dir:}")
private String dataDir;
@ -89,6 +101,9 @@ public class InstallScripts {
@Autowired
private OAuth2ConfigTemplateService oAuth2TemplateService;
@Autowired
private ResourceService resourceService;
private Path getTenantRuleChainsDir() {
return Paths.get(getDataDir(), JSON_DIR, TENANT_DIR, RULE_CHAINS_DIR);
}
@ -200,6 +215,73 @@ public class InstallScripts {
}
}
public void loadSystemLwm2mResources() throws Exception {
// Path modelsDir = Paths.get("/home/nick/Igor_project/thingsboard_ce_3_2_docker/thingsboard/common/transport/lwm2m/src/main/resources/models/");
Path modelsDir = Paths.get(getDataDir(), MODELS_DIR);
if (Files.isDirectory(modelsDir)) {
try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(modelsDir, path -> path.toString().endsWith(XML_EXT))) {
dirStream.forEach(
path -> {
try {
byte[] fileBytes = Files.readAllBytes(path);
String key = getObjectModelLwm2mValid(fileBytes, path.getFileName().toString(), new DefaultDDFFileValidator());
if (key != null) {
Resource resource = new Resource();
resource.setTenantId(TenantId.SYS_TENANT_ID);
resource.setResourceType(ResourceType.LWM2M_MODEL);
resource.setResourceId(key);
resource.setValue(Base64.getEncoder().encodeToString(fileBytes));
resourceService.saveResource(resource);
}
} catch (Exception e) {
log.error("Unable to load lwm2m model [{}]", path.toString());
throw new RuntimeException("Unable to load lwm2m model", e);
}
}
);
}
}
Path jksPath = Paths.get(getDataDir(), CREDENTIALS_DIR, "serverKeyStore.jks");
try {
Resource resource = new Resource();
resource.setTenantId(TenantId.SYS_TENANT_ID);
resource.setResourceType(ResourceType.JKS);
resource.setResourceId(jksPath.getFileName().toString());
resource.setValue(Base64.getEncoder().encodeToString(Files.readAllBytes(jksPath)));
resourceService.saveResource(resource);
} catch (Exception e) {
log.error("Unable to load lwm2m serverKeyStore [{}]", jksPath.toString());
throw new RuntimeException("Unable to load l2m2m serverKeyStore", e);
}
}
private String getObjectModelLwm2mValid(byte[] xmlByte, String streamName, DefaultDDFFileValidator ddfValidator) {
try {
DDFFileParser ddfFileParser = new DDFFileParser(ddfValidator);
ObjectModel objectModel = ddfFileParser.parseEx(new ByteArrayInputStream(xmlByte), streamName).get(0);
return objectModel.id + "##" + objectModel.getVersion();
} catch (IOException | InvalidDDFFileException e) {
log.error("Could not parse the XML file [{}]", streamName, e);
return null;
}
}
private void removeFile(Path modelsDir, String nameFile, byte[] fileBytes) {
String path = "/home/nick/Igor_project/thingsboard_ce_3_2_docker/thingsboard/common/transport/lwm2m/src/main/resources/models/";
File file = new File(path + nameFile);
if (!file.isDirectory()) {
try {
Files.write(Paths.get(path + "server/" + nameFile), fileBytes);
file.delete();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void loadDashboards(TenantId tenantId, CustomerId customerId) throws Exception {
Path dashboardsDir = Paths.get(getDataDir(), JSON_DIR, DEMO_DIR, DASHBOARDS_DIR);
try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(dashboardsDir, path -> path.toString().endsWith(JSON_EXT))) {

10
application/src/main/java/org/thingsboard/server/service/install/PsqlTsDatabaseUpgradeService.java

@ -196,17 +196,23 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe
}
break;
case "3.1.1":
case "3.2.1":
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
log.info("Load TTL functions ...");
loadSql(conn, "2.4.3", LOAD_TTL_FUNCTIONS_SQL);
log.info("Load Drop Partitions functions ...");
loadSql(conn, "2.4.3", LOAD_DROP_PARTITIONS_FUNCTIONS_SQL);
executeQuery(conn, "DROP PROCEDURE IF EXISTS cleanup_timeseries_by_ttl(character varying, bigint, bigint);");
executeQuery(conn, "DROP FUNCTION IF EXISTS delete_asset_records_from_ts_kv(character varying, character varying, bigint);");
executeQuery(conn, "DROP FUNCTION IF EXISTS delete_device_records_from_ts_kv(character varying, character varying, bigint);");
executeQuery(conn, "DROP FUNCTION IF EXISTS delete_customer_records_from_ts_kv(character varying, character varying, bigint);");
}
break;
case "3.2.1":
case "3.2.2":
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
log.info("Load Edge TTL functions ...");
loadSql(conn, "3.2.1", LOAD_TTL_FUNCTIONS_SQL);
loadSql(conn, "3.2.2", LOAD_TTL_FUNCTIONS_SQL);
}
break;
default:

25
application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java

@ -434,14 +434,29 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
log.info("Schema updated.");
}
break;
case "3.2.1":
case "3.2.2":
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
log.info("Updating schema ...");
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.2.1", SCHEMA_UPDATE_SQL);
loadSql(schemaUpdateFile, conn);
try {
conn.createStatement().execute("ALTER TABLE rule_chain ADD type varchar(255) DEFAULT 'CORE'"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
} catch (Exception e) {}
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.2.2", SCHEMA_UPDATE_SQL);
loadSql(schemaUpdateFile, conn);
try {
conn.createStatement().execute("ALTER TABLE rule_chain ADD type varchar(255) DEFAULT 'CORE'"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
} catch (Exception e) {}
conn.createStatement().execute("CREATE TABLE IF NOT EXISTS resource (" +
" tenant_id uuid NOT NULL," +
" resource_type varchar(32) NOT NULL," +
" resource_id varchar(255) NOT NULL," +
" resource_value varchar," +
" CONSTRAINT resource_unq_key UNIQUE (tenant_id, resource_type, resource_id)" +
" );");
conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3003000;");
installScripts.loadSystemLwm2mResources();
} catch (Exception e) {
log.error("Failed updating schema!!!", e);
}
log.info("Schema updated.");
}
break;

2
application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java

@ -33,4 +33,6 @@ public interface SystemDataLoaderService {
void deleteSystemWidgetBundle(String bundleAlias) throws Exception;
void loadSystemLwm2mResources() throws Exception;
}

3
application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java

@ -178,9 +178,6 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr
}
break;
case "3.1.1":
break;
case "3.2.0":
break;
case "3.2.1":
break;
default:

4
application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MModelsRepository.java

@ -101,8 +101,8 @@ public class LwM2MModelsRepository {
*/
private List<LwM2mObject> getLwm2mObjects(Predicate<? super ObjectModel> predicate, String sortProperty, String sortOrder) {
List<LwM2mObject> lwM2mObjects = new ArrayList<>();
List<ObjectModel> listObjects = (predicate == null) ? this.contextServer.getModelsValue() :
contextServer.getModelsValue().stream()
List<ObjectModel> listObjects = (predicate == null) ? this.contextServer.getModelsValueCommon() :
contextServer.getModelsValueCommon().stream()
.filter(predicate)
.collect(Collectors.toList());

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

@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.Resource;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.id.DeviceId;
@ -147,7 +148,7 @@ public class DefaultTbClusterService implements TbClusterService {
tbMsg = transformMsg(tbMsg, deviceProfileCache.get(tenantId, new DeviceProfileId(entityId.getId())));
}
}
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, tenantId, entityId);
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, tbMsg.getQueueName(), tenantId, entityId);
log.trace("PUSHING msg: {} to:{}", tbMsg, tpi);
ToRuleEngineMsg msg = ToRuleEngineMsg.newBuilder()
.setTenantIdMSB(tenantId.getId().getMostSignificantBits())
@ -249,6 +250,34 @@ public class DefaultTbClusterService implements TbClusterService {
onEntityDelete(entity.getTenantId(), entity.getId(), entity.getName(), callback);
}
@Override
public void onResourceChange(Resource resource, TbQueueCallback callback) {
TenantId tenantId = resource.getTenantId();
log.trace("[{}][{}][{}] Processing change resource", tenantId, resource.getResourceType(), resource.getResourceId());
TransportProtos.ResourceUpdateMsg resourceUpdateMsg = TransportProtos.ResourceUpdateMsg.newBuilder()
.setTenantIdMSB(tenantId.getId().getMostSignificantBits())
.setTenantIdLSB(tenantId.getId().getLeastSignificantBits())
.setResourceType(resource.getResourceType().name())
.setResourceId(resource.getResourceId())
.build();
ToTransportMsg transportMsg = ToTransportMsg.newBuilder().setResourceUpdateMsg(resourceUpdateMsg).build();
broadcast(transportMsg, callback);
}
@Override
public void onResourceDeleted(Resource resource, TbQueueCallback callback) {
TenantId tenantId = resource.getTenantId();
log.trace("[{}][{}][{}] Processing delete resource", tenantId, resource.getResourceType(), resource.getResourceId());
TransportProtos.ResourceDeleteMsg resourceUpdateMsg = TransportProtos.ResourceDeleteMsg.newBuilder()
.setTenantIdMSB(tenantId.getId().getMostSignificantBits())
.setTenantIdLSB(tenantId.getId().getLeastSignificantBits())
.setResourceType(resource.getResourceType().name())
.setResourceId(resource.getResourceId())
.build();
ToTransportMsg transportMsg = ToTransportMsg.newBuilder().setResourceDeleteMsg(resourceUpdateMsg).build();
broadcast(transportMsg, callback);
}
public <T> void onEntityChange(TenantId tenantId, EntityId entityid, T entity, TbQueueCallback callback) {
String entityName = (entity instanceof HasName) ? ((HasName) entity).getName() : entity.getClass().getName();
log.trace("[{}][{}][{}] Processing [{}] change event", tenantId, entityid.getEntityType(), entityid.getId(), entityName);

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

@ -19,6 +19,7 @@ import org.thingsboard.rule.engine.api.msg.ToDeviceActorNotificationMsg;
import org.thingsboard.server.common.data.ApiUsageState;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.Resource;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.id.EdgeId;
@ -72,6 +73,11 @@ public interface TbClusterService {
void onDeviceChange(Device device, TbQueueCallback callback);
void onDeviceDeleted(Device device, TbQueueCallback callback);
void onResourceChange(Resource resource, TbQueueCallback callback);
void onResourceDeleted(Resource resource, TbQueueCallback callback);
void onEdgeEventUpdate(TenantId tenantId, EdgeId edgeId);
}

43
application/src/main/java/org/thingsboard/server/service/queue/TbCoreConsumerStats.java

@ -58,32 +58,23 @@ public class TbCoreConsumerStats {
public TbCoreConsumerStats(StatsFactory statsFactory) {
String statsKey = StatsType.CORE.getName();
this.totalCounter = statsFactory.createStatsCounter(statsKey, TOTAL_MSGS);
this.sessionEventCounter = statsFactory.createStatsCounter(statsKey, SESSION_EVENTS);
this.getAttributesCounter = statsFactory.createStatsCounter(statsKey, GET_ATTRIBUTE);
this.subscribeToAttributesCounter = statsFactory.createStatsCounter(statsKey, ATTRIBUTE_SUBSCRIBES);
this.subscribeToRPCCounter = statsFactory.createStatsCounter(statsKey, RPC_SUBSCRIBES);
this.toDeviceRPCCallResponseCounter = statsFactory.createStatsCounter(statsKey, TO_DEVICE_RPC_CALL_RESPONSES);
this.subscriptionInfoCounter = statsFactory.createStatsCounter(statsKey, SUBSCRIPTION_INFO);
this.claimDeviceCounter = statsFactory.createStatsCounter(statsKey, DEVICE_CLAIMS);
this.deviceStateCounter = statsFactory.createStatsCounter(statsKey, DEVICE_STATES);
this.subscriptionMsgCounter = statsFactory.createStatsCounter(statsKey, SUBSCRIPTION_MSGS);
this.toCoreNotificationsCounter = statsFactory.createStatsCounter(statsKey, TO_CORE_NOTIFICATIONS);
this.edgeNotificationsCounter = statsFactory.createStatsCounter(statsKey, EDGE_NOTIFICATIONS);
counters.add(totalCounter);
counters.add(sessionEventCounter);
counters.add(getAttributesCounter);
counters.add(subscribeToAttributesCounter);
counters.add(subscribeToRPCCounter);
counters.add(toDeviceRPCCallResponseCounter);
counters.add(subscriptionInfoCounter);
counters.add(claimDeviceCounter);
counters.add(deviceStateCounter);
counters.add(subscriptionMsgCounter);
counters.add(toCoreNotificationsCounter);
counters.add(edgeNotificationsCounter);
this.totalCounter = register(statsFactory.createStatsCounter(statsKey, TOTAL_MSGS));
this.sessionEventCounter = register(statsFactory.createStatsCounter(statsKey, SESSION_EVENTS));
this.getAttributesCounter = register(statsFactory.createStatsCounter(statsKey, GET_ATTRIBUTE));
this.subscribeToAttributesCounter = register(statsFactory.createStatsCounter(statsKey, ATTRIBUTE_SUBSCRIBES));
this.subscribeToRPCCounter = register(statsFactory.createStatsCounter(statsKey, RPC_SUBSCRIBES));
this.toDeviceRPCCallResponseCounter = register(statsFactory.createStatsCounter(statsKey, TO_DEVICE_RPC_CALL_RESPONSES));
this.subscriptionInfoCounter = register(statsFactory.createStatsCounter(statsKey, SUBSCRIPTION_INFO));
this.claimDeviceCounter = register(statsFactory.createStatsCounter(statsKey, DEVICE_CLAIMS));
this.deviceStateCounter = register(statsFactory.createStatsCounter(statsKey, DEVICE_STATES));
this.subscriptionMsgCounter = register(statsFactory.createStatsCounter(statsKey, SUBSCRIPTION_MSGS));
this.toCoreNotificationsCounter = register(statsFactory.createStatsCounter(statsKey, TO_CORE_NOTIFICATIONS));
this.edgeNotificationsCounter = register(statsFactory.createStatsCounter(statsKey, EDGE_NOTIFICATIONS));
}
private StatsCounter register(StatsCounter counter){
counters.add(counter);
return counter;
}
public void log(TransportProtos.TransportToDeviceActorMsg msg) {

85
application/src/main/java/org/thingsboard/server/service/security/auth/TokenOutdatingService.java

@ -0,0 +1,85 @@
/**
* 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.service.security.auth;
import io.jsonwebtoken.Claims;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.CacheConstants;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.security.event.UserAuthDataChangedEvent;
import org.thingsboard.server.common.data.security.model.JwtToken;
import org.thingsboard.server.config.JwtSettings;
import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
import javax.annotation.PostConstruct;
import java.util.Optional;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
@Service
@RequiredArgsConstructor
public class TokenOutdatingService {
private final CacheManager cacheManager;
private final JwtTokenFactory tokenFactory;
private final JwtSettings jwtSettings;
private Cache tokenOutdatageTimeCache;
@PostConstruct
protected void initCache() {
tokenOutdatageTimeCache = cacheManager.getCache(CacheConstants.TOKEN_OUTDATAGE_TIME_CACHE);
}
@EventListener(classes = UserAuthDataChangedEvent.class)
public void onUserAuthDataChanged(UserAuthDataChangedEvent userAuthDataChangedEvent) {
outdateOldUserTokens(userAuthDataChangedEvent.getUserId());
}
public boolean isOutdated(JwtToken token, UserId userId) {
Claims claims = tokenFactory.parseTokenClaims(token).getBody();
long issueTime = claims.getIssuedAt().getTime();
return Optional.ofNullable(tokenOutdatageTimeCache.get(toKey(userId), Long.class))
.map(outdatageTime -> {
if (System.currentTimeMillis() - outdatageTime <= SECONDS.toMillis(jwtSettings.getRefreshTokenExpTime())) {
return MILLISECONDS.toSeconds(issueTime) < MILLISECONDS.toSeconds(outdatageTime);
} else {
/*
* Means that since the outdating has passed more than
* the lifetime of refresh token (the longest lived)
* and there is no need to store outdatage time anymore
* as all the tokens issued before the outdatage time
* are now expired by themselves
* */
tokenOutdatageTimeCache.evict(toKey(userId));
return false;
}
})
.orElse(false);
}
public void outdateOldUserTokens(UserId userId) {
tokenOutdatageTimeCache.put(toKey(userId), System.currentTimeMillis());
}
private String toKey(UserId userId) {
return userId.getId().toString();
}
}

17
application/src/main/java/org/thingsboard/server/service/security/auth/jwt/JwtAuthenticationProvider.java

@ -15,31 +15,34 @@
*/
package org.thingsboard.server.service.security.auth.jwt;
import org.springframework.beans.factory.annotation.Autowired;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;
import org.thingsboard.server.service.security.auth.TokenOutdatingService;
import org.thingsboard.server.service.security.auth.JwtAuthenticationToken;
import org.thingsboard.server.service.security.exception.JwtExpiredTokenException;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
import org.thingsboard.server.service.security.model.token.RawAccessJwtToken;
@Component
@SuppressWarnings("unchecked")
@RequiredArgsConstructor
public class JwtAuthenticationProvider implements AuthenticationProvider {
private final JwtTokenFactory tokenFactory;
@Autowired
public JwtAuthenticationProvider(JwtTokenFactory tokenFactory) {
this.tokenFactory = tokenFactory;
}
private final TokenOutdatingService tokenOutdatingService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
RawAccessJwtToken rawAccessToken = (RawAccessJwtToken) authentication.getCredentials();
SecurityUser securityUser = tokenFactory.parseAccessJwtToken(rawAccessToken);
if (tokenOutdatingService.isOutdated(rawAccessToken, securityUser.getId())) {
throw new JwtExpiredTokenException("Token is outdated");
}
return new JwtAuthenticationToken(securityUser);
}

21
application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenAuthenticationProvider.java

@ -15,9 +15,10 @@
*/
package org.thingsboard.server.service.security.auth.jwt;
import org.springframework.beans.factory.annotation.Autowired;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
@ -32,6 +33,7 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.service.security.auth.TokenOutdatingService;
import org.thingsboard.server.common.data.security.UserCredentials;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.user.UserService;
@ -44,18 +46,12 @@ import org.thingsboard.server.service.security.model.token.RawAccessJwtToken;
import java.util.UUID;
@Component
@RequiredArgsConstructor
public class RefreshTokenAuthenticationProvider implements AuthenticationProvider {
private final JwtTokenFactory tokenFactory;
private final UserService userService;
private final CustomerService customerService;
@Autowired
public RefreshTokenAuthenticationProvider(final UserService userService, final CustomerService customerService, final JwtTokenFactory tokenFactory) {
this.userService = userService;
this.customerService = customerService;
this.tokenFactory = tokenFactory;
}
private final TokenOutdatingService tokenOutdatingService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
@ -63,12 +59,18 @@ public class RefreshTokenAuthenticationProvider implements AuthenticationProvide
RawAccessJwtToken rawAccessToken = (RawAccessJwtToken) authentication.getCredentials();
SecurityUser unsafeUser = tokenFactory.parseRefreshToken(rawAccessToken);
UserPrincipal principal = unsafeUser.getUserPrincipal();
SecurityUser securityUser;
if (principal.getType() == UserPrincipal.Type.USER_NAME) {
securityUser = authenticateByUserId(unsafeUser.getId());
} else {
securityUser = authenticateByPublicId(principal.getValue());
}
if (tokenOutdatingService.isOutdated(rawAccessToken, securityUser.getId())) {
throw new CredentialsExpiredException("Token is outdated");
}
return new RefreshAuthenticationToken(securityUser);
}
@ -91,7 +93,6 @@ public class RefreshTokenAuthenticationProvider implements AuthenticationProvide
if (user.getAuthority() == null) throw new InsufficientAuthenticationException("User has no authority assigned");
UserPrincipal userPrincipal = new UserPrincipal(UserPrincipal.Type.USER_NAME, user.getEmail());
SecurityUser securityUser = new SecurityUser(user, userCredentials.isEnabled(), userPrincipal);
return securityUser;

2
application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenRepository.java

@ -17,8 +17,8 @@ package org.thingsboard.server.service.security.auth.jwt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.security.model.JwtToken;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.token.JwtToken;
import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
@Component

72
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CookieUtils.java

@ -0,0 +1,72 @@
/**
* 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.service.security.auth.oauth2;
import org.springframework.util.SerializationUtils;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Base64;
import java.util.Optional;
public class CookieUtils {
public static Optional<Cookie> getCookie(HttpServletRequest request, String name) {
Cookie[] cookies = request.getCookies();
if (cookies != null && cookies.length > 0) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals(name)) {
return Optional.of(cookie);
}
}
}
return Optional.empty();
}
public static void addCookie(HttpServletResponse response, String name, String value, int maxAge) {
Cookie cookie = new Cookie(name, value);
cookie.setPath("/");
cookie.setHttpOnly(true);
cookie.setMaxAge(maxAge);
response.addCookie(cookie);
}
public static void deleteCookie(HttpServletRequest request, HttpServletResponse response, String name) {
Cookie[] cookies = request.getCookies();
if (cookies != null && cookies.length > 0) {
for (Cookie cookie: cookies) {
if (cookie.getName().equals(name)) {
cookie.setValue("");
cookie.setPath("/");
cookie.setMaxAge(0);
response.addCookie(cookie);
}
}
}
}
public static String serialize(Object object) {
return Base64.getUrlEncoder()
.encodeToString(SerializationUtils.serialize(object));
}
public static <T> T deserialize(Cookie cookie, Class<T> cls) {
return cls.cast(SerializationUtils.deserialize(
Base64.getUrlDecoder().decode(cookie.getValue())));
}
}

55
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/HttpCookieOAuth2AuthorizationRequestRepository.java

@ -0,0 +1,55 @@
/**
* 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.service.security.auth.oauth2;
import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class HttpCookieOAuth2AuthorizationRequestRepository implements AuthorizationRequestRepository<OAuth2AuthorizationRequest> {
public static final String OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME = "oauth2_auth_request";
private static final int cookieExpireSeconds = 180;
@Override
public OAuth2AuthorizationRequest loadAuthorizationRequest(HttpServletRequest request) {
return CookieUtils.getCookie(request, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME)
.map(cookie -> CookieUtils.deserialize(cookie, OAuth2AuthorizationRequest.class))
.orElse(null);
}
@Override
public void saveAuthorizationRequest(OAuth2AuthorizationRequest authorizationRequest, HttpServletRequest request, HttpServletResponse response) {
if (authorizationRequest == null) {
CookieUtils.deleteCookie(request, response, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME);
return;
}
CookieUtils.addCookie(response, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME, CookieUtils.serialize(authorizationRequest), cookieExpireSeconds);
}
@SuppressWarnings("deprecation")
@Override
public OAuth2AuthorizationRequest removeAuthorizationRequest(HttpServletRequest request) {
return this.loadAuthorizationRequest(request);
}
public void removeAuthorizationRequestCookies(HttpServletRequest request, HttpServletResponse response) {
CookieUtils.deleteCookie(request, response, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME);
}
}

6
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java

@ -37,10 +37,13 @@ import java.nio.charset.StandardCharsets;
@ConditionalOnProperty(prefix = "security.oauth2", value = "enabled", havingValue = "true")
public class Oauth2AuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
private final HttpCookieOAuth2AuthorizationRequestRepository httpCookieOAuth2AuthorizationRequestRepository;
private final SystemSecurityService systemSecurityService;
@Autowired
public Oauth2AuthenticationFailureHandler(final SystemSecurityService systemSecurityService) {
public Oauth2AuthenticationFailureHandler(final HttpCookieOAuth2AuthorizationRequestRepository httpCookieOAuth2AuthorizationRequestRepository,
final SystemSecurityService systemSecurityService) {
this.httpCookieOAuth2AuthorizationRequestRepository = httpCookieOAuth2AuthorizationRequestRepository;
this.systemSecurityService = systemSecurityService;
}
@ -49,6 +52,7 @@ public class Oauth2AuthenticationFailureHandler extends SimpleUrlAuthenticationF
HttpServletResponse response, AuthenticationException exception)
throws IOException, ServletException {
String baseUrl = this.systemSecurityService.getBaseUrl(TenantId.SYS_TENANT_ID, new CustomerId(EntityId.NULL_UUID), request);
httpCookieOAuth2AuthorizationRequestRepository.removeAuthorizationRequestCookies(request, response);
getRedirectStrategy().sendRedirect(request, response, baseUrl + "/login?loginError=" +
URLEncoder.encode(exception.getMessage(), StandardCharsets.UTF_8.toString()));
}

16
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java

@ -26,13 +26,12 @@ import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo;
import org.thingsboard.server.common.data.security.model.JwtToken;
import org.thingsboard.server.dao.oauth2.OAuth2Service;
import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.token.JwtToken;
import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
import org.thingsboard.server.service.security.system.SystemSecurityService;
import org.thingsboard.server.utils.MiscUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -49,6 +48,7 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS
private final OAuth2ClientMapperProvider oauth2ClientMapperProvider;
private final OAuth2Service oAuth2Service;
private final OAuth2AuthorizedClientService oAuth2AuthorizedClientService;
private final HttpCookieOAuth2AuthorizationRequestRepository httpCookieOAuth2AuthorizationRequestRepository;
private final SystemSecurityService systemSecurityService;
@Autowired
@ -56,12 +56,15 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS
final RefreshTokenRepository refreshTokenRepository,
final OAuth2ClientMapperProvider oauth2ClientMapperProvider,
final OAuth2Service oAuth2Service,
final OAuth2AuthorizedClientService oAuth2AuthorizedClientService, final SystemSecurityService systemSecurityService) {
final OAuth2AuthorizedClientService oAuth2AuthorizedClientService,
final HttpCookieOAuth2AuthorizationRequestRepository httpCookieOAuth2AuthorizationRequestRepository,
final SystemSecurityService systemSecurityService) {
this.tokenFactory = tokenFactory;
this.refreshTokenRepository = refreshTokenRepository;
this.oauth2ClientMapperProvider = oauth2ClientMapperProvider;
this.oAuth2Service = oAuth2Service;
this.oAuth2AuthorizedClientService = oAuth2AuthorizedClientService;
this.httpCookieOAuth2AuthorizationRequestRepository = httpCookieOAuth2AuthorizationRequestRepository;
this.systemSecurityService = systemSecurityService;
}
@ -84,10 +87,17 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS
JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser);
JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser);
clearAuthenticationAttributes(request, response);
getRedirectStrategy().sendRedirect(request, response, baseUrl + "/?accessToken=" + accessToken.getToken() + "&refreshToken=" + refreshToken.getToken());
} catch (Exception e) {
clearAuthenticationAttributes(request, response);
getRedirectStrategy().sendRedirect(request, response, baseUrl + "/login?loginError=" +
URLEncoder.encode(e.getMessage(), StandardCharsets.UTF_8.toString()));
}
}
protected void clearAuthenticationAttributes(HttpServletRequest request, HttpServletResponse response) {
super.clearAuthenticationAttributes(request);
httpCookieOAuth2AuthorizationRequestRepository.removeAuthorizationRequestCookies(request, response);
}
}

2
application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAwareAuthenticationSuccessHandler.java

@ -23,9 +23,9 @@ import org.springframework.security.core.Authentication;
import org.springframework.security.web.WebAttributes;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.security.model.JwtToken;
import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.token.JwtToken;
import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
import javax.servlet.ServletException;

2
application/src/main/java/org/thingsboard/server/service/security/exception/JwtExpiredTokenException.java

@ -16,7 +16,7 @@
package org.thingsboard.server.service.security.exception;
import org.springframework.security.core.AuthenticationException;
import org.thingsboard.server.service.security.model.token.JwtToken;
import org.thingsboard.server.common.data.security.model.JwtToken;
public class JwtExpiredTokenException extends AuthenticationException {
private static final long serialVersionUID = -5959543783324224864L;

1
application/src/main/java/org/thingsboard/server/service/security/model/token/AccessJwtToken.java

@ -17,6 +17,7 @@ package org.thingsboard.server.service.security.model.token;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.jsonwebtoken.Claims;
import org.thingsboard.server.common.data.security.model.JwtToken;
public final class AccessJwtToken implements JwtToken {
private final String rawToken;

26
application/src/main/java/org/thingsboard/server/service/security/model/token/JwtTokenFactory.java

@ -16,18 +16,26 @@
package org.thingsboard.server.service.security.model.token;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.SignatureException;
import io.jsonwebtoken.UnsupportedJwtException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.model.JwtToken;
import org.thingsboard.server.config.JwtSettings;
import org.thingsboard.server.service.security.exception.JwtExpiredTokenException;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.UserPrincipal;
@ -39,6 +47,7 @@ import java.util.UUID;
import java.util.stream.Collectors;
@Component
@Slf4j
public class JwtTokenFactory {
private static final String SCOPES = "scopes";
@ -97,7 +106,7 @@ public class JwtTokenFactory {
}
public SecurityUser parseAccessJwtToken(RawAccessJwtToken rawAccessToken) {
Jws<Claims> jwsClaims = rawAccessToken.parseClaims(settings.getTokenSigningKey());
Jws<Claims> jwsClaims = parseTokenClaims(rawAccessToken);
Claims claims = jwsClaims.getBody();
String subject = claims.getSubject();
@SuppressWarnings("unchecked")
@ -153,7 +162,7 @@ public class JwtTokenFactory {
}
public SecurityUser parseRefreshToken(RawAccessJwtToken rawAccessToken) {
Jws<Claims> jwsClaims = rawAccessToken.parseClaims(settings.getTokenSigningKey());
Jws<Claims> jwsClaims = parseTokenClaims(rawAccessToken);
Claims claims = jwsClaims.getBody();
String subject = claims.getSubject();
@SuppressWarnings("unchecked")
@ -171,4 +180,17 @@ public class JwtTokenFactory {
return securityUser;
}
public Jws<Claims> parseTokenClaims(JwtToken token) {
try {
return Jwts.parser()
.setSigningKey(settings.getTokenSigningKey())
.parseClaimsJws(token.getToken());
} catch (UnsupportedJwtException | MalformedJwtException | IllegalArgumentException | SignatureException ex) {
log.debug("Invalid JWT Token", ex);
throw new BadCredentialsException("Invalid JWT token: ", ex);
} catch (ExpiredJwtException expiredEx) {
log.debug("JWT Token is expired", expiredEx);
throw new JwtExpiredTokenException(token, "JWT Token expired", expiredEx);
}
}
}

33
application/src/main/java/org/thingsboard/server/service/security/model/token/RawAccessJwtToken.java

@ -15,22 +15,10 @@
*/
package org.thingsboard.server.service.security.model.token;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureException;
import io.jsonwebtoken.UnsupportedJwtException;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.BadCredentialsException;
import org.thingsboard.server.service.security.exception.JwtExpiredTokenException;
import org.thingsboard.server.common.data.security.model.JwtToken;
import java.io.Serializable;
@Slf4j
public class RawAccessJwtToken implements JwtToken, Serializable {
private static final long serialVersionUID = -797397445703066079L;
@ -41,25 +29,6 @@ public class RawAccessJwtToken implements JwtToken, Serializable {
this.token = token;
}
/**
* Parses and validates JWT Token signature.
*
* @throws BadCredentialsException
* @throws JwtExpiredTokenException
*
*/
public Jws<Claims> parseClaims(String signingKey) {
try {
return Jwts.parser().setSigningKey(signingKey).parseClaimsJws(this.token);
} catch (UnsupportedJwtException | MalformedJwtException | IllegalArgumentException | SignatureException ex) {
log.debug("Invalid JWT Token", ex);
throw new BadCredentialsException("Invalid JWT token: ", ex);
} catch (ExpiredJwtException expiredEx) {
log.debug("JWT Token is expired", expiredEx);
throw new JwtExpiredTokenException(this, "JWT Token expired", expiredEx);
}
}
@Override
public String getToken() {
return token;

2
application/src/main/java/org/thingsboard/server/service/sms/AbstractSmsSender.java

@ -24,7 +24,7 @@ import java.util.regex.Pattern;
@Slf4j
public abstract class AbstractSmsSender implements SmsSender {
private static final Pattern E_164_PHONE_NUMBER_PATTERN = Pattern.compile("^\\+[1-9]\\d{1,14}$");
protected static final Pattern E_164_PHONE_NUMBER_PATTERN = Pattern.compile("^\\+[1-9]\\d{1,14}$");
private static final int MAX_SMS_MESSAGE_LENGTH = 1600;
private static final int MAX_SMS_SEGMENT_LENGTH = 70;

15
application/src/main/java/org/thingsboard/server/service/sms/twilio/TwilioSmsSender.java

@ -19,21 +19,34 @@ import com.twilio.http.TwilioRestClient;
import com.twilio.rest.api.v2010.account.Message;
import com.twilio.type.PhoneNumber;
import org.apache.commons.lang3.StringUtils;
import org.thingsboard.rule.engine.api.sms.exception.SmsParseException;
import org.thingsboard.server.common.data.sms.config.TwilioSmsProviderConfiguration;
import org.thingsboard.rule.engine.api.sms.exception.SmsException;
import org.thingsboard.rule.engine.api.sms.exception.SmsSendException;
import org.thingsboard.server.service.sms.AbstractSmsSender;
import java.util.regex.Pattern;
public class TwilioSmsSender extends AbstractSmsSender {
private static final Pattern PHONE_NUMBERS_SID_MESSAGE_SERVICE_SID = Pattern.compile("^(PN|MG).*$");
private TwilioRestClient twilioRestClient;
private String numberFrom;
private String validatePhoneTwilioNumber(String phoneNumber) throws SmsParseException {
phoneNumber = phoneNumber.trim();
if (!E_164_PHONE_NUMBER_PATTERN.matcher(phoneNumber).matches() && !PHONE_NUMBERS_SID_MESSAGE_SERVICE_SID.matcher(phoneNumber).matches()) {
throw new SmsParseException("Invalid phone number format. Phone number must be in E.164 format/Phone Number's SID/Messaging Service SID.");
}
return phoneNumber;
}
public TwilioSmsSender(TwilioSmsProviderConfiguration config) {
if (StringUtils.isEmpty(config.getAccountSid()) || StringUtils.isEmpty(config.getAccountToken()) || StringUtils.isEmpty(config.getNumberFrom())) {
throw new IllegalArgumentException("Invalid twilio sms provider configuration: accountSid, accountToken and numberFrom should be specified!");
}
this.numberFrom = this.validatePhoneNumber(config.getNumberFrom());
this.numberFrom = this.validatePhoneTwilioNumber(config.getNumberFrom());
this.twilioRestClient = new TwilioRestClient.Builder(config.getAccountSid(), config.getAccountToken()).build();
}

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

@ -54,6 +54,7 @@ import org.thingsboard.server.service.telemetry.TelemetryWebSocketService;
import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef;
import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataUpdate;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityHistoryCmd;
@ -92,7 +93,7 @@ import java.util.stream.Collectors;
public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubscriptionService {
private static final int DEFAULT_LIMIT = 100;
private final Map<String, Map<Integer, TbAbstractDataSubCtx>> subscriptionsBySessionId = new ConcurrentHashMap<>();
private final Map<String, Map<Integer, TbAbstractSubCtx>> subscriptionsBySessionId = new ConcurrentHashMap<>();
@Autowired
private TelemetryWebSocketService wsService;
@ -202,7 +203,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
//TODO: validate number of dynamic page links against rate limits. Ignore dynamic flag if limit is reached.
TbEntityDataSubCtx finalCtx = ctx;
ScheduledFuture<?> task = scheduler.scheduleWithFixedDelay(
() -> refreshDynamicQuery(tenantId, customerId, finalCtx),
() -> refreshDynamicQuery(finalCtx),
dynamicPageLinkRefreshInterval, dynamicPageLinkRefreshInterval, TimeUnit.SECONDS);
finalCtx.setRefreshTask(task);
}
@ -235,6 +236,26 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
}, wsCallBackExecutor);
}
@Override
public void handleCmd(TelemetryWebSocketSessionRef session, EntityCountCmd cmd) {
TbEntityCountSubCtx ctx = getSubCtx(session.getSessionId(), cmd.getCmdId());
if (ctx == null) {
ctx = createSubCtx(session, cmd);
long start = System.currentTimeMillis();
ctx.fetchData();
long end = System.currentTimeMillis();
stats.getRegularQueryInvocationCnt().incrementAndGet();
stats.getRegularQueryTimeSpent().addAndGet(end - start);
TbEntityCountSubCtx finalCtx = ctx;
ScheduledFuture<?> task = scheduler.scheduleWithFixedDelay(
() -> refreshDynamicQuery(finalCtx),
dynamicPageLinkRefreshInterval, dynamicPageLinkRefreshInterval, TimeUnit.SECONDS);
finalCtx.setRefreshTask(task);
} else {
log.debug("[{}][{}] Received duplicate command: {}", session.getSessionId(), cmd.getCmdId(), cmd);
}
}
@Override
public void handleCmd(TelemetryWebSocketSessionRef session, AlarmDataCmd cmd) {
TbAlarmDataSubCtx ctx = getSubCtx(session.getSessionId(), cmd.getCmdId());
@ -267,7 +288,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
}
}
private void refreshDynamicQuery(TenantId tenantId, CustomerId customerId, TbEntityDataSubCtx finalCtx) {
private void refreshDynamicQuery(TbAbstractSubCtx finalCtx) {
try {
long start = System.currentTimeMillis();
finalCtx.update();
@ -299,7 +320,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
}
private TbEntityDataSubCtx createSubCtx(TelemetryWebSocketSessionRef sessionRef, EntityDataCmd cmd) {
Map<Integer, TbAbstractDataSubCtx> sessionSubs = subscriptionsBySessionId.computeIfAbsent(sessionRef.getSessionId(), k -> new HashMap<>());
Map<Integer, TbAbstractSubCtx> sessionSubs = subscriptionsBySessionId.computeIfAbsent(sessionRef.getSessionId(), k -> new HashMap<>());
TbEntityDataSubCtx ctx = new TbEntityDataSubCtx(serviceId, wsService, entityService, localSubscriptionService,
attributesService, stats, sessionRef, cmd.getCmdId(), maxEntitiesPerDataSubscription);
if (cmd.getQuery() != null) {
@ -309,8 +330,20 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
return ctx;
}
private TbEntityCountSubCtx createSubCtx(TelemetryWebSocketSessionRef sessionRef, EntityCountCmd cmd) {
Map<Integer, TbAbstractSubCtx> sessionSubs = subscriptionsBySessionId.computeIfAbsent(sessionRef.getSessionId(), k -> new HashMap<>());
TbEntityCountSubCtx ctx = new TbEntityCountSubCtx(serviceId, wsService, entityService, localSubscriptionService,
attributesService, stats, sessionRef, cmd.getCmdId());
if (cmd.getQuery() != null) {
ctx.setAndResolveQuery(cmd.getQuery());
}
sessionSubs.put(cmd.getCmdId(), ctx);
return ctx;
}
private TbAlarmDataSubCtx createSubCtx(TelemetryWebSocketSessionRef sessionRef, AlarmDataCmd cmd) {
Map<Integer, TbAbstractDataSubCtx> sessionSubs = subscriptionsBySessionId.computeIfAbsent(sessionRef.getSessionId(), k -> new HashMap<>());
Map<Integer, TbAbstractSubCtx> sessionSubs = subscriptionsBySessionId.computeIfAbsent(sessionRef.getSessionId(), k -> new HashMap<>());
TbAlarmDataSubCtx ctx = new TbAlarmDataSubCtx(serviceId, wsService, entityService, localSubscriptionService,
attributesService, stats, alarmService, sessionRef, cmd.getCmdId(), maxEntitiesPerAlarmSubscription);
ctx.setAndResolveQuery(cmd.getQuery());
@ -319,8 +352,8 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
}
@SuppressWarnings("unchecked")
private <T extends TbAbstractDataSubCtx> T getSubCtx(String sessionId, int cmdId) {
Map<Integer, TbAbstractDataSubCtx> sessionSubs = subscriptionsBySessionId.get(sessionId);
private <T extends TbAbstractSubCtx> T getSubCtx(String sessionId, int cmdId) {
Map<Integer, TbAbstractSubCtx> sessionSubs = subscriptionsBySessionId.get(sessionId);
if (sessionSubs != null) {
return (T) sessionSubs.get(cmdId);
} else {
@ -464,17 +497,16 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
cleanupAndCancel(getSubCtx(sessionId, cmd.getCmdId()));
}
private void cleanupAndCancel(TbAbstractDataSubCtx ctx) {
private void cleanupAndCancel(TbAbstractSubCtx ctx) {
if (ctx != null) {
ctx.cancelTasks();
ctx.clearEntitySubscriptions();
ctx.clearDynamicValueSubscriptions();
ctx.clearSubscriptions();
}
}
@Override
public void cancelAllSessionSubscriptions(String sessionId) {
Map<Integer, TbAbstractDataSubCtx> sessionSubs = subscriptionsBySessionId.remove(sessionId);
Map<Integer, TbAbstractSubCtx> sessionSubs = subscriptionsBySessionId.remove(sessionId);
if (sessionSubs != null) {
sessionSubs.values().forEach(this::cleanupAndCancel);
}

267
application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractDataSubCtx.java

@ -15,32 +15,16 @@
*/
package org.thingsboard.server.service.subscription;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.AbstractDataQuery;
import org.thingsboard.server.common.data.query.ComplexFilterPredicate;
import org.thingsboard.server.common.data.query.DynamicValue;
import org.thingsboard.server.common.data.query.DynamicValueSourceType;
import org.thingsboard.server.common.data.query.EntityData;
import org.thingsboard.server.common.data.query.EntityDataPageLink;
import org.thingsboard.server.common.data.query.EntityDataQuery;
import org.thingsboard.server.common.data.query.EntityKey;
import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.common.data.query.FilterPredicateType;
import org.thingsboard.server.common.data.query.KeyFilter;
import org.thingsboard.server.common.data.query.KeyFilterPredicate;
import org.thingsboard.server.common.data.query.SimpleKeyFilterPredicate;
import org.thingsboard.server.common.data.query.TsValue;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.entity.EntityService;
@ -52,140 +36,25 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ScheduledFuture;
import java.util.function.Function;
import java.util.stream.Collectors;
@Slf4j
@Data
public abstract class TbAbstractDataSubCtx<T extends AbstractDataQuery<? extends EntityDataPageLink>> {
public abstract class TbAbstractDataSubCtx<T extends AbstractDataQuery<? extends EntityDataPageLink>> extends TbAbstractSubCtx<T> {
protected final String serviceId;
protected final SubscriptionServiceStatistics stats;
protected final TelemetryWebSocketService wsService;
protected final EntityService entityService;
protected final TbLocalSubscriptionService localSubscriptionService;
protected final AttributesService attributesService;
protected final TelemetryWebSocketSessionRef sessionRef;
protected final int cmdId;
protected final Map<Integer, EntityId> subToEntityIdMap;
protected final Set<Integer> subToDynamicValueKeySet;
@Getter
protected final Map<DynamicValueKey, List<DynamicValue>> dynamicValues;
@Getter
protected PageData<EntityData> data;
@Getter
@Setter
protected T query;
@Setter
protected volatile ScheduledFuture<?> refreshTask;
public TbAbstractDataSubCtx(String serviceId, TelemetryWebSocketService wsService,
EntityService entityService, TbLocalSubscriptionService localSubscriptionService,
AttributesService attributesService, SubscriptionServiceStatistics stats,
TelemetryWebSocketSessionRef sessionRef, int cmdId) {
this.serviceId = serviceId;
this.wsService = wsService;
this.entityService = entityService;
this.localSubscriptionService = localSubscriptionService;
this.attributesService = attributesService;
this.stats = stats;
this.sessionRef = sessionRef;
this.cmdId = cmdId;
super(serviceId, wsService, entityService, localSubscriptionService, attributesService, stats, sessionRef, cmdId);
this.subToEntityIdMap = new ConcurrentHashMap<>();
this.subToDynamicValueKeySet = ConcurrentHashMap.newKeySet();
this.dynamicValues = new ConcurrentHashMap<>();
}
public void setAndResolveQuery(T query) {
dynamicValues.clear();
this.query = query;
if (query != null && query.getKeyFilters() != null) {
for (KeyFilter filter : query.getKeyFilters()) {
registerDynamicValues(filter.getPredicate());
}
}
resolve(getTenantId(), getCustomerId(), getUserId());
}
public void resolve(TenantId tenantId, CustomerId customerId, UserId userId) {
List<ListenableFuture<DynamicValueKeySub>> futures = new ArrayList<>();
for (DynamicValueKey key : dynamicValues.keySet()) {
switch (key.getSourceType()) {
case CURRENT_TENANT:
futures.add(resolveEntityValue(tenantId, tenantId, key));
break;
case CURRENT_CUSTOMER:
if (customerId != null && !customerId.isNullUid()) {
futures.add(resolveEntityValue(tenantId, customerId, key));
}
break;
case CURRENT_USER:
if (userId != null && !userId.isNullUid()) {
futures.add(resolveEntityValue(tenantId, userId, key));
}
break;
}
}
try {
Map<EntityId, Map<String, DynamicValueKeySub>> tmpSubMap = new HashMap<>();
for (DynamicValueKeySub sub : Futures.successfulAsList(futures).get()) {
tmpSubMap.computeIfAbsent(sub.getEntityId(), tmp -> new HashMap<>()).put(sub.getKey().getSourceAttribute(), sub);
}
for (EntityId entityId : tmpSubMap.keySet()) {
Map<String, Long> keyStates = new HashMap<>();
Map<String, DynamicValueKeySub> dynamicValueKeySubMap = tmpSubMap.get(entityId);
dynamicValueKeySubMap.forEach((k, v) -> keyStates.put(k, v.getLastUpdateTs()));
int subIdx = sessionRef.getSessionSubIdSeq().incrementAndGet();
TbAttributeSubscription sub = TbAttributeSubscription.builder()
.serviceId(serviceId)
.sessionId(sessionRef.getSessionId())
.subscriptionId(subIdx)
.tenantId(sessionRef.getSecurityCtx().getTenantId())
.entityId(entityId)
.updateConsumer((s, subscriptionUpdate) -> dynamicValueSubUpdate(s, subscriptionUpdate, dynamicValueKeySubMap))
.allKeys(false)
.keyStates(keyStates)
.scope(TbAttributeSubscriptionScope.SERVER_SCOPE)
.build();
subToDynamicValueKeySet.add(subIdx);
localSubscriptionService.addSubscription(sub);
}
} catch (InterruptedException | ExecutionException e) {
log.info("[{}][{}][{}] Failed to resolve dynamic values: {}", tenantId, customerId, userId, dynamicValues.keySet());
}
}
private void dynamicValueSubUpdate(String sessionId, TelemetrySubscriptionUpdate subscriptionUpdate,
Map<String, DynamicValueKeySub> dynamicValueKeySubMap) {
Map<String, TsValue> latestUpdate = new HashMap<>();
subscriptionUpdate.getData().forEach((k, v) -> {
Object[] data = (Object[]) v.get(0);
latestUpdate.put(k, new TsValue((Long) data[0], (String) data[1]));
});
boolean invalidateFilter = false;
for (Map.Entry<String, TsValue> entry : latestUpdate.entrySet()) {
String k = entry.getKey();
TsValue tsValue = entry.getValue();
DynamicValueKeySub sub = dynamicValueKeySubMap.get(k);
if (sub.updateValue(tsValue)) {
invalidateFilter = true;
updateDynamicValuesByKey(sub, tsValue);
}
}
if (invalidateFilter) {
update();
}
}
public void fetchData() {
@ -231,104 +100,10 @@ public abstract class TbAbstractDataSubCtx<T extends AbstractDataQuery<? extends
return data.getData();
}
@Data
private static class DynamicValueKeySub {
private final DynamicValueKey key;
private final EntityId entityId;
private long lastUpdateTs;
private String lastUpdateValue;
boolean updateValue(TsValue value) {
if (value.getTs() > lastUpdateTs && (lastUpdateValue == null || !lastUpdateValue.equals(value.getValue()))) {
this.lastUpdateTs = value.getTs();
this.lastUpdateValue = value.getValue();
return true;
} else {
return false;
}
}
}
private ListenableFuture<DynamicValueKeySub> resolveEntityValue(TenantId tenantId, EntityId entityId, DynamicValueKey key) {
ListenableFuture<Optional<AttributeKvEntry>> entry = attributesService.find(tenantId, entityId,
TbAttributeSubscriptionScope.SERVER_SCOPE.name(), key.getSourceAttribute());
return Futures.transform(entry, attributeOpt -> {
DynamicValueKeySub sub = new DynamicValueKeySub(key, entityId);
if (attributeOpt.isPresent()) {
AttributeKvEntry attribute = attributeOpt.get();
sub.setLastUpdateTs(attribute.getLastUpdateTs());
sub.setLastUpdateValue(attribute.getValueAsString());
updateDynamicValuesByKey(sub, new TsValue(attribute.getLastUpdateTs(), attribute.getValueAsString()));
}
return sub;
}, MoreExecutors.directExecutor());
}
@SuppressWarnings("unchecked")
private void updateDynamicValuesByKey(DynamicValueKeySub sub, TsValue tsValue) {
DynamicValueKey dvk = sub.getKey();
switch (dvk.getPredicateType()) {
case STRING:
dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(tsValue.getValue()));
break;
case NUMERIC:
try {
Double dValue = Double.parseDouble(tsValue.getValue());
dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(dValue));
} catch (NumberFormatException e) {
dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(null));
}
break;
case BOOLEAN:
Boolean bValue = Boolean.parseBoolean(tsValue.getValue());
dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(bValue));
break;
}
}
@SuppressWarnings("unchecked")
private void registerDynamicValues(KeyFilterPredicate predicate) {
switch (predicate.getType()) {
case STRING:
case NUMERIC:
case BOOLEAN:
Optional<DynamicValue> value = getDynamicValueFromSimplePredicate((SimpleKeyFilterPredicate) predicate);
if (value.isPresent()) {
DynamicValue dynamicValue = value.get();
DynamicValueKey key = new DynamicValueKey(
predicate.getType(),
dynamicValue.getSourceType(),
dynamicValue.getSourceAttribute());
dynamicValues.computeIfAbsent(key, tmp -> new ArrayList<>()).add(dynamicValue);
}
break;
case COMPLEX:
((ComplexFilterPredicate) predicate).getPredicates().forEach(this::registerDynamicValues);
}
}
private Optional<DynamicValue<T>> getDynamicValueFromSimplePredicate(SimpleKeyFilterPredicate<T> predicate) {
if (predicate.getValue().getUserValue() == null) {
return Optional.ofNullable(predicate.getValue().getDynamicValue());
} else {
return Optional.empty();
}
}
public String getSessionId() {
return sessionRef.getSessionId();
}
public TenantId getTenantId() {
return sessionRef.getSecurityCtx().getTenantId();
}
public CustomerId getCustomerId() {
return sessionRef.getSecurityCtx().getCustomerId();
}
public UserId getUserId() {
return sessionRef.getSecurityCtx().getId();
@Override
public void clearSubscriptions() {
clearEntitySubscriptions();
super.clearSubscriptions();
}
public void clearEntitySubscriptions() {
@ -340,26 +115,6 @@ public abstract class TbAbstractDataSubCtx<T extends AbstractDataQuery<? extends
}
}
public void clearDynamicValueSubscriptions() {
if (subToDynamicValueKeySet != null) {
for (Integer subId : subToDynamicValueKeySet) {
localSubscriptionService.cancelSubscription(sessionRef.getSessionId(), subId);
}
subToDynamicValueKeySet.clear();
}
}
public void setRefreshTask(ScheduledFuture<?> task) {
this.refreshTask = task;
}
public void cancelTasks() {
if (this.refreshTask != null) {
log.trace("[{}][{}] Canceling old refresh task", sessionRef.getSessionId(), cmdId);
this.refreshTask.cancel(true);
}
}
public void createSubscriptions(List<EntityKey> keys, boolean resultToLatestValues) {
Map<EntityKeyType, List<EntityKey>> keysByType = getEntityKeyByTypeMap(keys);
for (EntityData entityData : data.getData()) {
@ -459,14 +214,4 @@ public abstract class TbAbstractDataSubCtx<T extends AbstractDataQuery<? extends
abstract void sendWsMsg(String sessionId, TelemetrySubscriptionUpdate subscriptionUpdate, EntityKeyType keyType, boolean resultToLatestValues);
@Data
private static class DynamicValueKey {
@Getter
private final FilterPredicateType predicateType;
@Getter
private final DynamicValueSourceType sourceType;
@Getter
private final String sourceAttribute;
}
}

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

@ -0,0 +1,315 @@
/**
* 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.service.subscription;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.query.ComplexFilterPredicate;
import org.thingsboard.server.common.data.query.DynamicValue;
import org.thingsboard.server.common.data.query.DynamicValueSourceType;
import org.thingsboard.server.common.data.query.EntityCountQuery;
import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.common.data.query.FilterPredicateType;
import org.thingsboard.server.common.data.query.KeyFilter;
import org.thingsboard.server.common.data.query.KeyFilterPredicate;
import org.thingsboard.server.common.data.query.SimpleKeyFilterPredicate;
import org.thingsboard.server.common.data.query.TsValue;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.service.telemetry.TelemetryWebSocketService;
import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef;
import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ScheduledFuture;
@Slf4j
@Data
public abstract class TbAbstractSubCtx<T extends EntityCountQuery> {
protected final String serviceId;
protected final SubscriptionServiceStatistics stats;
protected final TelemetryWebSocketService wsService;
protected final EntityService entityService;
protected final TbLocalSubscriptionService localSubscriptionService;
protected final AttributesService attributesService;
protected final TelemetryWebSocketSessionRef sessionRef;
protected final int cmdId;
protected final Set<Integer> subToDynamicValueKeySet;
@Getter
protected final Map<DynamicValueKey, List<DynamicValue>> dynamicValues;
@Getter
@Setter
protected T query;
@Setter
protected volatile ScheduledFuture<?> refreshTask;
public TbAbstractSubCtx(String serviceId, TelemetryWebSocketService wsService,
EntityService entityService, TbLocalSubscriptionService localSubscriptionService,
AttributesService attributesService, SubscriptionServiceStatistics stats,
TelemetryWebSocketSessionRef sessionRef, int cmdId) {
this.serviceId = serviceId;
this.wsService = wsService;
this.entityService = entityService;
this.localSubscriptionService = localSubscriptionService;
this.attributesService = attributesService;
this.stats = stats;
this.sessionRef = sessionRef;
this.cmdId = cmdId;
this.subToDynamicValueKeySet = ConcurrentHashMap.newKeySet();
this.dynamicValues = new ConcurrentHashMap<>();
}
public void setAndResolveQuery(T query) {
dynamicValues.clear();
this.query = query;
if (query != null && query.getKeyFilters() != null) {
for (KeyFilter filter : query.getKeyFilters()) {
registerDynamicValues(filter.getPredicate());
}
}
resolve(getTenantId(), getCustomerId(), getUserId());
}
public void resolve(TenantId tenantId, CustomerId customerId, UserId userId) {
List<ListenableFuture<DynamicValueKeySub>> futures = new ArrayList<>();
for (DynamicValueKey key : dynamicValues.keySet()) {
switch (key.getSourceType()) {
case CURRENT_TENANT:
futures.add(resolveEntityValue(tenantId, tenantId, key));
break;
case CURRENT_CUSTOMER:
if (customerId != null && !customerId.isNullUid()) {
futures.add(resolveEntityValue(tenantId, customerId, key));
}
break;
case CURRENT_USER:
if (userId != null && !userId.isNullUid()) {
futures.add(resolveEntityValue(tenantId, userId, key));
}
break;
}
}
try {
Map<EntityId, Map<String, DynamicValueKeySub>> tmpSubMap = new HashMap<>();
for (DynamicValueKeySub sub : Futures.successfulAsList(futures).get()) {
tmpSubMap.computeIfAbsent(sub.getEntityId(), tmp -> new HashMap<>()).put(sub.getKey().getSourceAttribute(), sub);
}
for (EntityId entityId : tmpSubMap.keySet()) {
Map<String, Long> keyStates = new HashMap<>();
Map<String, DynamicValueKeySub> dynamicValueKeySubMap = tmpSubMap.get(entityId);
dynamicValueKeySubMap.forEach((k, v) -> keyStates.put(k, v.getLastUpdateTs()));
int subIdx = sessionRef.getSessionSubIdSeq().incrementAndGet();
TbAttributeSubscription sub = TbAttributeSubscription.builder()
.serviceId(serviceId)
.sessionId(sessionRef.getSessionId())
.subscriptionId(subIdx)
.tenantId(sessionRef.getSecurityCtx().getTenantId())
.entityId(entityId)
.updateConsumer((s, subscriptionUpdate) -> dynamicValueSubUpdate(s, subscriptionUpdate, dynamicValueKeySubMap))
.allKeys(false)
.keyStates(keyStates)
.scope(TbAttributeSubscriptionScope.SERVER_SCOPE)
.build();
subToDynamicValueKeySet.add(subIdx);
localSubscriptionService.addSubscription(sub);
}
} catch (InterruptedException | ExecutionException e) {
log.info("[{}][{}][{}] Failed to resolve dynamic values: {}", tenantId, customerId, userId, dynamicValues.keySet());
}
}
private void dynamicValueSubUpdate(String sessionId, TelemetrySubscriptionUpdate subscriptionUpdate,
Map<String, DynamicValueKeySub> dynamicValueKeySubMap) {
Map<String, TsValue> latestUpdate = new HashMap<>();
subscriptionUpdate.getData().forEach((k, v) -> {
Object[] data = (Object[]) v.get(0);
latestUpdate.put(k, new TsValue((Long) data[0], (String) data[1]));
});
boolean invalidateFilter = false;
for (Map.Entry<String, TsValue> entry : latestUpdate.entrySet()) {
String k = entry.getKey();
TsValue tsValue = entry.getValue();
DynamicValueKeySub sub = dynamicValueKeySubMap.get(k);
if (sub.updateValue(tsValue)) {
invalidateFilter = true;
updateDynamicValuesByKey(sub, tsValue);
}
}
if (invalidateFilter) {
update();
}
}
public abstract void fetchData();
protected abstract void update();
public void clearSubscriptions() {
clearDynamicValueSubscriptions();
}
@Data
private static class DynamicValueKeySub {
private final DynamicValueKey key;
private final EntityId entityId;
private long lastUpdateTs;
private String lastUpdateValue;
boolean updateValue(TsValue value) {
if (value.getTs() > lastUpdateTs && (lastUpdateValue == null || !lastUpdateValue.equals(value.getValue()))) {
this.lastUpdateTs = value.getTs();
this.lastUpdateValue = value.getValue();
return true;
} else {
return false;
}
}
}
private ListenableFuture<DynamicValueKeySub> resolveEntityValue(TenantId tenantId, EntityId entityId, DynamicValueKey key) {
ListenableFuture<Optional<AttributeKvEntry>> entry = attributesService.find(tenantId, entityId,
TbAttributeSubscriptionScope.SERVER_SCOPE.name(), key.getSourceAttribute());
return Futures.transform(entry, attributeOpt -> {
DynamicValueKeySub sub = new DynamicValueKeySub(key, entityId);
if (attributeOpt.isPresent()) {
AttributeKvEntry attribute = attributeOpt.get();
sub.setLastUpdateTs(attribute.getLastUpdateTs());
sub.setLastUpdateValue(attribute.getValueAsString());
updateDynamicValuesByKey(sub, new TsValue(attribute.getLastUpdateTs(), attribute.getValueAsString()));
}
return sub;
}, MoreExecutors.directExecutor());
}
@SuppressWarnings("unchecked")
protected void updateDynamicValuesByKey(DynamicValueKeySub sub, TsValue tsValue) {
DynamicValueKey dvk = sub.getKey();
switch (dvk.getPredicateType()) {
case STRING:
dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(tsValue.getValue()));
break;
case NUMERIC:
try {
Double dValue = Double.parseDouble(tsValue.getValue());
dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(dValue));
} catch (NumberFormatException e) {
dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(null));
}
break;
case BOOLEAN:
Boolean bValue = Boolean.parseBoolean(tsValue.getValue());
dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(bValue));
break;
}
}
@SuppressWarnings("unchecked")
private void registerDynamicValues(KeyFilterPredicate predicate) {
switch (predicate.getType()) {
case STRING:
case NUMERIC:
case BOOLEAN:
Optional<DynamicValue> value = getDynamicValueFromSimplePredicate((SimpleKeyFilterPredicate) predicate);
if (value.isPresent()) {
DynamicValue dynamicValue = value.get();
DynamicValueKey key = new DynamicValueKey(
predicate.getType(),
dynamicValue.getSourceType(),
dynamicValue.getSourceAttribute());
dynamicValues.computeIfAbsent(key, tmp -> new ArrayList<>()).add(dynamicValue);
}
break;
case COMPLEX:
((ComplexFilterPredicate) predicate).getPredicates().forEach(this::registerDynamicValues);
}
}
private Optional<DynamicValue<T>> getDynamicValueFromSimplePredicate(SimpleKeyFilterPredicate<T> predicate) {
if (predicate.getValue().getUserValue() == null) {
return Optional.ofNullable(predicate.getValue().getDynamicValue());
} else {
return Optional.empty();
}
}
public String getSessionId() {
return sessionRef.getSessionId();
}
public TenantId getTenantId() {
return sessionRef.getSecurityCtx().getTenantId();
}
public CustomerId getCustomerId() {
return sessionRef.getSecurityCtx().getCustomerId();
}
public UserId getUserId() {
return sessionRef.getSecurityCtx().getId();
}
protected void clearDynamicValueSubscriptions() {
if (subToDynamicValueKeySet != null) {
for (Integer subId : subToDynamicValueKeySet) {
localSubscriptionService.cancelSubscription(sessionRef.getSessionId(), subId);
}
subToDynamicValueKeySet.clear();
}
}
public void setRefreshTask(ScheduledFuture<?> task) {
this.refreshTask = task;
}
public void cancelTasks() {
if (this.refreshTask != null) {
log.trace("[{}][{}] Canceling old refresh task", sessionRef.getSessionId(), cmdId);
this.refreshTask.cancel(true);
}
}
@Data
public static class DynamicValueKey {
@Getter
private final FilterPredicateType predicateType;
@Getter
private final DynamicValueSourceType sourceType;
@Getter
private final String sourceAttribute;
}
}

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

@ -90,8 +90,7 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx<AlarmDataQuery> {
AlarmDataUpdate update;
if (!entitiesMap.isEmpty()) {
long start = System.currentTimeMillis();
PageData<AlarmData> alarms = alarmService.findAlarmDataByQueryForEntities(getTenantId(), getCustomerId(),
query, getOrderedEntityIds());
PageData<AlarmData> alarms = alarmService.findAlarmDataByQueryForEntities(getTenantId(), getCustomerId(), query, getOrderedEntityIds());
long end = System.currentTimeMillis();
stats.getAlarmQueryInvocationCnt().incrementAndGet();
stats.getAlarmQueryTimeSpent().addAndGet(end - start);

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

@ -0,0 +1,55 @@
/**
* 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.service.subscription;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.query.EntityCountQuery;
import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.service.telemetry.TelemetryWebSocketService;
import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountUpdate;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate;
import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate;
@Slf4j
public class TbEntityCountSubCtx extends TbAbstractSubCtx<EntityCountQuery> {
private volatile int result;
public TbEntityCountSubCtx(String serviceId, TelemetryWebSocketService wsService, EntityService entityService,
TbLocalSubscriptionService localSubscriptionService, AttributesService attributesService,
SubscriptionServiceStatistics stats, TelemetryWebSocketSessionRef sessionRef, int cmdId) {
super(serviceId, wsService, entityService, localSubscriptionService, attributesService, stats, sessionRef, cmdId);
}
@Override
public void fetchData() {
result = (int) entityService.countEntitiesByQuery(getTenantId(), getCustomerId(), query);
wsService.sendWsMsg(sessionRef.getSessionId(), new EntityCountUpdate(cmdId, result));
}
@Override
protected void update() {
int newCount = (int) entityService.countEntitiesByQuery(getTenantId(), getCustomerId(), query);
if (newCount != result) {
result = newCount;
wsService.sendWsMsg(sessionRef.getSessionId(), new EntityCountUpdate(cmdId, result));
}
}
}

3
application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubscriptionService.java

@ -17,6 +17,7 @@ package org.thingsboard.server.service.subscription;
import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef;
import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUnsubscribeCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.UnsubscribeCmd;
@ -25,6 +26,8 @@ public interface TbEntityDataSubscriptionService {
void handleCmd(TelemetryWebSocketSessionRef sessionId, EntityDataCmd cmd);
void handleCmd(TelemetryWebSocketSessionRef sessionId, EntityCountCmd cmd);
void handleCmd(TelemetryWebSocketSessionRef sessionId, AlarmDataCmd cmd);
void cancelSubscription(String sessionId, UnsubscribeCmd subscriptionId);

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

@ -51,22 +51,21 @@ import org.thingsboard.server.service.security.ValidationResult;
import org.thingsboard.server.service.security.ValidationResultCode;
import org.thingsboard.server.service.security.model.UserPrincipal;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.subscription.TbAttributeSubscription;
import org.thingsboard.server.service.subscription.TbAttributeSubscriptionScope;
import org.thingsboard.server.service.subscription.TbEntityDataSubscriptionService;
import org.thingsboard.server.service.subscription.TbLocalSubscriptionService;
import org.thingsboard.server.service.subscription.TbAttributeSubscriptionScope;
import org.thingsboard.server.service.subscription.TbAttributeSubscription;
import org.thingsboard.server.service.subscription.TbTimeseriesSubscription;
import org.thingsboard.server.service.telemetry.cmd.TelemetryPluginCmdsWrapper;
import org.thingsboard.server.service.telemetry.cmd.v1.AttributesSubscriptionCmd;
import org.thingsboard.server.service.telemetry.cmd.v1.GetHistoryCmd;
import org.thingsboard.server.service.telemetry.cmd.v1.SubscriptionCmd;
import org.thingsboard.server.service.telemetry.cmd.v1.TelemetryPluginCmd;
import org.thingsboard.server.service.telemetry.cmd.TelemetryPluginCmdsWrapper;
import org.thingsboard.server.service.telemetry.cmd.v1.TimeseriesSubscriptionCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataUnsubscribeCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.DataUpdate;
import org.thingsboard.server.service.telemetry.cmd.v2.CmdUpdate;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUnsubscribeCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate;
import org.thingsboard.server.service.telemetry.cmd.v2.UnsubscribeCmd;
import org.thingsboard.server.service.telemetry.exception.UnauthorizedException;
@ -89,6 +88,8 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@ -151,14 +152,23 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
private ExecutorService executor;
private String serviceId;
private ScheduledExecutorService pingExecutor;
@PostConstruct
public void initExecutor() {
serviceId = serviceInfoProvider.getServiceId();
executor = Executors.newWorkStealingPool(50);
pingExecutor = Executors.newSingleThreadScheduledExecutor();
pingExecutor.scheduleWithFixedDelay(this::sendPing, 10000, 10000, TimeUnit.MILLISECONDS);
}
@PreDestroy
public void shutdownExecutor() {
if (pingExecutor != null) {
pingExecutor.shutdownNow();
}
if (executor != null) {
executor.shutdownNow();
}
@ -216,12 +226,18 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
if (cmdsWrapper.getAlarmDataCmds() != null) {
cmdsWrapper.getAlarmDataCmds().forEach(cmd -> handleWsAlarmDataCmd(sessionRef, cmd));
}
if (cmdsWrapper.getEntityCountCmds() != null) {
cmdsWrapper.getEntityCountCmds().forEach(cmd -> handleWsEntityCountCmd(sessionRef, cmd));
}
if (cmdsWrapper.getEntityDataUnsubscribeCmds() != null) {
cmdsWrapper.getEntityDataUnsubscribeCmds().forEach(cmd -> handleWsDataUnsubscribeCmd(sessionRef, cmd));
}
if (cmdsWrapper.getAlarmDataUnsubscribeCmds() != null) {
cmdsWrapper.getAlarmDataUnsubscribeCmds().forEach(cmd -> handleWsDataUnsubscribeCmd(sessionRef, cmd));
}
if (cmdsWrapper.getEntityCountUnsubscribeCmds() != null) {
cmdsWrapper.getEntityCountUnsubscribeCmds().forEach(cmd -> handleWsDataUnsubscribeCmd(sessionRef, cmd));
}
}
} catch (IOException e) {
log.warn("Failed to decode subscription cmd: {}", e.getMessage(), e);
@ -239,6 +255,16 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
}
}
private void handleWsEntityCountCmd(TelemetryWebSocketSessionRef sessionRef, EntityCountCmd cmd) {
String sessionId = sessionRef.getSessionId();
log.debug("[{}] Processing: {}", sessionId, cmd);
if (validateSessionMetadata(sessionRef, cmd.getCmdId(), sessionId)
&& validateSubscriptionCmd(sessionRef, cmd)) {
entityDataSubService.handleCmd(sessionRef, cmd);
}
}
private void handleWsAlarmDataCmd(TelemetryWebSocketSessionRef sessionRef, AlarmDataCmd cmd) {
String sessionId = sessionRef.getSessionId();
log.debug("[{}] Processing: {}", sessionId, cmd);
@ -264,7 +290,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
}
@Override
public void sendWsMsg(String sessionId, DataUpdate update) {
public void sendWsMsg(String sessionId, CmdUpdate update) {
sendWsMsg(sessionId, update.getCmdId(), update);
}
@ -679,6 +705,20 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
return true;
}
private boolean validateSubscriptionCmd(TelemetryWebSocketSessionRef sessionRef, EntityCountCmd cmd) {
if (cmd.getCmdId() < 0) {
TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST,
"Cmd id is negative value!");
sendWsMsg(sessionRef, update);
return false;
} else if (cmd.getQuery() == null) {
TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST, "Query is empty!");
sendWsMsg(sessionRef, update);
return false;
}
return true;
}
private boolean validateSubscriptionCmd(TelemetryWebSocketSessionRef sessionRef, AlarmDataCmd cmd) {
if (cmd.getCmdId() < 0) {
TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST,
@ -744,6 +784,17 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
}
}
private void sendPing() {
long currentTime = System.currentTimeMillis();
wsSessionsMap.values().forEach(md ->
executor.submit(() -> {
try {
msgEndpoint.sendPing(md.getSessionRef(), currentTime);
} catch (IOException e) {
log.warn("[{}] Failed to send ping: {}", md.getSessionRef().getSessionId(), e);
}
}));
}
private static Optional<Set<String>> getKeys(TelemetryPluginCmd cmd) {
if (!StringUtils.isEmpty(cmd.getKeys())) {

2
application/src/main/java/org/thingsboard/server/service/telemetry/TelemetryWebSocketMsgEndpoint.java

@ -26,5 +26,7 @@ public interface TelemetryWebSocketMsgEndpoint {
void send(TelemetryWebSocketSessionRef sessionRef, int subscriptionId, String msg) throws IOException;
void sendPing(TelemetryWebSocketSessionRef sessionRef, long currentTime) throws IOException;
void close(TelemetryWebSocketSessionRef sessionRef, CloseStatus withReason) throws IOException;
}

3
application/src/main/java/org/thingsboard/server/service/telemetry/TelemetryWebSocketService.java

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.service.telemetry;
import org.thingsboard.server.service.telemetry.cmd.v2.CmdUpdate;
import org.thingsboard.server.service.telemetry.cmd.v2.DataUpdate;
import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate;
@ -29,6 +30,6 @@ public interface TelemetryWebSocketService {
void sendWsMsg(String sessionId, TelemetrySubscriptionUpdate update);
void sendWsMsg(String sessionId, DataUpdate update);
void sendWsMsg(String sessionId, CmdUpdate update);
}

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

@ -21,6 +21,8 @@ import org.thingsboard.server.service.telemetry.cmd.v1.GetHistoryCmd;
import org.thingsboard.server.service.telemetry.cmd.v1.TimeseriesSubscriptionCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataUnsubscribeCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountUnsubscribeCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUnsubscribeCmd;
@ -46,4 +48,8 @@ public class TelemetryPluginCmdsWrapper {
private List<AlarmDataUnsubscribeCmd> alarmDataUnsubscribeCmds;
private List<EntityCountCmd> entityCountCmds;
private List<EntityCountUnsubscribeCmd> entityCountUnsubscribeCmds;
}

8
application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/AlarmDataUpdate.java

@ -18,14 +18,14 @@ package org.thingsboard.server.service.telemetry.cmd.v2;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.AlarmData;
import org.thingsboard.server.common.data.query.EntityData;
import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode;
import java.util.List;
@ToString
public class AlarmDataUpdate extends DataUpdate<AlarmData> {
@Getter
@ -44,8 +44,8 @@ public class AlarmDataUpdate extends DataUpdate<AlarmData> {
}
@Override
public DataUpdateType getDataUpdateType() {
return DataUpdateType.ALARM_DATA;
public CmdUpdateType getCmdUpdateType() {
return CmdUpdateType.ALARM_DATA;
}
@JsonCreator

33
application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/CmdUpdate.java

@ -0,0 +1,33 @@
/**
* 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.service.telemetry.cmd.v2;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public abstract class CmdUpdate {
private final int cmdId;
private final int errorCode;
private final String errorMsg;
public abstract CmdUpdateType getCmdUpdateType();
}

5
application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/DataUpdateType.java → application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/CmdUpdateType.java

@ -15,7 +15,8 @@
*/
package org.thingsboard.server.service.telemetry.cmd.v2;
public enum DataUpdateType {
public enum CmdUpdateType {
ENTITY_DATA,
ALARM_DATA
ALARM_DATA,
COUNT_DATA
}

21
application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/DataUpdate.java

@ -15,24 +15,24 @@
*/
package org.thingsboard.server.service.telemetry.cmd.v2;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode;
import java.util.List;
@Data
@AllArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public abstract class DataUpdate<T> {
public abstract class DataUpdate<T> extends CmdUpdate {
private final int cmdId;
@Getter
private final PageData<T> data;
@Getter
private final List<T> update;
private final int errorCode;
private final String errorMsg;
public DataUpdate(int cmdId, PageData<T> data, List<T> update, int errorCode, String errorMsg) {
super(cmdId, errorCode, errorMsg);
this.data = data;
this.update = update;
}
public DataUpdate(int cmdId, PageData<T> data, List<T> update) {
this(cmdId, data, update, SubscriptionErrorCode.NO_ERROR.getCode(), null);
@ -42,5 +42,4 @@ public abstract class DataUpdate<T> {
this(cmdId, null, null, errorCode, errorMsg);
}
public abstract DataUpdateType getDataUpdateType();
}

35
application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityCountCmd.java

@ -0,0 +1,35 @@
/**
* 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.service.telemetry.cmd.v2;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import org.thingsboard.server.common.data.query.EntityCountQuery;
import org.thingsboard.server.common.data.query.EntityDataQuery;
public class EntityCountCmd extends DataCmd {
@Getter
private final EntityCountQuery query;
@JsonCreator
public EntityCountCmd(@JsonProperty("cmdId") int cmdId,
@JsonProperty("query") EntityCountQuery query) {
super(cmdId);
this.query = query;
}
}

25
application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityCountUnsubscribeCmd.java

@ -0,0 +1,25 @@
/**
* 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.service.telemetry.cmd.v2;
import lombok.Data;
@Data
public class EntityCountUnsubscribeCmd implements UnsubscribeCmd {
private final int cmdId;
}

57
application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityCountUpdate.java

@ -0,0 +1,57 @@
/**
* 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.service.telemetry.cmd.v2;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import lombok.ToString;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.EntityData;
import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode;
import java.util.List;
@ToString
public class EntityCountUpdate extends CmdUpdate {
@Getter
private int count;
public EntityCountUpdate(int cmdId, int count) {
super(cmdId, SubscriptionErrorCode.NO_ERROR.getCode(), null);
this.count = count;
}
public EntityCountUpdate(int cmdId, int errorCode, String errorMsg) {
super(cmdId, errorCode, errorMsg);
}
@Override
public CmdUpdateType getCmdUpdateType() {
return CmdUpdateType.COUNT_DATA;
}
@JsonCreator
public EntityCountUpdate(@JsonProperty("cmdId") int cmdId,
@JsonProperty("count") int count,
@JsonProperty("errorCode") int errorCode,
@JsonProperty("errorMsg") String errorMsg) {
super(cmdId, errorCode, errorMsg);
this.count = count;
}
}

8
application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityDataUpdate.java

@ -16,16 +16,16 @@
package org.thingsboard.server.service.telemetry.cmd.v2;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import lombok.ToString;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.EntityData;
import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode;
import java.util.List;
@ToString
public class EntityDataUpdate extends DataUpdate<EntityData> {
@Getter
@ -41,8 +41,8 @@ public class EntityDataUpdate extends DataUpdate<EntityData> {
}
@Override
public DataUpdateType getDataUpdateType() {
return DataUpdateType.ENTITY_DATA;
public CmdUpdateType getCmdUpdateType() {
return CmdUpdateType.ENTITY_DATA;
}
@JsonCreator

2
application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/UnsubscribeCmd.java

@ -15,8 +15,6 @@
*/
package org.thingsboard.server.service.telemetry.cmd.v2;
import lombok.Data;
public interface UnsubscribeCmd {
int getCmdId();

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

@ -25,11 +25,14 @@ import com.google.protobuf.ByteString;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.ApiUsageState;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.Resource;
import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials;
import org.thingsboard.server.common.data.device.credentials.ProvisionDeviceCredentialsData;
@ -53,14 +56,15 @@ import org.thingsboard.server.dao.device.provision.ProvisionFailedException;
import org.thingsboard.server.dao.device.provision.ProvisionRequest;
import org.thingsboard.server.dao.device.provision.ProvisionResponse;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto;
import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetResourceRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ProvisionResponseStatus;
import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg;
@ -104,6 +108,7 @@ public class DefaultTransportApiService implements TransportApiService {
private final TbClusterService tbClusterService;
private final DataDecodingEncodingService dataDecodingEncodingService;
private final DeviceProvisionService deviceProvisionService;
private final ResourceService resourceService;
private final ConcurrentMap<String, ReentrantLock> deviceCreationLocks = new ConcurrentHashMap<>();
@ -112,7 +117,7 @@ public class DefaultTransportApiService implements TransportApiService {
RelationService relationService, DeviceCredentialsService deviceCredentialsService,
DeviceStateService deviceStateService, DbCallbackExecutorService dbCallbackExecutorService,
TbClusterService tbClusterService, DataDecodingEncodingService dataDecodingEncodingService,
DeviceProvisionService deviceProvisionService) {
DeviceProvisionService deviceProvisionService, ResourceService resourceService) {
this.deviceProfileCache = deviceProfileCache;
this.tenantProfileCache = tenantProfileCache;
this.apiUsageStateService = apiUsageStateService;
@ -124,6 +129,7 @@ public class DefaultTransportApiService implements TransportApiService {
this.tbClusterService = tbClusterService;
this.dataDecodingEncodingService = dataDecodingEncodingService;
this.deviceProvisionService = deviceProvisionService;
this.resourceService = resourceService;
}
@Override
@ -157,6 +163,9 @@ public class DefaultTransportApiService implements TransportApiService {
} else if (transportApiRequestMsg.hasProvisionDeviceRequestMsg()) {
return Futures.transform(handle(transportApiRequestMsg.getProvisionDeviceRequestMsg()),
value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
} else if (transportApiRequestMsg.hasResourceRequestMsg()) {
return Futures.transform(handle(transportApiRequestMsg.getResourceRequestMsg()),
value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
}
return Futures.transform(getEmptyTransportApiResponseFuture(),
value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
@ -315,9 +324,9 @@ public class DefaultTransportApiService implements TransportApiService {
return TransportApiResponseMsg.newBuilder().setProvisionDeviceResponseMsg(TransportProtos.ProvisionDeviceResponseMsg.newBuilder().setStatus(status).build()).build();
}
TransportProtos.ProvisionDeviceResponseMsg.Builder provisionResponse = TransportProtos.ProvisionDeviceResponseMsg.newBuilder()
.setCredentialsType(TransportProtos.CredentialsType.valueOf(deviceCredentials.getCredentialsType().name()))
.setStatus(status);
switch (deviceCredentials.getCredentialsType()){
.setCredentialsType(TransportProtos.CredentialsType.valueOf(deviceCredentials.getCredentialsType().name()))
.setStatus(status);
switch (deviceCredentials.getCredentialsType()) {
case ACCESS_TOKEN:
provisionResponse.setCredentialsValue(deviceCredentials.getCredentialsId());
break;
@ -353,6 +362,24 @@ public class DefaultTransportApiService implements TransportApiService {
return Futures.immediateFuture(TransportApiResponseMsg.newBuilder().setEntityProfileResponseMsg(builder).build());
}
private ListenableFuture<TransportApiResponseMsg> handle(GetResourceRequestMsg requestMsg) {
TenantId tenantId = new TenantId(new UUID(requestMsg.getTenantIdMSB(), requestMsg.getTenantIdLSB()));
ResourceType resourceType = ResourceType.valueOf(requestMsg.getResourceType());
String resourceId = requestMsg.getResourceId();
TransportProtos.GetResourceResponseMsg.Builder builder = TransportProtos.GetResourceResponseMsg.newBuilder();
Resource resource = resourceService.getResource(tenantId, resourceType, resourceId);
if (resource == null && !tenantId.equals(TenantId.SYS_TENANT_ID)) {
resource = resourceService.getResource(TenantId.SYS_TENANT_ID, resourceType, resourceId);
}
if (resource != null) {
builder.setResource(ByteString.copyFrom(dataDecodingEncodingService.encode(resource)));
}
return Futures.immediateFuture(TransportApiResponseMsg.newBuilder().setResourceResponseMsg(builder).build());
}
private ListenableFuture<TransportApiResponseMsg> getDeviceInfo(DeviceId deviceId, DeviceCredentials credentials) {
return Futures.transform(deviceService.findDeviceByIdAsync(TenantId.SYS_TENANT_ID, deviceId), device -> {
if (device == null) {

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

@ -30,7 +30,7 @@
<!-- <logger name="org.thingsboard.server.service.queue" level="TRACE" />-->
<!-- <logger name="org.thingsboard.server.service.transport" level="TRACE" />-->
<!-- <logger name="org.thingsboard.server.queue.memory.InMemoryStorage" level="DEBUG" />-->
<!-- <logger name="org.thingsboard.server.service.ttl.AbstractCleanUpService" level="DEBUG" />-->
<!-- <logger name="org.thingsboard.server.service.subscription" level="TRACE"/>-->
<!-- <logger name="org.thingsboard.server.service.telemetry" level="TRACE"/>-->

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

@ -34,6 +34,7 @@ server:
log_controller_error_stack_trace: "${HTTP_LOG_CONTROLLER_ERROR_STACK_TRACE:false}"
ws:
send_timeout: "${TB_SERVER_WS_SEND_TIMEOUT:5000}"
ping_timeout: "${TB_SERVER_WS_PING_TIMEOUT:30000}"
limits:
# Limit the amount of sessions and subscriptions available on each server. Put values to zero to disable particular limitation
max_sessions_per_tenant: "${TB_SERVER_WS_TENANT_RATE_LIMITS_MAX_SESSIONS_PER_TENANT:0}"
@ -326,6 +327,9 @@ actors:
cache:
# caffeine or redis
type: "${CACHE_TYPE:caffeine}"
attributes:
# make sure that if cache.type is 'redis' and cache.attributes.enabled is 'true' that you change 'maxmemory-policy' Redis config property to 'allkeys-lru', 'allkeys-lfu' or 'allkeys-random'
enabled: "${CACHE_ATTRIBUTES_ENABLED:true}"
caffeine:
specs:
@ -359,6 +363,12 @@ caffeine:
deviceProfiles:
timeToLiveInMinutes: 1440
maxSize: 0
attributes:
timeToLiveInMinutes: 1440
maxSize: 100000
tokensOutdatageTime:
timeToLiveInMinutes: 20000
maxSize: 10000
edges:
timeToLiveInMinutes: 1440
maxSize: 0
@ -547,6 +557,7 @@ transport:
http:
enabled: "${HTTP_ENABLED:true}"
request_timeout: "${HTTP_REQUEST_TIMEOUT:60000}"
max_request_timeout: "${HTTP_MAX_REQUEST_TIMEOUT:300000}"
# Local MQTT transport parameters
mqtt:
# Enable/disable mqtt transport protocol.

44
application/src/test/java/org/thingsboard/server/controller/BaseEntityQueryControllerTest.java

@ -16,16 +16,10 @@
package org.thingsboard.server.controller;
import com.fasterxml.jackson.core.type.TypeReference;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.ssl.SSLContexts;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.springframework.boot.web.server.LocalServerPort;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityType;
@ -33,7 +27,6 @@ import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.kv.Aggregation;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.DeviceTypeFilter;
import org.thingsboard.server.common.data.query.EntityCountQuery;
@ -44,20 +37,15 @@ import org.thingsboard.server.common.data.query.EntityDataSortOrder;
import org.thingsboard.server.common.data.query.EntityKey;
import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.common.data.query.EntityListFilter;
import org.thingsboard.server.common.data.query.EntityTypeFilter;
import org.thingsboard.server.common.data.query.FilterPredicateValue;
import org.thingsboard.server.common.data.query.KeyFilter;
import org.thingsboard.server.common.data.query.NumericFilterPredicate;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityHistoryCmd;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ -132,6 +120,14 @@ public abstract class BaseEntityQueryControllerTest extends AbstractControllerTe
count = doPostWithResponse("/api/entitiesQuery/count", countQuery, Long.class);
Assert.assertEquals(97, count.longValue());
EntityTypeFilter filter2 = new EntityTypeFilter();
filter2.setEntityType(EntityType.DEVICE);
EntityCountQuery countQuery2 = new EntityCountQuery(filter2);
Long count2 = doPostWithResponse("/api/entitiesQuery/count", countQuery2, Long.class);
Assert.assertEquals(97, count2.longValue());
}
@Test
@ -198,11 +194,31 @@ public abstract class BaseEntityQueryControllerTest extends AbstractControllerTe
Assert.assertEquals(11, data.getTotalElements());
Assert.assertEquals("Device19", data.getData().get(0).getLatest().get(EntityKeyType.ENTITY_FIELD).get("name").getValue());
EntityTypeFilter filter2 = new EntityTypeFilter();
filter2.setEntityType(EntityType.DEVICE);
EntityDataSortOrder sortOrder2 = new EntityDataSortOrder(
new EntityKey(EntityKeyType.ENTITY_FIELD, "createdTime"), EntityDataSortOrder.Direction.ASC
);
EntityDataPageLink pageLink2 = new EntityDataPageLink(10, 0, null, sortOrder2);
List<EntityKey> entityFields2 = Collections.singletonList(new EntityKey(EntityKeyType.ENTITY_FIELD, "name"));
EntityDataQuery query2 = new EntityDataQuery(filter2, pageLink2, entityFields2, null, null);
PageData<EntityData> data2 =
doPostWithTypedResponse("/api/entitiesQuery/find", query2, new TypeReference<PageData<EntityData>>() {
});
Assert.assertEquals(97, data2.getTotalElements());
Assert.assertEquals(10, data2.getTotalPages());
Assert.assertTrue(data2.hasNext());
Assert.assertEquals(10, data2.getData().size());
}
@Test
public void testFindEntityDataByQueryWithAttributes() throws Exception {
List<Device> devices = new ArrayList<>();
List<Long> temperatures = new ArrayList<>();
List<Long> highTemperatures = new ArrayList<>();

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

@ -35,16 +35,23 @@ import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.DeviceTypeFilter;
import org.thingsboard.server.common.data.query.EntityCountQuery;
import org.thingsboard.server.common.data.query.EntityData;
import org.thingsboard.server.common.data.query.EntityDataPageLink;
import org.thingsboard.server.common.data.query.EntityDataQuery;
import org.thingsboard.server.common.data.query.EntityKey;
import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.common.data.query.EntityKeyValueType;
import org.thingsboard.server.common.data.query.FilterPredicateValue;
import org.thingsboard.server.common.data.query.KeyFilter;
import org.thingsboard.server.common.data.query.NumericFilterPredicate;
import org.thingsboard.server.common.data.query.TsValue;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.service.subscription.TbAttributeSubscriptionScope;
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import org.thingsboard.server.service.telemetry.cmd.TelemetryPluginCmdsWrapper;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountUpdate;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityHistoryCmd;
@ -243,6 +250,98 @@ public class BaseWebsocketApiTest extends AbstractWebsocketTest {
Assert.assertEquals(new TsValue(dataPoint4.getTs(), dataPoint4.getValueAsString()), tsValues[0]);
}
@Test
public void testEntityCountWsCmd() throws Exception {
Device device = new Device();
device.setName("Device");
device.setType("default");
device.setLabel("testLabel" + (int) (Math.random() * 1000));
device = doPost("/api/device", device, Device.class);
AttributeKvEntry dataPoint1 = new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("temperature", 42L));
sendAttributes(device, TbAttributeSubscriptionScope.SERVER_SCOPE, Collections.singletonList(dataPoint1));
DeviceTypeFilter dtf1 = new DeviceTypeFilter();
dtf1.setDeviceNameFilter("D");
dtf1.setDeviceType("default");
EntityCountQuery edq1 = new EntityCountQuery(dtf1, Collections.emptyList());
EntityCountCmd cmd1 = new EntityCountCmd(1, edq1);
TelemetryPluginCmdsWrapper wrapper1 = new TelemetryPluginCmdsWrapper();
wrapper1.setEntityCountCmds(Collections.singletonList(cmd1));
wsClient.send(mapper.writeValueAsString(wrapper1));
String msg1 = wsClient.waitForReply();
EntityCountUpdate update1 = mapper.readValue(msg1, EntityCountUpdate.class);
Assert.assertEquals(1, update1.getCmdId());
Assert.assertEquals(1, update1.getCount());
DeviceTypeFilter dtf2 = new DeviceTypeFilter();
dtf2.setDeviceNameFilter("D");
dtf2.setDeviceType("non-existing-device-type");
EntityCountQuery edq2 = new EntityCountQuery(dtf2, Collections.emptyList());
EntityCountCmd cmd2 = new EntityCountCmd(2, edq2);
TelemetryPluginCmdsWrapper wrapper2 = new TelemetryPluginCmdsWrapper();
wrapper2.setEntityCountCmds(Collections.singletonList(cmd2));
wsClient.send(mapper.writeValueAsString(wrapper2));
String msg2 = wsClient.waitForReply();
EntityCountUpdate update2 = mapper.readValue(msg2, EntityCountUpdate.class);
Assert.assertEquals(2, update2.getCmdId());
Assert.assertEquals(0, update2.getCount());
KeyFilter highTemperatureFilter = new KeyFilter();
highTemperatureFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "temperature"));
NumericFilterPredicate predicate = new NumericFilterPredicate();
predicate.setValue(FilterPredicateValue.fromDouble(40));
predicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
highTemperatureFilter.setPredicate(predicate);
highTemperatureFilter.setValueType(EntityKeyValueType.NUMERIC);
DeviceTypeFilter dtf3 = new DeviceTypeFilter();
dtf3.setDeviceNameFilter("D");
dtf3.setDeviceType("default");
EntityCountQuery edq3 = new EntityCountQuery(dtf3, Collections.singletonList(highTemperatureFilter));
EntityCountCmd cmd3 = new EntityCountCmd(3, edq3);
TelemetryPluginCmdsWrapper wrapper3 = new TelemetryPluginCmdsWrapper();
wrapper3.setEntityCountCmds(Collections.singletonList(cmd3));
wsClient.send(mapper.writeValueAsString(wrapper3));
String msg3 = wsClient.waitForReply();
EntityCountUpdate update3 = mapper.readValue(msg3, EntityCountUpdate.class);
Assert.assertEquals(3, update3.getCmdId());
Assert.assertEquals(1, update3.getCount());
KeyFilter highTemperatureFilter2 = new KeyFilter();
highTemperatureFilter2.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "temperature"));
NumericFilterPredicate predicate2 = new NumericFilterPredicate();
predicate2.setValue(FilterPredicateValue.fromDouble(50));
predicate2.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
highTemperatureFilter2.setPredicate(predicate2);
highTemperatureFilter2.setValueType(EntityKeyValueType.NUMERIC);
DeviceTypeFilter dtf4 = new DeviceTypeFilter();
dtf4.setDeviceNameFilter("D");
dtf4.setDeviceType("default");
EntityCountQuery edq4 = new EntityCountQuery(dtf4, Collections.singletonList(highTemperatureFilter2));
EntityCountCmd cmd4 = new EntityCountCmd(4, edq4);
TelemetryPluginCmdsWrapper wrapper4 = new TelemetryPluginCmdsWrapper();
wrapper4.setEntityCountCmds(Collections.singletonList(cmd4));
wsClient.send(mapper.writeValueAsString(wrapper4));
String msg4 = wsClient.waitForReply();
EntityCountUpdate update4 = mapper.readValue(msg4, EntityCountUpdate.class);
Assert.assertEquals(4, update4.getCmdId());
Assert.assertEquals(0, update4.getCount());
}
@Test
public void testEntityDataLatestWidgetFlow() throws Exception {
Device device = new Device();

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

@ -27,7 +27,7 @@ import java.util.Arrays;
@RunWith(ClasspathSuite.class)
@ClasspathSuite.ClassnameFilters({
// "org.thingsboard.server.controller.sql.WebsocketApiSqlTest",
// "org.thingsboard.server.controller.sql.TenantProfileControllerSqlTest",
// "org.thingsboard.server.controller.sql.EntityQueryControllerSqlTest",
"org.thingsboard.server.controller.sql.*Test",
})
public class ControllerSqlTestSuite {

184
application/src/test/java/org/thingsboard/server/service/security/auth/TokenOutdatingTest.java

@ -0,0 +1,184 @@
/**
* 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.service.security.auth;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.thingsboard.server.common.data.CacheConstants;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.UserCredentials;
import org.thingsboard.server.common.data.security.model.JwtToken;
import org.thingsboard.server.config.JwtSettings;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.service.security.auth.jwt.JwtAuthenticationProvider;
import org.thingsboard.server.service.security.auth.jwt.RefreshTokenAuthenticationProvider;
import org.thingsboard.server.service.security.exception.JwtExpiredTokenException;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.UserPrincipal;
import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
import org.thingsboard.server.service.security.model.token.RawAccessJwtToken;
import java.util.UUID;
import static java.util.concurrent.TimeUnit.DAYS;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class TokenOutdatingTest {
private JwtAuthenticationProvider accessTokenAuthenticationProvider;
private RefreshTokenAuthenticationProvider refreshTokenAuthenticationProvider;
private TokenOutdatingService tokenOutdatingService;
private ConcurrentMapCacheManager cacheManager;
private JwtTokenFactory tokenFactory;
private JwtSettings jwtSettings;
private UserId userId;
@BeforeEach
public void setUp() {
jwtSettings = new JwtSettings();
jwtSettings.setTokenIssuer("test.io");
jwtSettings.setTokenExpirationTime((int) MINUTES.toSeconds(10));
jwtSettings.setRefreshTokenExpTime((int) DAYS.toSeconds(7));
jwtSettings.setTokenSigningKey("secret");
tokenFactory = new JwtTokenFactory(jwtSettings);
cacheManager = new ConcurrentMapCacheManager();
tokenOutdatingService = new TokenOutdatingService(cacheManager, tokenFactory, jwtSettings);
tokenOutdatingService.initCache();
userId = new UserId(UUID.randomUUID());
UserService userService = mock(UserService.class);
User user = new User();
user.setId(userId);
user.setAuthority(Authority.TENANT_ADMIN);
user.setEmail("email");
when(userService.findUserById(any(), eq(userId))).thenReturn(user);
UserCredentials userCredentials = new UserCredentials();
userCredentials.setEnabled(true);
when(userService.findUserCredentialsByUserId(any(), eq(userId))).thenReturn(userCredentials);
accessTokenAuthenticationProvider = new JwtAuthenticationProvider(tokenFactory, tokenOutdatingService);
refreshTokenAuthenticationProvider = new RefreshTokenAuthenticationProvider(tokenFactory, userService, mock(CustomerService.class), tokenOutdatingService);
}
@Test
public void testOutdateOldUserTokens() throws Exception {
JwtToken jwtToken = createAccessJwtToken(userId);
SECONDS.sleep(1); // need to wait before outdating so that outdatage time is strictly after token issue time
tokenOutdatingService.outdateOldUserTokens(userId);
assertTrue(tokenOutdatingService.isOutdated(jwtToken, userId));
SECONDS.sleep(1);
JwtToken newJwtToken = tokenFactory.createAccessJwtToken(createMockSecurityUser(userId));
assertFalse(tokenOutdatingService.isOutdated(newJwtToken, userId));
}
@Test
public void testAuthenticateWithOutdatedAccessToken() throws InterruptedException {
RawAccessJwtToken accessJwtToken = getRawJwtToken(createAccessJwtToken(userId));
assertDoesNotThrow(() -> {
accessTokenAuthenticationProvider.authenticate(new JwtAuthenticationToken(accessJwtToken));
});
SECONDS.sleep(1);
tokenOutdatingService.outdateOldUserTokens(userId);
assertThrows(JwtExpiredTokenException.class, () -> {
accessTokenAuthenticationProvider.authenticate(new JwtAuthenticationToken(accessJwtToken));
});
}
@Test
public void testAuthenticateWithOutdatedRefreshToken() throws InterruptedException {
RawAccessJwtToken refreshJwtToken = getRawJwtToken(createRefreshJwtToken(userId));
assertDoesNotThrow(() -> {
refreshTokenAuthenticationProvider.authenticate(new RefreshAuthenticationToken(refreshJwtToken));
});
SECONDS.sleep(1);
tokenOutdatingService.outdateOldUserTokens(userId);
assertThrows(CredentialsExpiredException.class, () -> {
refreshTokenAuthenticationProvider.authenticate(new RefreshAuthenticationToken(refreshJwtToken));
});
}
@Test
public void testTokensOutdatageTimeRemovalFromCache() throws Exception {
JwtToken jwtToken = createAccessJwtToken(userId);
SECONDS.sleep(1);
tokenOutdatingService.outdateOldUserTokens(userId);
int refreshTokenExpirationTime = 5;
jwtSettings.setRefreshTokenExpTime(refreshTokenExpirationTime);
SECONDS.sleep(refreshTokenExpirationTime - 2);
assertTrue(tokenOutdatingService.isOutdated(jwtToken, userId));
assertNotNull(cacheManager.getCache(CacheConstants.TOKEN_OUTDATAGE_TIME_CACHE).get(userId.getId().toString()));
SECONDS.sleep(3);
assertFalse(tokenOutdatingService.isOutdated(jwtToken, userId));
assertNull(cacheManager.getCache(CacheConstants.TOKEN_OUTDATAGE_TIME_CACHE).get(userId.getId().toString()));
}
private JwtToken createAccessJwtToken(UserId userId) {
return tokenFactory.createAccessJwtToken(createMockSecurityUser(userId));
}
private JwtToken createRefreshJwtToken(UserId userId) {
return tokenFactory.createRefreshToken(createMockSecurityUser(userId));
}
private RawAccessJwtToken getRawJwtToken(JwtToken token) {
return new RawAccessJwtToken(token.getToken());
}
private SecurityUser createMockSecurityUser(UserId userId) {
SecurityUser securityUser = new SecurityUser();
securityUser.setEmail("email");
securityUser.setUserPrincipal(new UserPrincipal(UserPrincipal.Type.USER_NAME, securityUser.getEmail()));
securityUser.setAuthority(Authority.CUSTOMER_USER);
securityUser.setId(userId);
return securityUser;
}
}

39
common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ResourceService.java

@ -0,0 +1,39 @@
/**
* 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.dao.resource;
import org.thingsboard.server.common.data.Resource;
import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import java.util.List;
public interface ResourceService {
Resource saveResource(Resource resource);
Resource getResource(TenantId tenantId, ResourceType resourceType, String resourceId);
PageData<Resource> findResourcesByTenantId(TenantId tenantId, PageLink pageLink);
List<Resource> findResourcesByTenantIdResourceType(TenantId tenantId, ResourceType resourceType);
void deleteResource(TenantId tenantId, ResourceType resourceType, String resourceId);
void deleteResourcesByTenantId(TenantId tenantId);
}

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

@ -27,4 +27,6 @@ public class CacheConstants {
public static final String SECURITY_SETTINGS_CACHE = "securitySettings";
public static final String TENANT_PROFILE_CACHE = "tenantProfiles";
public static final String DEVICE_PROFILE_CACHE = "deviceProfiles";
public static final String ATTRIBUTES_CACHE = "attributes";
public static final String TOKEN_OUTDATAGE_TIME_CACHE = "tokensOutdatageTime";
}

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

@ -33,6 +33,8 @@ public class DataConstants {
public static final String IS_EXISTING_ALARM = "isExistingAlarm";
public static final String IS_SEVERITY_UPDATED_ALARM = "isSeverityUpdated";
public static final String IS_CLEARED_ALARM = "isClearedAlarm";
public static final String ALARM_CONDITION_REPEATS = "alarmConditionRepeats";
public static final String ALARM_CONDITION_DURATION = "alarmConditionDuration";
public static final String[] allScopes() {
return new String[]{CLIENT_SCOPE, SHARED_SCOPE, SERVER_SCOPE};

41
common/data/src/main/java/org/thingsboard/server/common/data/Resource.java

@ -0,0 +1,41 @@
/**
* 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.common.data;
import lombok.Data;
import org.thingsboard.server.common.data.id.TenantId;
import java.io.Serializable;
@Data
public class Resource implements HasTenantId, Serializable {
private static final long serialVersionUID = 7379609705527272306L;
private TenantId tenantId;
private ResourceType resourceType;
private String resourceId;
private String value;
@Override
public String toString() {
return "Resource{" +
"tenantId=" + tenantId +
", resourceType=" + resourceType +
", resourceId='" + resourceId + '\'' +
'}';
}
}

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

@ -0,0 +1,20 @@
/**
* 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.common.data;
public enum ResourceType {
LWM2M_MODEL, JKS, PKCS_12
}

4
common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetSearchQuery.java

@ -19,7 +19,7 @@ import lombok.Data;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.EntityRelationsQuery;
import org.thingsboard.server.common.data.relation.EntityTypeFilter;
import org.thingsboard.server.common.data.relation.RelationEntityTypeFilter;
import org.thingsboard.server.common.data.relation.RelationsSearchParameters;
import java.util.Collections;
@ -39,7 +39,7 @@ public class AssetSearchQuery {
EntityRelationsQuery query = new EntityRelationsQuery();
query.setParameters(parameters);
query.setFilters(
Collections.singletonList(new EntityTypeFilter(relationType == null ? EntityRelation.CONTAINS_TYPE : relationType,
Collections.singletonList(new RelationEntityTypeFilter(relationType == null ? EntityRelation.CONTAINS_TYPE : relationType,
Collections.singletonList(EntityType.ASSET))));
return query;
}

4
common/data/src/main/java/org/thingsboard/server/common/data/device/DeviceSearchQuery.java

@ -19,7 +19,7 @@ import lombok.Data;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.EntityRelationsQuery;
import org.thingsboard.server.common.data.relation.EntityTypeFilter;
import org.thingsboard.server.common.data.relation.RelationEntityTypeFilter;
import org.thingsboard.server.common.data.relation.RelationsSearchParameters;
import java.util.Collections;
@ -36,7 +36,7 @@ public class DeviceSearchQuery {
EntityRelationsQuery query = new EntityRelationsQuery();
query.setParameters(parameters);
query.setFilters(
Collections.singletonList(new EntityTypeFilter(relationType == null ? EntityRelation.CONTAINS_TYPE : relationType,
Collections.singletonList(new RelationEntityTypeFilter(relationType == null ? EntityRelation.CONTAINS_TYPE : relationType,
Collections.singletonList(EntityType.DEVICE))));
return query;
}

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

@ -26,7 +26,7 @@ import java.util.List;
@JsonIgnoreProperties(ignoreUnknown = true)
public class AlarmCondition implements Serializable {
private List<KeyFilter> condition;
private List<AlarmConditionFilter> condition;
private AlarmConditionSpec spec;
}

30
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmConditionFilter.java

@ -0,0 +1,30 @@
/**
* 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.common.data.device.profile;
import lombok.Data;
import org.thingsboard.server.common.data.query.EntityKeyValueType;
import org.thingsboard.server.common.data.query.KeyFilterPredicate;
@Data
public class AlarmConditionFilter {
private AlarmConditionFilterKey key;
private EntityKeyValueType valueType;
private Object value;
private KeyFilterPredicate predicate;
}

26
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmConditionFilterKey.java

@ -0,0 +1,26 @@
/**
* 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.common.data.device.profile;
import lombok.Data;
@Data
public class AlarmConditionFilterKey {
private final AlarmConditionKeyType type;
private final String key;
}

23
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmConditionKeyType.java

@ -0,0 +1,23 @@
/**
* 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.common.data.device.profile;
public enum AlarmConditionKeyType {
ATTRIBUTE,
TIME_SERIES,
ENTITY_FIELD,
CONSTANT
}

4
common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeSearchQuery.java

@ -19,7 +19,7 @@ import lombok.Data;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.EntityRelationsQuery;
import org.thingsboard.server.common.data.relation.EntityTypeFilter;
import org.thingsboard.server.common.data.relation.RelationEntityTypeFilter;
import org.thingsboard.server.common.data.relation.RelationsSearchParameters;
import java.util.Collections;
@ -36,7 +36,7 @@ public class EdgeSearchQuery {
EntityRelationsQuery query = new EntityRelationsQuery();
query.setParameters(parameters);
query.setFilters(
Collections.singletonList(new EntityTypeFilter(relationType == null ? EntityRelation.CONTAINS_TYPE : relationType,
Collections.singletonList(new RelationEntityTypeFilter(relationType == null ? EntityRelation.CONTAINS_TYPE : relationType,
Collections.singletonList(EntityType.EDGE))));
return query;
}

4
common/data/src/main/java/org/thingsboard/server/common/data/entityview/EntityViewSearchQuery.java

@ -19,7 +19,7 @@ import lombok.Data;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.EntityRelationsQuery;
import org.thingsboard.server.common.data.relation.EntityTypeFilter;
import org.thingsboard.server.common.data.relation.RelationEntityTypeFilter;
import org.thingsboard.server.common.data.relation.RelationsSearchParameters;
import java.util.Collections;
@ -36,7 +36,7 @@ public class EntityViewSearchQuery {
EntityRelationsQuery query = new EntityRelationsQuery();
query.setParameters(parameters);
query.setFilters(
Collections.singletonList(new EntityTypeFilter(relationType == null ? EntityRelation.CONTAINS_TYPE : relationType,
Collections.singletonList(new RelationEntityTypeFilter(relationType == null ? EntityRelation.CONTAINS_TYPE : relationType,
Collections.singletonList(EntityType.ENTITY_VIEW))));
return query;
}

9
common/data/src/main/java/org/thingsboard/server/common/data/query/AbstractDataQuery.java

@ -29,15 +29,13 @@ public abstract class AbstractDataQuery<T extends EntityDataPageLink> extends En
protected List<EntityKey> entityFields;
@Getter
protected List<EntityKey> latestValues;
@Getter
protected List<KeyFilter> keyFilters;
public AbstractDataQuery() {
super();
}
public AbstractDataQuery(EntityFilter entityFilter) {
super(entityFilter);
public AbstractDataQuery(EntityFilter entityFilter, List<KeyFilter> keyFilters) {
super(entityFilter, keyFilters);
}
public AbstractDataQuery(EntityFilter entityFilter,
@ -45,11 +43,10 @@ public abstract class AbstractDataQuery<T extends EntityDataPageLink> extends En
List<EntityKey> entityFields,
List<EntityKey> latestValues,
List<KeyFilter> keyFilters) {
super(entityFilter);
super(entityFilter, keyFilters);
this.pageLink = pageLink;
this.entityFields = entityFields;
this.latestValues = latestValues;
this.keyFilters = keyFilters;
}
}

4
common/data/src/main/java/org/thingsboard/server/common/data/query/AlarmDataQuery.java

@ -30,8 +30,8 @@ public class AlarmDataQuery extends AbstractDataQuery<AlarmDataPageLink> {
public AlarmDataQuery() {
}
public AlarmDataQuery(EntityFilter entityFilter) {
super(entityFilter);
public AlarmDataQuery(EntityFilter entityFilter, List<KeyFilter> keyFilters) {
super(entityFilter, keyFilters);
}
public AlarmDataQuery(EntityFilter entityFilter, AlarmDataPageLink pageLink, List<EntityKey> entityFields, List<EntityKey> latestValues, List<KeyFilter> keyFilters, List<EntityKey> alarmFields) {

14
common/data/src/main/java/org/thingsboard/server/common/data/query/EntityCountQuery.java

@ -17,14 +17,26 @@ package org.thingsboard.server.common.data.query;
import lombok.Getter;
import java.util.Collections;
import java.util.List;
public class EntityCountQuery {
@Getter
private EntityFilter entityFilter;
public EntityCountQuery() {}
@Getter
protected List<KeyFilter> keyFilters;
public EntityCountQuery() {
}
public EntityCountQuery(EntityFilter entityFilter) {
this(entityFilter, Collections.emptyList());
}
public EntityCountQuery(EntityFilter entityFilter, List<KeyFilter> keyFilters) {
this.entityFilter = entityFilter;
this.keyFilters = keyFilters;
}
}

4
common/data/src/main/java/org/thingsboard/server/common/data/query/EntityDataQuery.java

@ -27,8 +27,8 @@ public class EntityDataQuery extends AbstractDataQuery<EntityDataPageLink> {
public EntityDataQuery() {
}
public EntityDataQuery(EntityFilter entityFilter) {
super(entityFilter);
public EntityDataQuery(EntityFilter entityFilter, List<KeyFilter> keyFilters) {
super(entityFilter, keyFilters);
}
public EntityDataQuery(EntityFilter entityFilter, EntityDataPageLink pageLink, List<EntityKey> entityFields, List<EntityKey> latestValues, List<KeyFilter> keyFilters) {

1
common/data/src/main/java/org/thingsboard/server/common/data/query/EntityFilter.java

@ -29,6 +29,7 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
@JsonSubTypes.Type(value = SingleEntityFilter.class, name = "singleEntity"),
@JsonSubTypes.Type(value = EntityListFilter.class, name = "entityList"),
@JsonSubTypes.Type(value = EntityNameFilter.class, name = "entityName"),
@JsonSubTypes.Type(value = EntityTypeFilter.class, name = "entityType"),
@JsonSubTypes.Type(value = AssetTypeFilter.class, name = "assetType"),
@JsonSubTypes.Type(value = DeviceTypeFilter.class, name = "deviceType"),
@JsonSubTypes.Type(value = EdgeTypeFilter.class, name = "edgeType"),

1
common/data/src/main/java/org/thingsboard/server/common/data/query/EntityFilterType.java

@ -19,6 +19,7 @@ public enum EntityFilterType {
SINGLE_ENTITY("singleEntity"),
ENTITY_LIST("entityList"),
ENTITY_NAME("entityName"),
ENTITY_TYPE("entityType"),
ASSET_TYPE("assetType"),
DEVICE_TYPE("deviceType"),
ENTITY_VIEW_TYPE("entityViewType"),

30
common/data/src/main/java/org/thingsboard/server/common/data/query/EntityTypeFilter.java

@ -0,0 +1,30 @@
/**
* 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.common.data.query;
import lombok.Data;
import org.thingsboard.server.common.data.EntityType;
@Data
public class EntityTypeFilter implements EntityFilter {
@Override
public EntityFilterType getType() {
return EntityFilterType.ENTITY_TYPE;
}
private EntityType entityType;
}

5
common/data/src/main/java/org/thingsboard/server/common/data/query/RelationsQueryFilter.java

@ -18,8 +18,7 @@ package org.thingsboard.server.common.data.query;
import lombok.Data;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.relation.EntitySearchDirection;
import org.thingsboard.server.common.data.relation.EntityTypeFilter;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.relation.RelationEntityTypeFilter;
import java.util.List;
@ -33,7 +32,7 @@ public class RelationsQueryFilter implements EntityFilter {
private EntityId rootEntity;
private EntitySearchDirection direction;
private List<EntityTypeFilter> filters;
private List<RelationEntityTypeFilter> filters;
private int maxLevel;
private boolean fetchLastLevelOnly;

2
common/data/src/main/java/org/thingsboard/server/common/data/relation/EntityRelationsQuery.java

@ -26,6 +26,6 @@ import java.util.List;
public class EntityRelationsQuery {
private RelationsSearchParameters parameters;
private List<EntityTypeFilter> filters;
private List<RelationEntityTypeFilter> filters;
}

2
common/data/src/main/java/org/thingsboard/server/common/data/relation/EntityTypeFilter.java → common/data/src/main/java/org/thingsboard/server/common/data/relation/RelationEntityTypeFilter.java

@ -26,7 +26,7 @@ import java.util.List;
*/
@Data
@AllArgsConstructor
public class EntityTypeFilter {
public class RelationEntityTypeFilter {
private String relationType;

30
common/data/src/main/java/org/thingsboard/server/common/data/security/event/UserAuthDataChangedEvent.java

@ -0,0 +1,30 @@
/**
* 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.common.data.security.event;
import org.thingsboard.server.common.data.id.UserId;
public class UserAuthDataChangedEvent {
private final UserId userId;
public UserAuthDataChangedEvent(UserId userId) {
this.userId = userId;
}
public UserId getUserId() {
return userId;
}
}

2
application/src/main/java/org/thingsboard/server/service/security/model/token/JwtToken.java → common/data/src/main/java/org/thingsboard/server/common/data/security/model/JwtToken.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.security.model.token;
package org.thingsboard.server.common.data.security.model;
import java.io.Serializable;

37
common/data/src/main/java/org/thingsboard/server/common/data/transport/resource/Resource.java

@ -0,0 +1,37 @@
/**
* 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.common.data.transport.resource;
import lombok.Data;
import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.id.TenantId;
@Data
public class Resource implements HasTenantId {
private TenantId tenantId;
private ResourceType resourceType;
private String resourceId;
private String value;
@Override
public String toString() {
return "Resource{" +
"tenantId=" + tenantId +
", resourceType=" + resourceType +
", resourceId='" + resourceId + '\'' +
'}';
}
}

20
common/data/src/main/java/org/thingsboard/server/common/data/transport/resource/ResourceType.java

@ -0,0 +1,20 @@
/**
* 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.common.data.transport.resource;
public enum ResourceType {
LWM2M_MODEL, JKS, PKCS_12
}

2
common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java

@ -120,7 +120,7 @@ public final class TbMsg implements Serializable {
private TbMsg(String queueName, UUID id, long ts, String type, EntityId originator, TbMsgMetaData metaData, TbMsgDataType dataType, String data,
RuleChainId ruleChainId, RuleNodeId ruleNodeId, int ruleNodeExecCounter, TbMsgCallback callback) {
this.id = id;
this.queueName = queueName;
this.queueName = queueName != null ? queueName : ServiceQueue.MAIN;
if (ts > 0) {
this.ts = ts;
} else {

2
common/queue/src/main/java/org/thingsboard/server/queue/util/TbLwM2mTransportComponent.java

@ -21,6 +21,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' ) || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true')")
@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true') || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true')")
public @interface TbLwM2mTransportComponent {
}

29
common/queue/src/main/proto/queue.proto

@ -201,6 +201,17 @@ message LwM2MResponseMsg {
LwM2MRegistrationResponseMsg registrationMsg = 1;
}
message GetResourceRequestMsg {
int64 tenantIdMSB = 1;
int64 tenantIdLSB = 2;
string resourceType = 3;
string resourceId = 4;
}
message GetResourceResponseMsg {
bytes resource = 1;
}
message ValidateDeviceLwM2MCredentialsRequestMsg {
string credentialsId = 1;
}
@ -242,6 +253,20 @@ message EntityDeleteMsg {
int64 entityIdLSB = 3;
}
message ResourceUpdateMsg {
int64 tenantIdMSB = 1;
int64 tenantIdLSB = 2;
string resourceType = 3;
string resourceId = 4;
}
message ResourceDeleteMsg {
int64 tenantIdMSB = 1;
int64 tenantIdLSB = 2;
string resourceType = 3;
string resourceId = 4;
}
message SessionCloseNotificationProto {
string message = 1;
}
@ -540,6 +565,7 @@ message TransportApiRequestMsg {
ValidateBasicMqttCredRequestMsg validateBasicMqttCredRequestMsg = 6;
ProvisionDeviceRequestMsg provisionDeviceRequestMsg = 7;
ValidateDeviceLwM2MCredentialsRequestMsg validateDeviceLwM2MCredentialsRequestMsg = 8;
GetResourceRequestMsg resourceRequestMsg = 9;
}
/* Response from ThingsBoard Core Service to Transport Service */
@ -549,6 +575,7 @@ message TransportApiResponseMsg {
GetEntityProfileResponseMsg entityProfileResponseMsg = 3;
ProvisionDeviceResponseMsg provisionDeviceResponseMsg = 4;
LwM2MResponseMsg lwM2MResponseMsg = 6;
GetResourceResponseMsg resourceResponseMsg = 7;
}
/* Messages that are handled by ThingsBoard Core Service */
@ -595,6 +622,8 @@ message ToTransportMsg {
EntityDeleteMsg entityDeleteMsg = 9;
ProvisionDeviceResponseMsg provisionResponse = 10;
ToTransportUpdateCredentialsProto toTransportUpdateCredentialsNotification = 11;
ResourceUpdateMsg resourceUpdateMsg = 12;
ResourceDeleteMsg resourceDeleteMsg = 13;
}
message UsageStatsKVProto{

18
common/transport/http/src/main/java/org/thingsboard/server/transport/http/HttpTransportContext.java

@ -17,9 +17,13 @@ package org.thingsboard.server.transport.http;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.coyote.ProtocolHandler;
import org.apache.coyote.http11.Http11NioProtocol;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.transport.TransportContext;
@ -37,4 +41,18 @@ public class HttpTransportContext extends TransportContext {
@Value("${transport.http.request_timeout}")
private long defaultTimeout;
@Getter
@Value("${transport.http.max_request_timeout}")
private long maxRequestTimeout;
@Bean
public TomcatConnectorCustomizer tomcatAsyncTimeoutConnectorCustomizer() {
return connector -> {
ProtocolHandler handler = connector.getProtocolHandler();
if (handler instanceof Http11NioProtocol) {
log.trace("Setting async max request timeout {}", maxRequestTimeout);
connector.setAsyncTimeout(maxRequestTimeout);
}
};
}
}

18
common/transport/lwm2m/pom.xml

@ -14,24 +14,6 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<!--
Copyright © 2016-2020 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

3
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerConfiguration.java

@ -17,7 +17,6 @@ package org.thingsboard.server.transport.lwm2m.bootstrap;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
import org.eclipse.leshan.core.model.StaticModel;
import org.eclipse.leshan.core.util.Hex;
import org.eclipse.leshan.server.bootstrap.BootstrapSessionManager;
import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServer;
@ -94,7 +93,7 @@ public class LwM2MTransportBootstrapServerConfiguration {
builder.setCoapConfig(getCoapConfig(bootstrapPortNoSec, bootstrapSecurePort));
/** Define model provider (Create Models )*/
builder.setModel(new StaticModel(contextS.getLwM2MTransportConfigServer().getModelsValue()));
// builder.setModel(new StaticModel(contextS.getLwM2MTransportConfigServer().getModelsValueCommon()));
/** Create credentials */
this.setServerWithCredentials(builder);

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

@ -18,7 +18,6 @@ package org.thingsboard.server.transport.lwm2m.server;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.observation.Observation;
import org.eclipse.leshan.core.response.ObserveResponse;
import org.eclipse.leshan.server.californium.LeshanServer;
import org.eclipse.leshan.server.observation.ObservationListener;
import org.eclipse.leshan.server.queue.PresenceListener;
import org.eclipse.leshan.server.registration.Registration;
@ -30,11 +29,9 @@ import java.util.Collection;
@Slf4j
public class LwM2mServerListener {
private final LeshanServer lhServer;
private final LwM2mTransportServiceImpl service;
public LwM2mServerListener(LeshanServer lhServer, LwM2mTransportServiceImpl service) {
this.lhServer = lhServer;
public LwM2mServerListener(LwM2mTransportServiceImpl service) {
this.service = service;
}
@ -45,7 +42,7 @@ public class LwM2mServerListener {
@Override
public void registered(Registration registration, Registration previousReg,
Collection<Observation> previousObservations) {
service.onRegistered(lhServer, registration, previousObservations);
service.onRegistered(registration, previousObservations);
}
/**
@ -54,7 +51,7 @@ public class LwM2mServerListener {
@Override
public void updated(RegistrationUpdate update, Registration updatedRegistration,
Registration previousRegistration) {
service.updatedReg(lhServer, updatedRegistration);
service.updatedReg(updatedRegistration);
}
/**
@ -63,7 +60,7 @@ public class LwM2mServerListener {
@Override
public void unregistered(Registration registration, Collection<Observation> observations, boolean expired,
Registration newReg) {
service.unReg(lhServer, registration, observations);
service.unReg(registration, observations);
}
};

48
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportContextServer.java

@ -16,13 +16,13 @@
package org.thingsboard.server.transport.lwm2m.server;
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* <p>
* 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
*
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
*
* <p>
* 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.
@ -34,16 +34,27 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.model.DDFFileParser;
import org.eclipse.leshan.core.model.DefaultDDFFileValidator;
import org.eclipse.leshan.core.model.InvalidDDFFileException;
import org.eclipse.leshan.core.model.ObjectModel;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.transport.TransportContext;
import org.thingsboard.server.common.transport.TransportResourceCache;
import org.thingsboard.server.common.transport.TransportService;
import org.thingsboard.server.common.transport.TransportServiceCallback;
import org.thingsboard.server.common.transport.adaptor.AdaptorException;
import org.thingsboard.server.common.transport.lwm2m.LwM2MTransportConfigServer;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg;
import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg;
import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto;
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg;
import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
import org.thingsboard.server.transport.lwm2m.server.adaptors.LwM2MJsonAdaptor;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_TELEMETRY;
@Slf4j
@ -56,12 +67,16 @@ public class LwM2mTransportContextServer extends TransportContext {
private final TransportService transportService;
private final TransportResourceCache transportResourceCache;
@Getter
private final LwM2MJsonAdaptor adaptor;
public LwM2mTransportContextServer(LwM2MTransportConfigServer lwM2MTransportConfigServer, TransportService transportService, LwM2MJsonAdaptor adaptor) {
public LwM2mTransportContextServer(LwM2MTransportConfigServer lwM2MTransportConfigServer, TransportService transportService, TransportResourceCache transportResourceCache, LwM2MJsonAdaptor adaptor) {
this.lwM2MTransportConfigServer = lwM2MTransportConfigServer;
this.transportService = transportService;
this.transportResourceCache = transportResourceCache;
this.adaptor = adaptor;
}
@ -69,6 +84,10 @@ public class LwM2mTransportContextServer extends TransportContext {
return this.lwM2MTransportConfigServer;
}
public TransportResourceCache getTransportResourceCache() {
return this.transportResourceCache;
}
/**
* Sent to Thingsboard Attribute || Telemetry
*
@ -89,14 +108,14 @@ public class LwM2mTransportContextServer extends TransportContext {
};
}
public void sentParametersOnThingsboard(JsonElement msg, String topicName, TransportProtos.SessionInfoProto sessionInfo) {
public void sentParametersOnThingsboard(JsonElement msg, String topicName, SessionInfoProto sessionInfo) {
try {
if (topicName.equals(LwM2mTransportHandler.DEVICE_ATTRIBUTES_TOPIC)) {
TransportProtos.PostAttributeMsg postAttributeMsg = adaptor.convertToPostAttributes(msg);
PostAttributeMsg postAttributeMsg = adaptor.convertToPostAttributes(msg);
TransportServiceCallback call = this.getPubAckCallbackSentAttrTelemetry(postAttributeMsg);
transportService.process(sessionInfo, postAttributeMsg, this.getPubAckCallbackSentAttrTelemetry(call));
} else if (topicName.equals(LwM2mTransportHandler.DEVICE_TELEMETRY_TOPIC)) {
TransportProtos.PostTelemetryMsg postTelemetryMsg = adaptor.convertToPostTelemetry(msg);
PostTelemetryMsg postTelemetryMsg = adaptor.convertToPostTelemetry(msg);
TransportServiceCallback call = this.getPubAckCallbackSentAttrTelemetry(postTelemetryMsg);
transportService.process(sessionInfo, postTelemetryMsg, this.getPubAckCallbackSentAttrTelemetry(call));
}
@ -115,8 +134,8 @@ public class LwM2mTransportContextServer extends TransportContext {
/**
* @return - sessionInfo after access connect client
*/
public TransportProtos.SessionInfoProto getValidateSessionInfo(TransportProtos.ValidateDeviceCredentialsResponseMsg msg, long mostSignificantBits, long leastSignificantBits) {
return TransportProtos.SessionInfoProto.newBuilder()
public SessionInfoProto getValidateSessionInfo(ValidateDeviceCredentialsResponseMsg msg, long mostSignificantBits, long leastSignificantBits) {
return SessionInfoProto.newBuilder()
.setNodeId(this.getNodeId())
.setSessionIdMSB(mostSignificantBits)
.setSessionIdLSB(leastSignificantBits)
@ -131,4 +150,13 @@ public class LwM2mTransportContextServer extends TransportContext {
.build();
}
public ObjectModel parseFromXmlToObjectModel(byte[] xmlByte, String streamName, DefaultDDFFileValidator ddfValidator) {
try {
DDFFileParser ddfFileParser = new DDFFileParser(ddfValidator);
return ddfFileParser.parseEx(new ByteArrayInputStream(xmlByte), streamName).get(0);
} catch (IOException | InvalidDDFFileException e) {
log.error("Could not parse the XML file [{}]", streamName, e);
return null;
}
}
}

13
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportHandler.java

@ -35,9 +35,10 @@ import org.eclipse.leshan.server.californium.LeshanServerBuilder;
import org.nustaq.serialization.FSTConfiguration;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.transport.TransportServiceCallback;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientProfile;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientProfile;
import java.io.File;
import java.io.IOException;
@ -188,8 +189,9 @@ public class LwM2mTransportHandler {
return null;
}
public static LwM2mClientProfile getNewProfileParameters(JsonObject profilesConfigData) {
public static LwM2mClientProfile getNewProfileParameters(JsonObject profilesConfigData, TenantId tenantId) {
LwM2mClientProfile lwM2MClientProfile = new LwM2mClientProfile();
lwM2MClientProfile.setTenantId(tenantId);
lwM2MClientProfile.setPostClientLwM2mSettings(profilesConfigData.get(CLIENT_LWM2M_SETTINGS).getAsJsonObject());
lwM2MClientProfile.setPostKeyNameProfile(profilesConfigData.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(KEY_NAME).getAsJsonObject());
lwM2MClientProfile.setPostAttributeProfile(profilesConfigData.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(ATTRIBUTE).getAsJsonArray());
@ -221,7 +223,7 @@ public class LwM2mTransportHandler {
ObjectMapper mapper = new ObjectMapper();
String profileStr = mapper.writeValueAsString(profile);
JsonObject profileJson = (profileStr != null) ? validateJson(profileStr) : null;
return (getValidateCredentialsBodyFromThingsboard(profileJson)) ? LwM2mTransportHandler.getNewProfileParameters(profileJson) : null;
return (getValidateCredentialsBodyFromThingsboard(profileJson)) ? LwM2mTransportHandler.getNewProfileParameters(profileJson, deviceProfile.getTenantId()) : null;
} catch (IOException e) {
log.error("", e);
}
@ -244,11 +246,6 @@ public class LwM2mTransportHandler {
return null;
}
public static boolean getClientUpdateValueAfterConnect (LwM2mClientProfile profile) {
return profile.getPostClientLwM2mSettings().getAsJsonObject().has("clientUpdateValueAfterConnect") &&
profile.getPostClientLwM2mSettings().getAsJsonObject().get("clientUpdateValueAfterConnect").getAsBoolean();
}
public static boolean getClientOnlyObserveAfterConnect (LwM2mClientProfile profile) {
return profile.getPostClientLwM2mSettings().getAsJsonObject().has("clientOnlyObserveAfterConnect") &&
profile.getPostClientLwM2mSettings().getAsJsonObject().get("clientOnlyObserveAfterConnect").getAsBoolean();

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

Loading…
Cancel
Save