Commit a3cb9724e3a99b3db6b86c5aeffeafc2aecdc822

Authored by Andrew Shvayka
Committed by GitHub
2 parents 3716e283 ae2ca8af

Merge pull request #4191 from thingsboard/master

Merge master to develop 3.3
Showing 100 changed files with 1596 additions and 488 deletions

Too many changes to show.

To preserve performance only 100 of 198 files are displayed.

@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>3.3.0-SNAPSHOT</version> 23 + <version>3.2.2-SNAPSHOT</version>
24 <artifactId>thingsboard</artifactId> 24 <artifactId>thingsboard</artifactId>
25 </parent> 25 </parent>
26 <artifactId>application</artifactId> 26 <artifactId>application</artifactId>
@@ -47,7 +47,7 @@ @@ -47,7 +47,7 @@
47 "resources": [], 47 "resources": [],
48 "templateHtml": "<tb-timeseries-table-widget \n [ctx]=\"ctx\">\n</tb-timeseries-table-widget>", 48 "templateHtml": "<tb-timeseries-table-widget \n [ctx]=\"ctx\">\n</tb-timeseries-table-widget>",
49 "templateCss": "", 49 "templateCss": "",
50 - "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}", 50 + "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}",
51 "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}", 51 "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}",
52 "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}", 52 "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}",
53 "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\"}" 53 "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 @@ @@ -134,4 +134,4 @@
134 } 134 }
135 } 135 }
136 ] 136 ]
137 -}  
  137 +}
@@ -84,11 +84,12 @@ BEGIN @@ -84,11 +84,12 @@ BEGIN
84 END IF; 84 END IF;
85 END IF; 85 END IF;
86 END IF; 86 END IF;
87 - END IF;  
88 - IF partition_to_delete IS NOT NULL THEN  
89 - RAISE NOTICE 'Partition to delete by max ttl: %', partition_to_delete;  
90 - EXECUTE format('DROP TABLE %I', partition_to_delete);  
91 - deleted := deleted + 1; 87 + IF partition_to_delete IS NOT NULL THEN
  88 + RAISE NOTICE 'Partition to delete by max ttl: %', partition_to_delete;
  89 + EXECUTE format('DROP TABLE IF EXISTS %I', partition_to_delete);
  90 + partition_to_delete := NULL;
  91 + deleted := deleted + 1;
  92 + END IF;
92 END IF; 93 END IF;
93 END LOOP; 94 END LOOP;
94 END IF; 95 END IF;
@@ -37,6 +37,7 @@ import org.thingsboard.server.common.data.Customer; @@ -37,6 +37,7 @@ import org.thingsboard.server.common.data.Customer;
37 import org.thingsboard.server.common.data.DataConstants; 37 import org.thingsboard.server.common.data.DataConstants;
38 import org.thingsboard.server.common.data.Device; 38 import org.thingsboard.server.common.data.Device;
39 import org.thingsboard.server.common.data.DeviceProfile; 39 import org.thingsboard.server.common.data.DeviceProfile;
  40 +import org.thingsboard.server.common.data.EntityType;
40 import org.thingsboard.server.common.data.TenantProfile; 41 import org.thingsboard.server.common.data.TenantProfile;
41 import org.thingsboard.server.common.data.alarm.Alarm; 42 import org.thingsboard.server.common.data.alarm.Alarm;
42 import org.thingsboard.server.common.data.asset.Asset; 43 import org.thingsboard.server.common.data.asset.Asset;
@@ -278,7 +279,21 @@ class DefaultTbContext implements TbContext { @@ -278,7 +279,21 @@ class DefaultTbContext implements TbContext {
278 } 279 }
279 280
280 public TbMsg deviceCreatedMsg(Device device, RuleNodeId ruleNodeId) { 281 public TbMsg deviceCreatedMsg(Device device, RuleNodeId ruleNodeId) {
281 - return entityActionMsg(device, device.getId(), ruleNodeId, DataConstants.ENTITY_CREATED); 282 + RuleChainId ruleChainId = null;
  283 + String queueName = ServiceQueue.MAIN;
  284 + if (device.getDeviceProfileId() != null) {
  285 + DeviceProfile deviceProfile = mainCtx.getDeviceProfileCache().find(device.getDeviceProfileId());
  286 + if (deviceProfile == null) {
  287 + log.warn("[{}] Device profile is null!", device.getDeviceProfileId());
  288 + ruleChainId = null;
  289 + queueName = ServiceQueue.MAIN;
  290 + } else {
  291 + ruleChainId = deviceProfile.getDefaultRuleChainId();
  292 + String defaultQueueName = deviceProfile.getDefaultQueueName();
  293 + queueName = defaultQueueName != null ? defaultQueueName : ServiceQueue.MAIN;
  294 + }
  295 + }
  296 + return entityActionMsg(device, device.getId(), ruleNodeId, DataConstants.ENTITY_CREATED, queueName, ruleChainId);
282 } 297 }
283 298
284 public TbMsg assetCreatedMsg(Asset asset, RuleNodeId ruleNodeId) { 299 public TbMsg assetCreatedMsg(Asset asset, RuleNodeId ruleNodeId) {
@@ -286,12 +301,31 @@ class DefaultTbContext implements TbContext { @@ -286,12 +301,31 @@ class DefaultTbContext implements TbContext {
286 } 301 }
287 302
288 public TbMsg alarmActionMsg(Alarm alarm, RuleNodeId ruleNodeId, String action) { 303 public TbMsg alarmActionMsg(Alarm alarm, RuleNodeId ruleNodeId, String action) {
289 - return entityActionMsg(alarm, alarm.getId(), ruleNodeId, action); 304 + RuleChainId ruleChainId = null;
  305 + String queueName = ServiceQueue.MAIN;
  306 + if (EntityType.DEVICE.equals(alarm.getOriginator().getEntityType())) {
  307 + DeviceId deviceId = new DeviceId(alarm.getOriginator().getId());
  308 + DeviceProfile deviceProfile = mainCtx.getDeviceProfileCache().get(getTenantId(), deviceId);
  309 + if (deviceProfile == null) {
  310 + log.warn("[{}] Device profile is null!", deviceId);
  311 + ruleChainId = null;
  312 + queueName = ServiceQueue.MAIN;
  313 + } else {
  314 + ruleChainId = deviceProfile.getDefaultRuleChainId();
  315 + String defaultQueueName = deviceProfile.getDefaultQueueName();
  316 + queueName = defaultQueueName != null ? defaultQueueName : ServiceQueue.MAIN;
  317 + }
  318 + }
  319 + return entityActionMsg(alarm, alarm.getId(), ruleNodeId, action, queueName, ruleChainId);
290 } 320 }
291 321
292 public <E, I extends EntityId> TbMsg entityActionMsg(E entity, I id, RuleNodeId ruleNodeId, String action) { 322 public <E, I extends EntityId> TbMsg entityActionMsg(E entity, I id, RuleNodeId ruleNodeId, String action) {
  323 + return entityActionMsg(entity, id, ruleNodeId, action, ServiceQueue.MAIN, null);
  324 + }
  325 +
  326 + public <E, I extends EntityId> TbMsg entityActionMsg(E entity, I id, RuleNodeId ruleNodeId, String action, String queueName, RuleChainId ruleChainId) {
293 try { 327 try {
294 - return TbMsg.newMsg(action, id, getActionMetaData(ruleNodeId), mapper.writeValueAsString(mapper.valueToTree(entity))); 328 + return TbMsg.newMsg(queueName, action, id, getActionMetaData(ruleNodeId), mapper.writeValueAsString(mapper.valueToTree(entity)), ruleChainId, null);
295 } catch (JsonProcessingException | IllegalArgumentException e) { 329 } catch (JsonProcessingException | IllegalArgumentException e) {
296 throw new RuntimeException("Failed to process " + id.getEntityType().name().toLowerCase() + " " + action + " msg: " + e); 330 throw new RuntimeException("Failed to process " + id.getEntityType().name().toLowerCase() + " " + action + " msg: " + e);
297 } 331 }
@@ -215,6 +215,7 @@ public class AuthController extends BaseController { @@ -215,6 +215,7 @@ public class AuthController extends BaseController {
215 User user = userService.findUserById(TenantId.SYS_TENANT_ID, credentials.getUserId()); 215 User user = userService.findUserById(TenantId.SYS_TENANT_ID, credentials.getUserId());
216 UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.USER_NAME, user.getEmail()); 216 UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.USER_NAME, user.getEmail());
217 SecurityUser securityUser = new SecurityUser(user, credentials.isEnabled(), principal); 217 SecurityUser securityUser = new SecurityUser(user, credentials.isEnabled(), principal);
  218 + userService.setUserCredentialsEnabled(user.getTenantId(), user.getId(), true);
218 String baseUrl = systemSecurityService.getBaseUrl(user.getTenantId(), user.getCustomerId(), request); 219 String baseUrl = systemSecurityService.getBaseUrl(user.getTenantId(), user.getCustomerId(), request);
219 String loginUrl = String.format("%s/login", baseUrl); 220 String loginUrl = String.format("%s/login", baseUrl);
220 String email = user.getEmail(); 221 String email = user.getEmail();
@@ -94,12 +94,24 @@ public class UserController extends BaseController { @@ -94,12 +94,24 @@ public class UserController extends BaseController {
94 processDashboardIdFromAdditionalInfo((ObjectNode) user.getAdditionalInfo(), DEFAULT_DASHBOARD); 94 processDashboardIdFromAdditionalInfo((ObjectNode) user.getAdditionalInfo(), DEFAULT_DASHBOARD);
95 processDashboardIdFromAdditionalInfo((ObjectNode) user.getAdditionalInfo(), HOME_DASHBOARD); 95 processDashboardIdFromAdditionalInfo((ObjectNode) user.getAdditionalInfo(), HOME_DASHBOARD);
96 } 96 }
  97 + UserCredentials userCredentials = userService.findUserCredentialsByUserId(user.getTenantId(), user.getId());
  98 + if(userCredentials.isEnabled()) {
  99 + addUserCredentialsEnabled((ObjectNode) user.getAdditionalInfo());
  100 + }
97 return user; 101 return user;
98 } catch (Exception e) { 102 } catch (Exception e) {
99 throw handleException(e); 103 throw handleException(e);
100 } 104 }
101 } 105 }
102 106
  107 + private void addUserCredentialsEnabled(ObjectNode additionalInfo) {
  108 + if(!additionalInfo.isNull()) {
  109 + if(!additionalInfo.has("userCredentialsEnabled")) {
  110 + additionalInfo.put("userCredentialsEnabled", true);
  111 + }
  112 + }
  113 + }
  114 +
103 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") 115 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
104 @RequestMapping(value = "/user/tokenAccessEnabled", method = RequestMethod.GET) 116 @RequestMapping(value = "/user/tokenAccessEnabled", method = RequestMethod.GET)
105 @ResponseBody 117 @ResponseBody
@@ -193,13 +205,13 @@ public class UserController extends BaseController { @@ -193,13 +205,13 @@ public class UserController extends BaseController {
193 user.getId(), user); 205 user.getId(), user);
194 206
195 UserCredentials userCredentials = userService.findUserCredentialsByUserId(getCurrentUser().getTenantId(), user.getId()); 207 UserCredentials userCredentials = userService.findUserCredentialsByUserId(getCurrentUser().getTenantId(), user.getId());
196 - if (!userCredentials.isEnabled()) { 208 + if (!userCredentials.isEnabled() && userCredentials.getActivateToken() != null) {
197 String baseUrl = systemSecurityService.getBaseUrl(getTenantId(), getCurrentUser().getCustomerId(), request); 209 String baseUrl = systemSecurityService.getBaseUrl(getTenantId(), getCurrentUser().getCustomerId(), request);
198 String activateUrl = String.format(ACTIVATE_URL_PATTERN, baseUrl, 210 String activateUrl = String.format(ACTIVATE_URL_PATTERN, baseUrl,
199 userCredentials.getActivateToken()); 211 userCredentials.getActivateToken());
200 mailService.sendActivationEmail(activateUrl, email); 212 mailService.sendActivationEmail(activateUrl, email);
201 } else { 213 } else {
202 - throw new ThingsboardException("User is already active!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); 214 + throw new ThingsboardException("User is already activated!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
203 } 215 }
204 } catch (Exception e) { 216 } catch (Exception e) {
205 throw handleException(e); 217 throw handleException(e);
@@ -218,13 +230,13 @@ public class UserController extends BaseController { @@ -218,13 +230,13 @@ public class UserController extends BaseController {
218 User user = checkUserId(userId, Operation.READ); 230 User user = checkUserId(userId, Operation.READ);
219 SecurityUser authUser = getCurrentUser(); 231 SecurityUser authUser = getCurrentUser();
220 UserCredentials userCredentials = userService.findUserCredentialsByUserId(authUser.getTenantId(), user.getId()); 232 UserCredentials userCredentials = userService.findUserCredentialsByUserId(authUser.getTenantId(), user.getId());
221 - if (!userCredentials.isEnabled()) { 233 + if (!userCredentials.isEnabled() && userCredentials.getActivateToken() != null) {
222 String baseUrl = systemSecurityService.getBaseUrl(getTenantId(), getCurrentUser().getCustomerId(), request); 234 String baseUrl = systemSecurityService.getBaseUrl(getTenantId(), getCurrentUser().getCustomerId(), request);
223 String activateUrl = String.format(ACTIVATE_URL_PATTERN, baseUrl, 235 String activateUrl = String.format(ACTIVATE_URL_PATTERN, baseUrl,
224 userCredentials.getActivateToken()); 236 userCredentials.getActivateToken());
225 return activateUrl; 237 return activateUrl;
226 } else { 238 } else {
227 - throw new ThingsboardException("User is already active!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); 239 + throw new ThingsboardException("User is already activated!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
228 } 240 }
229 } catch (Exception e) { 241 } catch (Exception e) {
230 throw handleException(e); 242 throw handleException(e);
@@ -186,7 +186,11 @@ public class ThingsboardInstallService { @@ -186,7 +186,11 @@ public class ThingsboardInstallService {
186 log.info("Upgrading ThingsBoard from version 3.2.0 to 3.2.1 ..."); 186 log.info("Upgrading ThingsBoard from version 3.2.0 to 3.2.1 ...");
187 databaseEntitiesUpgradeService.upgradeDatabase("3.2.0"); 187 databaseEntitiesUpgradeService.upgradeDatabase("3.2.0");
188 case "3.2.1": 188 case "3.2.1":
189 - log.info("Upgrading ThingsBoard from version 3.2.1 to 3.3.0 ..."); 189 + log.info("Upgrading ThingsBoard from version 3.2.1 to 3.2.2 ...");
  190 + if (databaseTsUpgradeService != null) {
  191 + databaseTsUpgradeService.upgradeDatabase("3.2.1");
  192 + }
  193 +
190 log.info("Updating system data..."); 194 log.info("Updating system data...");
191 systemDataLoaderService.updateSystemWidgets(); 195 systemDataLoaderService.updateSystemWidgets();
192 break; 196 break;
@@ -50,6 +50,7 @@ public class CassandraTsDatabaseUpgradeService extends AbstractCassandraDatabase @@ -50,6 +50,7 @@ public class CassandraTsDatabaseUpgradeService extends AbstractCassandraDatabase
50 break; 50 break;
51 case "2.5.0": 51 case "2.5.0":
52 case "3.1.1": 52 case "3.1.1":
  53 + case "3.2.1":
53 break; 54 break;
54 default: 55 default:
55 throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion); 56 throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion);
@@ -36,6 +36,9 @@ import org.thingsboard.server.common.data.TenantProfile; @@ -36,6 +36,9 @@ import org.thingsboard.server.common.data.TenantProfile;
36 import org.thingsboard.server.common.data.User; 36 import org.thingsboard.server.common.data.User;
37 import org.thingsboard.server.common.data.alarm.AlarmSeverity; 37 import org.thingsboard.server.common.data.alarm.AlarmSeverity;
38 import org.thingsboard.server.common.data.device.profile.AlarmCondition; 38 import org.thingsboard.server.common.data.device.profile.AlarmCondition;
  39 +import org.thingsboard.server.common.data.device.profile.AlarmConditionFilter;
  40 +import org.thingsboard.server.common.data.device.profile.AlarmConditionFilterKey;
  41 +import org.thingsboard.server.common.data.device.profile.AlarmConditionKeyType;
39 import org.thingsboard.server.common.data.device.profile.AlarmRule; 42 import org.thingsboard.server.common.data.device.profile.AlarmRule;
40 import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; 43 import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration;
41 import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration; 44 import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration;
@@ -290,16 +293,16 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { @@ -290,16 +293,16 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
290 AlarmCondition temperatureCondition = new AlarmCondition(); 293 AlarmCondition temperatureCondition = new AlarmCondition();
291 temperatureCondition.setSpec(new SimpleAlarmConditionSpec()); 294 temperatureCondition.setSpec(new SimpleAlarmConditionSpec());
292 295
293 - KeyFilter temperatureAlarmFlagAttributeFilter = new KeyFilter();  
294 - temperatureAlarmFlagAttributeFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "temperatureAlarmFlag")); 296 + AlarmConditionFilter temperatureAlarmFlagAttributeFilter = new AlarmConditionFilter();
  297 + temperatureAlarmFlagAttributeFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.ATTRIBUTE, "temperatureAlarmFlag"));
295 temperatureAlarmFlagAttributeFilter.setValueType(EntityKeyValueType.BOOLEAN); 298 temperatureAlarmFlagAttributeFilter.setValueType(EntityKeyValueType.BOOLEAN);
296 BooleanFilterPredicate temperatureAlarmFlagAttributePredicate = new BooleanFilterPredicate(); 299 BooleanFilterPredicate temperatureAlarmFlagAttributePredicate = new BooleanFilterPredicate();
297 temperatureAlarmFlagAttributePredicate.setOperation(BooleanFilterPredicate.BooleanOperation.EQUAL); 300 temperatureAlarmFlagAttributePredicate.setOperation(BooleanFilterPredicate.BooleanOperation.EQUAL);
298 temperatureAlarmFlagAttributePredicate.setValue(new FilterPredicateValue<>(Boolean.TRUE)); 301 temperatureAlarmFlagAttributePredicate.setValue(new FilterPredicateValue<>(Boolean.TRUE));
299 temperatureAlarmFlagAttributeFilter.setPredicate(temperatureAlarmFlagAttributePredicate); 302 temperatureAlarmFlagAttributeFilter.setPredicate(temperatureAlarmFlagAttributePredicate);
300 303
301 - KeyFilter temperatureTimeseriesFilter = new KeyFilter();  
302 - temperatureTimeseriesFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "temperature")); 304 + AlarmConditionFilter temperatureTimeseriesFilter = new AlarmConditionFilter();
  305 + temperatureTimeseriesFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, "temperature"));
303 temperatureTimeseriesFilter.setValueType(EntityKeyValueType.NUMERIC); 306 temperatureTimeseriesFilter.setValueType(EntityKeyValueType.NUMERIC);
304 NumericFilterPredicate temperatureTimeseriesFilterPredicate = new NumericFilterPredicate(); 307 NumericFilterPredicate temperatureTimeseriesFilterPredicate = new NumericFilterPredicate();
305 temperatureTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER); 308 temperatureTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
@@ -317,8 +320,8 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { @@ -317,8 +320,8 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
317 AlarmCondition clearTemperatureCondition = new AlarmCondition(); 320 AlarmCondition clearTemperatureCondition = new AlarmCondition();
318 clearTemperatureCondition.setSpec(new SimpleAlarmConditionSpec()); 321 clearTemperatureCondition.setSpec(new SimpleAlarmConditionSpec());
319 322
320 - KeyFilter clearTemperatureTimeseriesFilter = new KeyFilter();  
321 - clearTemperatureTimeseriesFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "temperature")); 323 + AlarmConditionFilter clearTemperatureTimeseriesFilter = new AlarmConditionFilter();
  324 + clearTemperatureTimeseriesFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, "temperature"));
322 clearTemperatureTimeseriesFilter.setValueType(EntityKeyValueType.NUMERIC); 325 clearTemperatureTimeseriesFilter.setValueType(EntityKeyValueType.NUMERIC);
323 NumericFilterPredicate clearTemperatureTimeseriesFilterPredicate = new NumericFilterPredicate(); 326 NumericFilterPredicate clearTemperatureTimeseriesFilterPredicate = new NumericFilterPredicate();
324 clearTemperatureTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.LESS_OR_EQUAL); 327 clearTemperatureTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.LESS_OR_EQUAL);
@@ -340,16 +343,16 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { @@ -340,16 +343,16 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
340 AlarmCondition humidityCondition = new AlarmCondition(); 343 AlarmCondition humidityCondition = new AlarmCondition();
341 humidityCondition.setSpec(new SimpleAlarmConditionSpec()); 344 humidityCondition.setSpec(new SimpleAlarmConditionSpec());
342 345
343 - KeyFilter humidityAlarmFlagAttributeFilter = new KeyFilter();  
344 - humidityAlarmFlagAttributeFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "humidityAlarmFlag")); 346 + AlarmConditionFilter humidityAlarmFlagAttributeFilter = new AlarmConditionFilter();
  347 + humidityAlarmFlagAttributeFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.ATTRIBUTE, "humidityAlarmFlag"));
345 humidityAlarmFlagAttributeFilter.setValueType(EntityKeyValueType.BOOLEAN); 348 humidityAlarmFlagAttributeFilter.setValueType(EntityKeyValueType.BOOLEAN);
346 BooleanFilterPredicate humidityAlarmFlagAttributePredicate = new BooleanFilterPredicate(); 349 BooleanFilterPredicate humidityAlarmFlagAttributePredicate = new BooleanFilterPredicate();
347 humidityAlarmFlagAttributePredicate.setOperation(BooleanFilterPredicate.BooleanOperation.EQUAL); 350 humidityAlarmFlagAttributePredicate.setOperation(BooleanFilterPredicate.BooleanOperation.EQUAL);
348 humidityAlarmFlagAttributePredicate.setValue(new FilterPredicateValue<>(Boolean.TRUE)); 351 humidityAlarmFlagAttributePredicate.setValue(new FilterPredicateValue<>(Boolean.TRUE));
349 humidityAlarmFlagAttributeFilter.setPredicate(humidityAlarmFlagAttributePredicate); 352 humidityAlarmFlagAttributeFilter.setPredicate(humidityAlarmFlagAttributePredicate);
350 353
351 - KeyFilter humidityTimeseriesFilter = new KeyFilter();  
352 - humidityTimeseriesFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "humidity")); 354 + AlarmConditionFilter humidityTimeseriesFilter = new AlarmConditionFilter();
  355 + humidityTimeseriesFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, "humidity"));
353 humidityTimeseriesFilter.setValueType(EntityKeyValueType.NUMERIC); 356 humidityTimeseriesFilter.setValueType(EntityKeyValueType.NUMERIC);
354 NumericFilterPredicate humidityTimeseriesFilterPredicate = new NumericFilterPredicate(); 357 NumericFilterPredicate humidityTimeseriesFilterPredicate = new NumericFilterPredicate();
355 humidityTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.LESS); 358 humidityTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.LESS);
@@ -368,8 +371,8 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { @@ -368,8 +371,8 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
368 AlarmCondition clearHumidityCondition = new AlarmCondition(); 371 AlarmCondition clearHumidityCondition = new AlarmCondition();
369 clearHumidityCondition.setSpec(new SimpleAlarmConditionSpec()); 372 clearHumidityCondition.setSpec(new SimpleAlarmConditionSpec());
370 373
371 - KeyFilter clearHumidityTimeseriesFilter = new KeyFilter();  
372 - clearHumidityTimeseriesFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "humidity")); 374 + AlarmConditionFilter clearHumidityTimeseriesFilter = new AlarmConditionFilter();
  375 + clearHumidityTimeseriesFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, "humidity"));
373 clearHumidityTimeseriesFilter.setValueType(EntityKeyValueType.NUMERIC); 376 clearHumidityTimeseriesFilter.setValueType(EntityKeyValueType.NUMERIC);
374 NumericFilterPredicate clearHumidityTimeseriesFilterPredicate = new NumericFilterPredicate(); 377 NumericFilterPredicate clearHumidityTimeseriesFilterPredicate = new NumericFilterPredicate();
375 clearHumidityTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER_OR_EQUAL); 378 clearHumidityTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER_OR_EQUAL);
@@ -196,11 +196,17 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe @@ -196,11 +196,17 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe
196 } 196 }
197 break; 197 break;
198 case "3.1.1": 198 case "3.1.1":
  199 + case "3.2.1":
199 try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { 200 try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
200 log.info("Load TTL functions ..."); 201 log.info("Load TTL functions ...");
201 loadSql(conn, LOAD_TTL_FUNCTIONS_SQL); 202 loadSql(conn, LOAD_TTL_FUNCTIONS_SQL);
202 log.info("Load Drop Partitions functions ..."); 203 log.info("Load Drop Partitions functions ...");
203 loadSql(conn, LOAD_DROP_PARTITIONS_FUNCTIONS_SQL); 204 loadSql(conn, LOAD_DROP_PARTITIONS_FUNCTIONS_SQL);
  205 +
  206 + executeQuery(conn, "DROP PROCEDURE IF EXISTS cleanup_timeseries_by_ttl(character varying, bigint, bigint);");
  207 + executeQuery(conn, "DROP FUNCTION IF EXISTS delete_asset_records_from_ts_kv(character varying, character varying, bigint);");
  208 + executeQuery(conn, "DROP FUNCTION IF EXISTS delete_device_records_from_ts_kv(character varying, character varying, bigint);");
  209 + executeQuery(conn, "DROP FUNCTION IF EXISTS delete_customer_records_from_ts_kv(character varying, character varying, bigint);");
204 } 210 }
205 break; 211 break;
206 default: 212 default:
@@ -178,6 +178,7 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr @@ -178,6 +178,7 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr
178 } 178 }
179 break; 179 break;
180 case "3.1.1": 180 case "3.1.1":
  181 + case "3.2.1":
181 break; 182 break;
182 default: 183 default:
183 throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); 184 throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
@@ -145,7 +145,7 @@ public class DefaultTbClusterService implements TbClusterService { @@ -145,7 +145,7 @@ public class DefaultTbClusterService implements TbClusterService {
145 tbMsg = transformMsg(tbMsg, deviceProfileCache.get(tenantId, new DeviceProfileId(entityId.getId()))); 145 tbMsg = transformMsg(tbMsg, deviceProfileCache.get(tenantId, new DeviceProfileId(entityId.getId())));
146 } 146 }
147 } 147 }
148 - TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, tenantId, entityId); 148 + TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, tbMsg.getQueueName(), tenantId, entityId);
149 log.trace("PUSHING msg: {} to:{}", tbMsg, tpi); 149 log.trace("PUSHING msg: {} to:{}", tbMsg, tpi);
150 ToRuleEngineMsg msg = ToRuleEngineMsg.newBuilder() 150 ToRuleEngineMsg msg = ToRuleEngineMsg.newBuilder()
151 .setTenantIdMSB(tenantId.getId().getMostSignificantBits()) 151 .setTenantIdMSB(tenantId.getId().getMostSignificantBits())
@@ -55,31 +55,22 @@ public class TbCoreConsumerStats { @@ -55,31 +55,22 @@ public class TbCoreConsumerStats {
55 public TbCoreConsumerStats(StatsFactory statsFactory) { 55 public TbCoreConsumerStats(StatsFactory statsFactory) {
56 String statsKey = StatsType.CORE.getName(); 56 String statsKey = StatsType.CORE.getName();
57 57
58 - this.totalCounter = statsFactory.createStatsCounter(statsKey, TOTAL_MSGS);  
59 - this.sessionEventCounter = statsFactory.createStatsCounter(statsKey, SESSION_EVENTS);  
60 - this.getAttributesCounter = statsFactory.createStatsCounter(statsKey, GET_ATTRIBUTE);  
61 - this.subscribeToAttributesCounter = statsFactory.createStatsCounter(statsKey, ATTRIBUTE_SUBSCRIBES);  
62 - this.subscribeToRPCCounter = statsFactory.createStatsCounter(statsKey, RPC_SUBSCRIBES);  
63 - this.toDeviceRPCCallResponseCounter = statsFactory.createStatsCounter(statsKey, TO_DEVICE_RPC_CALL_RESPONSES);  
64 - this.subscriptionInfoCounter = statsFactory.createStatsCounter(statsKey, SUBSCRIPTION_INFO);  
65 - this.claimDeviceCounter = statsFactory.createStatsCounter(statsKey, DEVICE_CLAIMS);  
66 - this.deviceStateCounter = statsFactory.createStatsCounter(statsKey, DEVICE_STATES);  
67 - this.subscriptionMsgCounter = statsFactory.createStatsCounter(statsKey, SUBSCRIPTION_MSGS);  
68 - this.toCoreNotificationsCounter = statsFactory.createStatsCounter(statsKey, TO_CORE_NOTIFICATIONS);  
69 -  
70 -  
71 - counters.add(totalCounter);  
72 - counters.add(sessionEventCounter);  
73 - counters.add(getAttributesCounter);  
74 - counters.add(subscribeToAttributesCounter);  
75 - counters.add(subscribeToRPCCounter);  
76 - counters.add(toDeviceRPCCallResponseCounter);  
77 - counters.add(subscriptionInfoCounter);  
78 - counters.add(claimDeviceCounter); 58 + this.totalCounter = register(statsFactory.createStatsCounter(statsKey, TOTAL_MSGS));
  59 + this.sessionEventCounter = register(statsFactory.createStatsCounter(statsKey, SESSION_EVENTS));
  60 + this.getAttributesCounter = register(statsFactory.createStatsCounter(statsKey, GET_ATTRIBUTE));
  61 + this.subscribeToAttributesCounter = register(statsFactory.createStatsCounter(statsKey, ATTRIBUTE_SUBSCRIBES));
  62 + this.subscribeToRPCCounter = register(statsFactory.createStatsCounter(statsKey, RPC_SUBSCRIBES));
  63 + this.toDeviceRPCCallResponseCounter = register(statsFactory.createStatsCounter(statsKey, TO_DEVICE_RPC_CALL_RESPONSES));
  64 + this.subscriptionInfoCounter = register(statsFactory.createStatsCounter(statsKey, SUBSCRIPTION_INFO));
  65 + this.claimDeviceCounter = register(statsFactory.createStatsCounter(statsKey, DEVICE_CLAIMS));
  66 + this.deviceStateCounter = register(statsFactory.createStatsCounter(statsKey, DEVICE_STATES));
  67 + this.subscriptionMsgCounter = register(statsFactory.createStatsCounter(statsKey, SUBSCRIPTION_MSGS));
  68 + this.toCoreNotificationsCounter = register(statsFactory.createStatsCounter(statsKey, TO_CORE_NOTIFICATIONS));
  69 + }
79 70
80 - counters.add(deviceStateCounter);  
81 - counters.add(subscriptionMsgCounter);  
82 - counters.add(toCoreNotificationsCounter); 71 + private StatsCounter register(StatsCounter counter){
  72 + counters.add(counter);
  73 + return counter;
83 } 74 }
84 75
85 public void log(TransportProtos.TransportToDeviceActorMsg msg) { 76 public void log(TransportProtos.TransportToDeviceActorMsg msg) {
@@ -24,7 +24,7 @@ import java.util.regex.Pattern; @@ -24,7 +24,7 @@ import java.util.regex.Pattern;
24 @Slf4j 24 @Slf4j
25 public abstract class AbstractSmsSender implements SmsSender { 25 public abstract class AbstractSmsSender implements SmsSender {
26 26
27 - private static final Pattern E_164_PHONE_NUMBER_PATTERN = Pattern.compile("^\\+[1-9]\\d{1,14}$"); 27 + protected static final Pattern E_164_PHONE_NUMBER_PATTERN = Pattern.compile("^\\+[1-9]\\d{1,14}$");
28 28
29 private static final int MAX_SMS_MESSAGE_LENGTH = 1600; 29 private static final int MAX_SMS_MESSAGE_LENGTH = 1600;
30 private static final int MAX_SMS_SEGMENT_LENGTH = 70; 30 private static final int MAX_SMS_SEGMENT_LENGTH = 70;
@@ -19,21 +19,34 @@ import com.twilio.http.TwilioRestClient; @@ -19,21 +19,34 @@ import com.twilio.http.TwilioRestClient;
19 import com.twilio.rest.api.v2010.account.Message; 19 import com.twilio.rest.api.v2010.account.Message;
20 import com.twilio.type.PhoneNumber; 20 import com.twilio.type.PhoneNumber;
21 import org.apache.commons.lang3.StringUtils; 21 import org.apache.commons.lang3.StringUtils;
  22 +import org.thingsboard.rule.engine.api.sms.exception.SmsParseException;
22 import org.thingsboard.server.common.data.sms.config.TwilioSmsProviderConfiguration; 23 import org.thingsboard.server.common.data.sms.config.TwilioSmsProviderConfiguration;
23 import org.thingsboard.rule.engine.api.sms.exception.SmsException; 24 import org.thingsboard.rule.engine.api.sms.exception.SmsException;
24 import org.thingsboard.rule.engine.api.sms.exception.SmsSendException; 25 import org.thingsboard.rule.engine.api.sms.exception.SmsSendException;
25 import org.thingsboard.server.service.sms.AbstractSmsSender; 26 import org.thingsboard.server.service.sms.AbstractSmsSender;
26 27
  28 +import java.util.regex.Pattern;
  29 +
27 public class TwilioSmsSender extends AbstractSmsSender { 30 public class TwilioSmsSender extends AbstractSmsSender {
28 31
  32 + private static final Pattern PHONE_NUMBERS_SID_MESSAGE_SERVICE_SID = Pattern.compile("^(PN|MG).*$");
  33 +
29 private TwilioRestClient twilioRestClient; 34 private TwilioRestClient twilioRestClient;
30 private String numberFrom; 35 private String numberFrom;
31 36
  37 + private String validatePhoneTwilioNumber(String phoneNumber) throws SmsParseException {
  38 + phoneNumber = phoneNumber.trim();
  39 + if (!E_164_PHONE_NUMBER_PATTERN.matcher(phoneNumber).matches() && !PHONE_NUMBERS_SID_MESSAGE_SERVICE_SID.matcher(phoneNumber).matches()) {
  40 + throw new SmsParseException("Invalid phone number format. Phone number must be in E.164 format/Phone Number's SID/Messaging Service SID.");
  41 + }
  42 + return phoneNumber;
  43 + }
  44 +
32 public TwilioSmsSender(TwilioSmsProviderConfiguration config) { 45 public TwilioSmsSender(TwilioSmsProviderConfiguration config) {
33 if (StringUtils.isEmpty(config.getAccountSid()) || StringUtils.isEmpty(config.getAccountToken()) || StringUtils.isEmpty(config.getNumberFrom())) { 46 if (StringUtils.isEmpty(config.getAccountSid()) || StringUtils.isEmpty(config.getAccountToken()) || StringUtils.isEmpty(config.getNumberFrom())) {
34 throw new IllegalArgumentException("Invalid twilio sms provider configuration: accountSid, accountToken and numberFrom should be specified!"); 47 throw new IllegalArgumentException("Invalid twilio sms provider configuration: accountSid, accountToken and numberFrom should be specified!");
35 } 48 }
36 - this.numberFrom = this.validatePhoneNumber(config.getNumberFrom()); 49 + this.numberFrom = this.validatePhoneTwilioNumber(config.getNumberFrom());
37 this.twilioRestClient = new TwilioRestClient.Builder(config.getAccountSid(), config.getAccountToken()).build(); 50 this.twilioRestClient = new TwilioRestClient.Builder(config.getAccountSid(), config.getAccountToken()).build();
38 } 51 }
39 52
@@ -54,6 +54,7 @@ import org.thingsboard.server.service.telemetry.TelemetryWebSocketService; @@ -54,6 +54,7 @@ import org.thingsboard.server.service.telemetry.TelemetryWebSocketService;
54 import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef; 54 import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef;
55 import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataCmd; 55 import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataCmd;
56 import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataUpdate; 56 import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataUpdate;
  57 +import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountCmd;
57 import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd; 58 import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd;
58 import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate; 59 import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate;
59 import org.thingsboard.server.service.telemetry.cmd.v2.EntityHistoryCmd; 60 import org.thingsboard.server.service.telemetry.cmd.v2.EntityHistoryCmd;
@@ -92,7 +93,7 @@ import java.util.stream.Collectors; @@ -92,7 +93,7 @@ import java.util.stream.Collectors;
92 public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubscriptionService { 93 public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubscriptionService {
93 94
94 private static final int DEFAULT_LIMIT = 100; 95 private static final int DEFAULT_LIMIT = 100;
95 - private final Map<String, Map<Integer, TbAbstractDataSubCtx>> subscriptionsBySessionId = new ConcurrentHashMap<>(); 96 + private final Map<String, Map<Integer, TbAbstractSubCtx>> subscriptionsBySessionId = new ConcurrentHashMap<>();
96 97
97 @Autowired 98 @Autowired
98 private TelemetryWebSocketService wsService; 99 private TelemetryWebSocketService wsService;
@@ -202,7 +203,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc @@ -202,7 +203,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
202 //TODO: validate number of dynamic page links against rate limits. Ignore dynamic flag if limit is reached. 203 //TODO: validate number of dynamic page links against rate limits. Ignore dynamic flag if limit is reached.
203 TbEntityDataSubCtx finalCtx = ctx; 204 TbEntityDataSubCtx finalCtx = ctx;
204 ScheduledFuture<?> task = scheduler.scheduleWithFixedDelay( 205 ScheduledFuture<?> task = scheduler.scheduleWithFixedDelay(
205 - () -> refreshDynamicQuery(tenantId, customerId, finalCtx), 206 + () -> refreshDynamicQuery(finalCtx),
206 dynamicPageLinkRefreshInterval, dynamicPageLinkRefreshInterval, TimeUnit.SECONDS); 207 dynamicPageLinkRefreshInterval, dynamicPageLinkRefreshInterval, TimeUnit.SECONDS);
207 finalCtx.setRefreshTask(task); 208 finalCtx.setRefreshTask(task);
208 } 209 }
@@ -236,6 +237,26 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc @@ -236,6 +237,26 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
236 } 237 }
237 238
238 @Override 239 @Override
  240 + public void handleCmd(TelemetryWebSocketSessionRef session, EntityCountCmd cmd) {
  241 + TbEntityCountSubCtx ctx = getSubCtx(session.getSessionId(), cmd.getCmdId());
  242 + if (ctx == null) {
  243 + ctx = createSubCtx(session, cmd);
  244 + long start = System.currentTimeMillis();
  245 + ctx.fetchData();
  246 + long end = System.currentTimeMillis();
  247 + stats.getRegularQueryInvocationCnt().incrementAndGet();
  248 + stats.getRegularQueryTimeSpent().addAndGet(end - start);
  249 + TbEntityCountSubCtx finalCtx = ctx;
  250 + ScheduledFuture<?> task = scheduler.scheduleWithFixedDelay(
  251 + () -> refreshDynamicQuery(finalCtx),
  252 + dynamicPageLinkRefreshInterval, dynamicPageLinkRefreshInterval, TimeUnit.SECONDS);
  253 + finalCtx.setRefreshTask(task);
  254 + } else {
  255 + log.debug("[{}][{}] Received duplicate command: {}", session.getSessionId(), cmd.getCmdId(), cmd);
  256 + }
  257 + }
  258 +
  259 + @Override
239 public void handleCmd(TelemetryWebSocketSessionRef session, AlarmDataCmd cmd) { 260 public void handleCmd(TelemetryWebSocketSessionRef session, AlarmDataCmd cmd) {
240 TbAlarmDataSubCtx ctx = getSubCtx(session.getSessionId(), cmd.getCmdId()); 261 TbAlarmDataSubCtx ctx = getSubCtx(session.getSessionId(), cmd.getCmdId());
241 if (ctx == null) { 262 if (ctx == null) {
@@ -267,7 +288,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc @@ -267,7 +288,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
267 } 288 }
268 } 289 }
269 290
270 - private void refreshDynamicQuery(TenantId tenantId, CustomerId customerId, TbEntityDataSubCtx finalCtx) { 291 + private void refreshDynamicQuery(TbAbstractSubCtx finalCtx) {
271 try { 292 try {
272 long start = System.currentTimeMillis(); 293 long start = System.currentTimeMillis();
273 finalCtx.update(); 294 finalCtx.update();
@@ -299,7 +320,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc @@ -299,7 +320,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
299 } 320 }
300 321
301 private TbEntityDataSubCtx createSubCtx(TelemetryWebSocketSessionRef sessionRef, EntityDataCmd cmd) { 322 private TbEntityDataSubCtx createSubCtx(TelemetryWebSocketSessionRef sessionRef, EntityDataCmd cmd) {
302 - Map<Integer, TbAbstractDataSubCtx> sessionSubs = subscriptionsBySessionId.computeIfAbsent(sessionRef.getSessionId(), k -> new HashMap<>()); 323 + Map<Integer, TbAbstractSubCtx> sessionSubs = subscriptionsBySessionId.computeIfAbsent(sessionRef.getSessionId(), k -> new HashMap<>());
303 TbEntityDataSubCtx ctx = new TbEntityDataSubCtx(serviceId, wsService, entityService, localSubscriptionService, 324 TbEntityDataSubCtx ctx = new TbEntityDataSubCtx(serviceId, wsService, entityService, localSubscriptionService,
304 attributesService, stats, sessionRef, cmd.getCmdId(), maxEntitiesPerDataSubscription); 325 attributesService, stats, sessionRef, cmd.getCmdId(), maxEntitiesPerDataSubscription);
305 if (cmd.getQuery() != null) { 326 if (cmd.getQuery() != null) {
@@ -309,8 +330,20 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc @@ -309,8 +330,20 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
309 return ctx; 330 return ctx;
310 } 331 }
311 332
  333 + private TbEntityCountSubCtx createSubCtx(TelemetryWebSocketSessionRef sessionRef, EntityCountCmd cmd) {
  334 + Map<Integer, TbAbstractSubCtx> sessionSubs = subscriptionsBySessionId.computeIfAbsent(sessionRef.getSessionId(), k -> new HashMap<>());
  335 + TbEntityCountSubCtx ctx = new TbEntityCountSubCtx(serviceId, wsService, entityService, localSubscriptionService,
  336 + attributesService, stats, sessionRef, cmd.getCmdId());
  337 + if (cmd.getQuery() != null) {
  338 + ctx.setAndResolveQuery(cmd.getQuery());
  339 + }
  340 + sessionSubs.put(cmd.getCmdId(), ctx);
  341 + return ctx;
  342 + }
  343 +
  344 +
312 private TbAlarmDataSubCtx createSubCtx(TelemetryWebSocketSessionRef sessionRef, AlarmDataCmd cmd) { 345 private TbAlarmDataSubCtx createSubCtx(TelemetryWebSocketSessionRef sessionRef, AlarmDataCmd cmd) {
313 - Map<Integer, TbAbstractDataSubCtx> sessionSubs = subscriptionsBySessionId.computeIfAbsent(sessionRef.getSessionId(), k -> new HashMap<>()); 346 + Map<Integer, TbAbstractSubCtx> sessionSubs = subscriptionsBySessionId.computeIfAbsent(sessionRef.getSessionId(), k -> new HashMap<>());
314 TbAlarmDataSubCtx ctx = new TbAlarmDataSubCtx(serviceId, wsService, entityService, localSubscriptionService, 347 TbAlarmDataSubCtx ctx = new TbAlarmDataSubCtx(serviceId, wsService, entityService, localSubscriptionService,
315 attributesService, stats, alarmService, sessionRef, cmd.getCmdId(), maxEntitiesPerAlarmSubscription); 348 attributesService, stats, alarmService, sessionRef, cmd.getCmdId(), maxEntitiesPerAlarmSubscription);
316 ctx.setAndResolveQuery(cmd.getQuery()); 349 ctx.setAndResolveQuery(cmd.getQuery());
@@ -319,8 +352,8 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc @@ -319,8 +352,8 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
319 } 352 }
320 353
321 @SuppressWarnings("unchecked") 354 @SuppressWarnings("unchecked")
322 - private <T extends TbAbstractDataSubCtx> T getSubCtx(String sessionId, int cmdId) {  
323 - Map<Integer, TbAbstractDataSubCtx> sessionSubs = subscriptionsBySessionId.get(sessionId); 355 + private <T extends TbAbstractSubCtx> T getSubCtx(String sessionId, int cmdId) {
  356 + Map<Integer, TbAbstractSubCtx> sessionSubs = subscriptionsBySessionId.get(sessionId);
324 if (sessionSubs != null) { 357 if (sessionSubs != null) {
325 return (T) sessionSubs.get(cmdId); 358 return (T) sessionSubs.get(cmdId);
326 } else { 359 } else {
@@ -464,17 +497,16 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc @@ -464,17 +497,16 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
464 cleanupAndCancel(getSubCtx(sessionId, cmd.getCmdId())); 497 cleanupAndCancel(getSubCtx(sessionId, cmd.getCmdId()));
465 } 498 }
466 499
467 - private void cleanupAndCancel(TbAbstractDataSubCtx ctx) { 500 + private void cleanupAndCancel(TbAbstractSubCtx ctx) {
468 if (ctx != null) { 501 if (ctx != null) {
469 ctx.cancelTasks(); 502 ctx.cancelTasks();
470 - ctx.clearEntitySubscriptions();  
471 - ctx.clearDynamicValueSubscriptions(); 503 + ctx.clearSubscriptions();
472 } 504 }
473 } 505 }
474 506
475 @Override 507 @Override
476 public void cancelAllSessionSubscriptions(String sessionId) { 508 public void cancelAllSessionSubscriptions(String sessionId) {
477 - Map<Integer, TbAbstractDataSubCtx> sessionSubs = subscriptionsBySessionId.remove(sessionId); 509 + Map<Integer, TbAbstractSubCtx> sessionSubs = subscriptionsBySessionId.remove(sessionId);
478 if (sessionSubs != null) { 510 if (sessionSubs != null) {
479 sessionSubs.values().forEach(this::cleanupAndCancel); 511 sessionSubs.values().forEach(this::cleanupAndCancel);
480 } 512 }
@@ -15,32 +15,16 @@ @@ -15,32 +15,16 @@
15 */ 15 */
16 package org.thingsboard.server.service.subscription; 16 package org.thingsboard.server.service.subscription;
17 17
18 -import com.google.common.util.concurrent.Futures;  
19 -import com.google.common.util.concurrent.ListenableFuture;  
20 -import com.google.common.util.concurrent.MoreExecutors;  
21 -import lombok.Data;  
22 import lombok.Getter; 18 import lombok.Getter;
23 -import lombok.Setter;  
24 import lombok.extern.slf4j.Slf4j; 19 import lombok.extern.slf4j.Slf4j;
25 -import org.thingsboard.server.common.data.id.CustomerId;  
26 import org.thingsboard.server.common.data.id.EntityId; 20 import org.thingsboard.server.common.data.id.EntityId;
27 -import org.thingsboard.server.common.data.id.TenantId;  
28 -import org.thingsboard.server.common.data.id.UserId;  
29 -import org.thingsboard.server.common.data.kv.AttributeKvEntry;  
30 import org.thingsboard.server.common.data.page.PageData; 21 import org.thingsboard.server.common.data.page.PageData;
31 import org.thingsboard.server.common.data.query.AbstractDataQuery; 22 import org.thingsboard.server.common.data.query.AbstractDataQuery;
32 -import org.thingsboard.server.common.data.query.ComplexFilterPredicate;  
33 -import org.thingsboard.server.common.data.query.DynamicValue;  
34 -import org.thingsboard.server.common.data.query.DynamicValueSourceType;  
35 import org.thingsboard.server.common.data.query.EntityData; 23 import org.thingsboard.server.common.data.query.EntityData;
36 import org.thingsboard.server.common.data.query.EntityDataPageLink; 24 import org.thingsboard.server.common.data.query.EntityDataPageLink;
37 import org.thingsboard.server.common.data.query.EntityDataQuery; 25 import org.thingsboard.server.common.data.query.EntityDataQuery;
38 import org.thingsboard.server.common.data.query.EntityKey; 26 import org.thingsboard.server.common.data.query.EntityKey;
39 import org.thingsboard.server.common.data.query.EntityKeyType; 27 import org.thingsboard.server.common.data.query.EntityKeyType;
40 -import org.thingsboard.server.common.data.query.FilterPredicateType;  
41 -import org.thingsboard.server.common.data.query.KeyFilter;  
42 -import org.thingsboard.server.common.data.query.KeyFilterPredicate;  
43 -import org.thingsboard.server.common.data.query.SimpleKeyFilterPredicate;  
44 import org.thingsboard.server.common.data.query.TsValue; 28 import org.thingsboard.server.common.data.query.TsValue;
45 import org.thingsboard.server.dao.attributes.AttributesService; 29 import org.thingsboard.server.dao.attributes.AttributesService;
46 import org.thingsboard.server.dao.entity.EntityService; 30 import org.thingsboard.server.dao.entity.EntityService;
@@ -52,140 +36,25 @@ import java.util.ArrayList; @@ -52,140 +36,25 @@ import java.util.ArrayList;
52 import java.util.Arrays; 36 import java.util.Arrays;
53 import java.util.Collections; 37 import java.util.Collections;
54 import java.util.HashMap; 38 import java.util.HashMap;
55 -import java.util.HashSet;  
56 import java.util.List; 39 import java.util.List;
57 import java.util.Map; 40 import java.util.Map;
58 -import java.util.Optional;  
59 -import java.util.Set;  
60 import java.util.concurrent.ConcurrentHashMap; 41 import java.util.concurrent.ConcurrentHashMap;
61 -import java.util.concurrent.ExecutionException;  
62 -import java.util.concurrent.ScheduledFuture;  
63 import java.util.function.Function; 42 import java.util.function.Function;
64 import java.util.stream.Collectors; 43 import java.util.stream.Collectors;
65 44
66 @Slf4j 45 @Slf4j
67 -@Data  
68 -public abstract class TbAbstractDataSubCtx<T extends AbstractDataQuery<? extends EntityDataPageLink>> { 46 +public abstract class TbAbstractDataSubCtx<T extends AbstractDataQuery<? extends EntityDataPageLink>> extends TbAbstractSubCtx<T> {
69 47
70 - protected final String serviceId;  
71 - protected final SubscriptionServiceStatistics stats;  
72 - protected final TelemetryWebSocketService wsService;  
73 - protected final EntityService entityService;  
74 - protected final TbLocalSubscriptionService localSubscriptionService;  
75 - protected final AttributesService attributesService;  
76 - protected final TelemetryWebSocketSessionRef sessionRef;  
77 - protected final int cmdId;  
78 protected final Map<Integer, EntityId> subToEntityIdMap; 48 protected final Map<Integer, EntityId> subToEntityIdMap;
79 - protected final Set<Integer> subToDynamicValueKeySet;  
80 - @Getter  
81 - protected final Map<DynamicValueKey, List<DynamicValue>> dynamicValues;  
82 @Getter 49 @Getter
83 protected PageData<EntityData> data; 50 protected PageData<EntityData> data;
84 - @Getter  
85 - @Setter  
86 - protected T query;  
87 - @Setter  
88 - protected volatile ScheduledFuture<?> refreshTask;  
89 51
90 public TbAbstractDataSubCtx(String serviceId, TelemetryWebSocketService wsService, 52 public TbAbstractDataSubCtx(String serviceId, TelemetryWebSocketService wsService,
91 EntityService entityService, TbLocalSubscriptionService localSubscriptionService, 53 EntityService entityService, TbLocalSubscriptionService localSubscriptionService,
92 AttributesService attributesService, SubscriptionServiceStatistics stats, 54 AttributesService attributesService, SubscriptionServiceStatistics stats,
93 TelemetryWebSocketSessionRef sessionRef, int cmdId) { 55 TelemetryWebSocketSessionRef sessionRef, int cmdId) {
94 - this.serviceId = serviceId;  
95 - this.wsService = wsService;  
96 - this.entityService = entityService;  
97 - this.localSubscriptionService = localSubscriptionService;  
98 - this.attributesService = attributesService;  
99 - this.stats = stats;  
100 - this.sessionRef = sessionRef;  
101 - this.cmdId = cmdId; 56 + super(serviceId, wsService, entityService, localSubscriptionService, attributesService, stats, sessionRef, cmdId);
102 this.subToEntityIdMap = new ConcurrentHashMap<>(); 57 this.subToEntityIdMap = new ConcurrentHashMap<>();
103 - this.subToDynamicValueKeySet = ConcurrentHashMap.newKeySet();  
104 - this.dynamicValues = new ConcurrentHashMap<>();  
105 - }  
106 -  
107 - public void setAndResolveQuery(T query) {  
108 - dynamicValues.clear();  
109 - this.query = query;  
110 - if (query != null && query.getKeyFilters() != null) {  
111 - for (KeyFilter filter : query.getKeyFilters()) {  
112 - registerDynamicValues(filter.getPredicate());  
113 - }  
114 - }  
115 - resolve(getTenantId(), getCustomerId(), getUserId());  
116 - }  
117 -  
118 - public void resolve(TenantId tenantId, CustomerId customerId, UserId userId) {  
119 - List<ListenableFuture<DynamicValueKeySub>> futures = new ArrayList<>();  
120 - for (DynamicValueKey key : dynamicValues.keySet()) {  
121 - switch (key.getSourceType()) {  
122 - case CURRENT_TENANT:  
123 - futures.add(resolveEntityValue(tenantId, tenantId, key));  
124 - break;  
125 - case CURRENT_CUSTOMER:  
126 - if (customerId != null && !customerId.isNullUid()) {  
127 - futures.add(resolveEntityValue(tenantId, customerId, key));  
128 - }  
129 - break;  
130 - case CURRENT_USER:  
131 - if (userId != null && !userId.isNullUid()) {  
132 - futures.add(resolveEntityValue(tenantId, userId, key));  
133 - }  
134 - break;  
135 - }  
136 - }  
137 - try {  
138 - Map<EntityId, Map<String, DynamicValueKeySub>> tmpSubMap = new HashMap<>();  
139 - for (DynamicValueKeySub sub : Futures.successfulAsList(futures).get()) {  
140 - tmpSubMap.computeIfAbsent(sub.getEntityId(), tmp -> new HashMap<>()).put(sub.getKey().getSourceAttribute(), sub);  
141 - }  
142 - for (EntityId entityId : tmpSubMap.keySet()) {  
143 - Map<String, Long> keyStates = new HashMap<>();  
144 - Map<String, DynamicValueKeySub> dynamicValueKeySubMap = tmpSubMap.get(entityId);  
145 - dynamicValueKeySubMap.forEach((k, v) -> keyStates.put(k, v.getLastUpdateTs()));  
146 - int subIdx = sessionRef.getSessionSubIdSeq().incrementAndGet();  
147 - TbAttributeSubscription sub = TbAttributeSubscription.builder()  
148 - .serviceId(serviceId)  
149 - .sessionId(sessionRef.getSessionId())  
150 - .subscriptionId(subIdx)  
151 - .tenantId(sessionRef.getSecurityCtx().getTenantId())  
152 - .entityId(entityId)  
153 - .updateConsumer((s, subscriptionUpdate) -> dynamicValueSubUpdate(s, subscriptionUpdate, dynamicValueKeySubMap))  
154 - .allKeys(false)  
155 - .keyStates(keyStates)  
156 - .scope(TbAttributeSubscriptionScope.SERVER_SCOPE)  
157 - .build();  
158 - subToDynamicValueKeySet.add(subIdx);  
159 - localSubscriptionService.addSubscription(sub);  
160 - }  
161 - } catch (InterruptedException | ExecutionException e) {  
162 - log.info("[{}][{}][{}] Failed to resolve dynamic values: {}", tenantId, customerId, userId, dynamicValues.keySet());  
163 - }  
164 -  
165 - }  
166 -  
167 - private void dynamicValueSubUpdate(String sessionId, TelemetrySubscriptionUpdate subscriptionUpdate,  
168 - Map<String, DynamicValueKeySub> dynamicValueKeySubMap) {  
169 - Map<String, TsValue> latestUpdate = new HashMap<>();  
170 - subscriptionUpdate.getData().forEach((k, v) -> {  
171 - Object[] data = (Object[]) v.get(0);  
172 - latestUpdate.put(k, new TsValue((Long) data[0], (String) data[1]));  
173 - });  
174 -  
175 - boolean invalidateFilter = false;  
176 - for (Map.Entry<String, TsValue> entry : latestUpdate.entrySet()) {  
177 - String k = entry.getKey();  
178 - TsValue tsValue = entry.getValue();  
179 - DynamicValueKeySub sub = dynamicValueKeySubMap.get(k);  
180 - if (sub.updateValue(tsValue)) {  
181 - invalidateFilter = true;  
182 - updateDynamicValuesByKey(sub, tsValue);  
183 - }  
184 - }  
185 -  
186 - if (invalidateFilter) {  
187 - update();  
188 - }  
189 } 58 }
190 59
191 public void fetchData() { 60 public void fetchData() {
@@ -231,104 +100,10 @@ public abstract class TbAbstractDataSubCtx<T extends AbstractDataQuery<? extends @@ -231,104 +100,10 @@ public abstract class TbAbstractDataSubCtx<T extends AbstractDataQuery<? extends
231 return data.getData(); 100 return data.getData();
232 } 101 }
233 102
234 - @Data  
235 - private static class DynamicValueKeySub {  
236 - private final DynamicValueKey key;  
237 - private final EntityId entityId;  
238 - private long lastUpdateTs;  
239 - private String lastUpdateValue;  
240 -  
241 - boolean updateValue(TsValue value) {  
242 - if (value.getTs() > lastUpdateTs && (lastUpdateValue == null || !lastUpdateValue.equals(value.getValue()))) {  
243 - this.lastUpdateTs = value.getTs();  
244 - this.lastUpdateValue = value.getValue();  
245 - return true;  
246 - } else {  
247 - return false;  
248 - }  
249 - }  
250 - }  
251 -  
252 - private ListenableFuture<DynamicValueKeySub> resolveEntityValue(TenantId tenantId, EntityId entityId, DynamicValueKey key) {  
253 - ListenableFuture<Optional<AttributeKvEntry>> entry = attributesService.find(tenantId, entityId,  
254 - TbAttributeSubscriptionScope.SERVER_SCOPE.name(), key.getSourceAttribute());  
255 - return Futures.transform(entry, attributeOpt -> {  
256 - DynamicValueKeySub sub = new DynamicValueKeySub(key, entityId);  
257 - if (attributeOpt.isPresent()) {  
258 - AttributeKvEntry attribute = attributeOpt.get();  
259 - sub.setLastUpdateTs(attribute.getLastUpdateTs());  
260 - sub.setLastUpdateValue(attribute.getValueAsString());  
261 - updateDynamicValuesByKey(sub, new TsValue(attribute.getLastUpdateTs(), attribute.getValueAsString()));  
262 - }  
263 - return sub;  
264 - }, MoreExecutors.directExecutor());  
265 - }  
266 -  
267 - @SuppressWarnings("unchecked")  
268 - private void updateDynamicValuesByKey(DynamicValueKeySub sub, TsValue tsValue) {  
269 - DynamicValueKey dvk = sub.getKey();  
270 - switch (dvk.getPredicateType()) {  
271 - case STRING:  
272 - dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(tsValue.getValue()));  
273 - break;  
274 - case NUMERIC:  
275 - try {  
276 - Double dValue = Double.parseDouble(tsValue.getValue());  
277 - dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(dValue));  
278 - } catch (NumberFormatException e) {  
279 - dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(null));  
280 - }  
281 - break;  
282 - case BOOLEAN:  
283 - Boolean bValue = Boolean.parseBoolean(tsValue.getValue());  
284 - dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(bValue));  
285 - break;  
286 - }  
287 - }  
288 -  
289 - @SuppressWarnings("unchecked")  
290 - private void registerDynamicValues(KeyFilterPredicate predicate) {  
291 - switch (predicate.getType()) {  
292 - case STRING:  
293 - case NUMERIC:  
294 - case BOOLEAN:  
295 - Optional<DynamicValue> value = getDynamicValueFromSimplePredicate((SimpleKeyFilterPredicate) predicate);  
296 - if (value.isPresent()) {  
297 - DynamicValue dynamicValue = value.get();  
298 - DynamicValueKey key = new DynamicValueKey(  
299 - predicate.getType(),  
300 - dynamicValue.getSourceType(),  
301 - dynamicValue.getSourceAttribute());  
302 - dynamicValues.computeIfAbsent(key, tmp -> new ArrayList<>()).add(dynamicValue);  
303 - }  
304 - break;  
305 - case COMPLEX:  
306 - ((ComplexFilterPredicate) predicate).getPredicates().forEach(this::registerDynamicValues);  
307 - }  
308 - }  
309 -  
310 - private Optional<DynamicValue<T>> getDynamicValueFromSimplePredicate(SimpleKeyFilterPredicate<T> predicate) {  
311 - if (predicate.getValue().getUserValue() == null) {  
312 - return Optional.ofNullable(predicate.getValue().getDynamicValue());  
313 - } else {  
314 - return Optional.empty();  
315 - }  
316 - }  
317 -  
318 - public String getSessionId() {  
319 - return sessionRef.getSessionId();  
320 - }  
321 -  
322 - public TenantId getTenantId() {  
323 - return sessionRef.getSecurityCtx().getTenantId();  
324 - }  
325 -  
326 - public CustomerId getCustomerId() {  
327 - return sessionRef.getSecurityCtx().getCustomerId();  
328 - }  
329 -  
330 - public UserId getUserId() {  
331 - return sessionRef.getSecurityCtx().getId(); 103 + @Override
  104 + public void clearSubscriptions() {
  105 + clearEntitySubscriptions();
  106 + super.clearSubscriptions();
332 } 107 }
333 108
334 public void clearEntitySubscriptions() { 109 public void clearEntitySubscriptions() {
@@ -340,26 +115,6 @@ public abstract class TbAbstractDataSubCtx<T extends AbstractDataQuery<? extends @@ -340,26 +115,6 @@ public abstract class TbAbstractDataSubCtx<T extends AbstractDataQuery<? extends
340 } 115 }
341 } 116 }
342 117
343 - public void clearDynamicValueSubscriptions() {  
344 - if (subToDynamicValueKeySet != null) {  
345 - for (Integer subId : subToDynamicValueKeySet) {  
346 - localSubscriptionService.cancelSubscription(sessionRef.getSessionId(), subId);  
347 - }  
348 - subToDynamicValueKeySet.clear();  
349 - }  
350 - }  
351 -  
352 - public void setRefreshTask(ScheduledFuture<?> task) {  
353 - this.refreshTask = task;  
354 - }  
355 -  
356 - public void cancelTasks() {  
357 - if (this.refreshTask != null) {  
358 - log.trace("[{}][{}] Canceling old refresh task", sessionRef.getSessionId(), cmdId);  
359 - this.refreshTask.cancel(true);  
360 - }  
361 - }  
362 -  
363 public void createSubscriptions(List<EntityKey> keys, boolean resultToLatestValues) { 118 public void createSubscriptions(List<EntityKey> keys, boolean resultToLatestValues) {
364 Map<EntityKeyType, List<EntityKey>> keysByType = getEntityKeyByTypeMap(keys); 119 Map<EntityKeyType, List<EntityKey>> keysByType = getEntityKeyByTypeMap(keys);
365 for (EntityData entityData : data.getData()) { 120 for (EntityData entityData : data.getData()) {
@@ -459,14 +214,4 @@ public abstract class TbAbstractDataSubCtx<T extends AbstractDataQuery<? extends @@ -459,14 +214,4 @@ public abstract class TbAbstractDataSubCtx<T extends AbstractDataQuery<? extends
459 214
460 abstract void sendWsMsg(String sessionId, TelemetrySubscriptionUpdate subscriptionUpdate, EntityKeyType keyType, boolean resultToLatestValues); 215 abstract void sendWsMsg(String sessionId, TelemetrySubscriptionUpdate subscriptionUpdate, EntityKeyType keyType, boolean resultToLatestValues);
461 216
462 - @Data  
463 - private static class DynamicValueKey {  
464 - @Getter  
465 - private final FilterPredicateType predicateType;  
466 - @Getter  
467 - private final DynamicValueSourceType sourceType;  
468 - @Getter  
469 - private final String sourceAttribute;  
470 - }  
471 -  
472 } 217 }
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.service.subscription;
  17 +
  18 +import com.google.common.util.concurrent.Futures;
  19 +import com.google.common.util.concurrent.ListenableFuture;
  20 +import com.google.common.util.concurrent.MoreExecutors;
  21 +import lombok.Data;
  22 +import lombok.Getter;
  23 +import lombok.Setter;
  24 +import lombok.extern.slf4j.Slf4j;
  25 +import org.thingsboard.server.common.data.id.CustomerId;
  26 +import org.thingsboard.server.common.data.id.EntityId;
  27 +import org.thingsboard.server.common.data.id.TenantId;
  28 +import org.thingsboard.server.common.data.id.UserId;
  29 +import org.thingsboard.server.common.data.kv.AttributeKvEntry;
  30 +import org.thingsboard.server.common.data.query.ComplexFilterPredicate;
  31 +import org.thingsboard.server.common.data.query.DynamicValue;
  32 +import org.thingsboard.server.common.data.query.DynamicValueSourceType;
  33 +import org.thingsboard.server.common.data.query.EntityCountQuery;
  34 +import org.thingsboard.server.common.data.query.EntityKeyType;
  35 +import org.thingsboard.server.common.data.query.FilterPredicateType;
  36 +import org.thingsboard.server.common.data.query.KeyFilter;
  37 +import org.thingsboard.server.common.data.query.KeyFilterPredicate;
  38 +import org.thingsboard.server.common.data.query.SimpleKeyFilterPredicate;
  39 +import org.thingsboard.server.common.data.query.TsValue;
  40 +import org.thingsboard.server.dao.attributes.AttributesService;
  41 +import org.thingsboard.server.dao.entity.EntityService;
  42 +import org.thingsboard.server.service.telemetry.TelemetryWebSocketService;
  43 +import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef;
  44 +import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate;
  45 +
  46 +import java.util.ArrayList;
  47 +import java.util.HashMap;
  48 +import java.util.List;
  49 +import java.util.Map;
  50 +import java.util.Optional;
  51 +import java.util.Set;
  52 +import java.util.concurrent.ConcurrentHashMap;
  53 +import java.util.concurrent.ExecutionException;
  54 +import java.util.concurrent.ScheduledFuture;
  55 +
  56 +@Slf4j
  57 +@Data
  58 +public abstract class TbAbstractSubCtx<T extends EntityCountQuery> {
  59 +
  60 + protected final String serviceId;
  61 + protected final SubscriptionServiceStatistics stats;
  62 + protected final TelemetryWebSocketService wsService;
  63 + protected final EntityService entityService;
  64 + protected final TbLocalSubscriptionService localSubscriptionService;
  65 + protected final AttributesService attributesService;
  66 + protected final TelemetryWebSocketSessionRef sessionRef;
  67 + protected final int cmdId;
  68 + protected final Set<Integer> subToDynamicValueKeySet;
  69 + @Getter
  70 + protected final Map<DynamicValueKey, List<DynamicValue>> dynamicValues;
  71 + @Getter
  72 + @Setter
  73 + protected T query;
  74 + @Setter
  75 + protected volatile ScheduledFuture<?> refreshTask;
  76 +
  77 + public TbAbstractSubCtx(String serviceId, TelemetryWebSocketService wsService,
  78 + EntityService entityService, TbLocalSubscriptionService localSubscriptionService,
  79 + AttributesService attributesService, SubscriptionServiceStatistics stats,
  80 + TelemetryWebSocketSessionRef sessionRef, int cmdId) {
  81 + this.serviceId = serviceId;
  82 + this.wsService = wsService;
  83 + this.entityService = entityService;
  84 + this.localSubscriptionService = localSubscriptionService;
  85 + this.attributesService = attributesService;
  86 + this.stats = stats;
  87 + this.sessionRef = sessionRef;
  88 + this.cmdId = cmdId;
  89 + this.subToDynamicValueKeySet = ConcurrentHashMap.newKeySet();
  90 + this.dynamicValues = new ConcurrentHashMap<>();
  91 + }
  92 +
  93 + public void setAndResolveQuery(T query) {
  94 + dynamicValues.clear();
  95 + this.query = query;
  96 + if (query != null && query.getKeyFilters() != null) {
  97 + for (KeyFilter filter : query.getKeyFilters()) {
  98 + registerDynamicValues(filter.getPredicate());
  99 + }
  100 + }
  101 + resolve(getTenantId(), getCustomerId(), getUserId());
  102 + }
  103 +
  104 + public void resolve(TenantId tenantId, CustomerId customerId, UserId userId) {
  105 + List<ListenableFuture<DynamicValueKeySub>> futures = new ArrayList<>();
  106 + for (DynamicValueKey key : dynamicValues.keySet()) {
  107 + switch (key.getSourceType()) {
  108 + case CURRENT_TENANT:
  109 + futures.add(resolveEntityValue(tenantId, tenantId, key));
  110 + break;
  111 + case CURRENT_CUSTOMER:
  112 + if (customerId != null && !customerId.isNullUid()) {
  113 + futures.add(resolveEntityValue(tenantId, customerId, key));
  114 + }
  115 + break;
  116 + case CURRENT_USER:
  117 + if (userId != null && !userId.isNullUid()) {
  118 + futures.add(resolveEntityValue(tenantId, userId, key));
  119 + }
  120 + break;
  121 + }
  122 + }
  123 + try {
  124 + Map<EntityId, Map<String, DynamicValueKeySub>> tmpSubMap = new HashMap<>();
  125 + for (DynamicValueKeySub sub : Futures.successfulAsList(futures).get()) {
  126 + tmpSubMap.computeIfAbsent(sub.getEntityId(), tmp -> new HashMap<>()).put(sub.getKey().getSourceAttribute(), sub);
  127 + }
  128 + for (EntityId entityId : tmpSubMap.keySet()) {
  129 + Map<String, Long> keyStates = new HashMap<>();
  130 + Map<String, DynamicValueKeySub> dynamicValueKeySubMap = tmpSubMap.get(entityId);
  131 + dynamicValueKeySubMap.forEach((k, v) -> keyStates.put(k, v.getLastUpdateTs()));
  132 + int subIdx = sessionRef.getSessionSubIdSeq().incrementAndGet();
  133 + TbAttributeSubscription sub = TbAttributeSubscription.builder()
  134 + .serviceId(serviceId)
  135 + .sessionId(sessionRef.getSessionId())
  136 + .subscriptionId(subIdx)
  137 + .tenantId(sessionRef.getSecurityCtx().getTenantId())
  138 + .entityId(entityId)
  139 + .updateConsumer((s, subscriptionUpdate) -> dynamicValueSubUpdate(s, subscriptionUpdate, dynamicValueKeySubMap))
  140 + .allKeys(false)
  141 + .keyStates(keyStates)
  142 + .scope(TbAttributeSubscriptionScope.SERVER_SCOPE)
  143 + .build();
  144 + subToDynamicValueKeySet.add(subIdx);
  145 + localSubscriptionService.addSubscription(sub);
  146 + }
  147 + } catch (InterruptedException | ExecutionException e) {
  148 + log.info("[{}][{}][{}] Failed to resolve dynamic values: {}", tenantId, customerId, userId, dynamicValues.keySet());
  149 + }
  150 +
  151 + }
  152 +
  153 + private void dynamicValueSubUpdate(String sessionId, TelemetrySubscriptionUpdate subscriptionUpdate,
  154 + Map<String, DynamicValueKeySub> dynamicValueKeySubMap) {
  155 + Map<String, TsValue> latestUpdate = new HashMap<>();
  156 + subscriptionUpdate.getData().forEach((k, v) -> {
  157 + Object[] data = (Object[]) v.get(0);
  158 + latestUpdate.put(k, new TsValue((Long) data[0], (String) data[1]));
  159 + });
  160 +
  161 + boolean invalidateFilter = false;
  162 + for (Map.Entry<String, TsValue> entry : latestUpdate.entrySet()) {
  163 + String k = entry.getKey();
  164 + TsValue tsValue = entry.getValue();
  165 + DynamicValueKeySub sub = dynamicValueKeySubMap.get(k);
  166 + if (sub.updateValue(tsValue)) {
  167 + invalidateFilter = true;
  168 + updateDynamicValuesByKey(sub, tsValue);
  169 + }
  170 + }
  171 +
  172 + if (invalidateFilter) {
  173 + update();
  174 + }
  175 + }
  176 +
  177 + public abstract void fetchData();
  178 +
  179 + protected abstract void update();
  180 +
  181 + public void clearSubscriptions() {
  182 + clearDynamicValueSubscriptions();
  183 + }
  184 +
  185 + @Data
  186 + private static class DynamicValueKeySub {
  187 + private final DynamicValueKey key;
  188 + private final EntityId entityId;
  189 + private long lastUpdateTs;
  190 + private String lastUpdateValue;
  191 +
  192 + boolean updateValue(TsValue value) {
  193 + if (value.getTs() > lastUpdateTs && (lastUpdateValue == null || !lastUpdateValue.equals(value.getValue()))) {
  194 + this.lastUpdateTs = value.getTs();
  195 + this.lastUpdateValue = value.getValue();
  196 + return true;
  197 + } else {
  198 + return false;
  199 + }
  200 + }
  201 + }
  202 +
  203 + private ListenableFuture<DynamicValueKeySub> resolveEntityValue(TenantId tenantId, EntityId entityId, DynamicValueKey key) {
  204 + ListenableFuture<Optional<AttributeKvEntry>> entry = attributesService.find(tenantId, entityId,
  205 + TbAttributeSubscriptionScope.SERVER_SCOPE.name(), key.getSourceAttribute());
  206 + return Futures.transform(entry, attributeOpt -> {
  207 + DynamicValueKeySub sub = new DynamicValueKeySub(key, entityId);
  208 + if (attributeOpt.isPresent()) {
  209 + AttributeKvEntry attribute = attributeOpt.get();
  210 + sub.setLastUpdateTs(attribute.getLastUpdateTs());
  211 + sub.setLastUpdateValue(attribute.getValueAsString());
  212 + updateDynamicValuesByKey(sub, new TsValue(attribute.getLastUpdateTs(), attribute.getValueAsString()));
  213 + }
  214 + return sub;
  215 + }, MoreExecutors.directExecutor());
  216 + }
  217 +
  218 + @SuppressWarnings("unchecked")
  219 + protected void updateDynamicValuesByKey(DynamicValueKeySub sub, TsValue tsValue) {
  220 + DynamicValueKey dvk = sub.getKey();
  221 + switch (dvk.getPredicateType()) {
  222 + case STRING:
  223 + dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(tsValue.getValue()));
  224 + break;
  225 + case NUMERIC:
  226 + try {
  227 + Double dValue = Double.parseDouble(tsValue.getValue());
  228 + dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(dValue));
  229 + } catch (NumberFormatException e) {
  230 + dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(null));
  231 + }
  232 + break;
  233 + case BOOLEAN:
  234 + Boolean bValue = Boolean.parseBoolean(tsValue.getValue());
  235 + dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(bValue));
  236 + break;
  237 + }
  238 + }
  239 +
  240 + @SuppressWarnings("unchecked")
  241 + private void registerDynamicValues(KeyFilterPredicate predicate) {
  242 + switch (predicate.getType()) {
  243 + case STRING:
  244 + case NUMERIC:
  245 + case BOOLEAN:
  246 + Optional<DynamicValue> value = getDynamicValueFromSimplePredicate((SimpleKeyFilterPredicate) predicate);
  247 + if (value.isPresent()) {
  248 + DynamicValue dynamicValue = value.get();
  249 + DynamicValueKey key = new DynamicValueKey(
  250 + predicate.getType(),
  251 + dynamicValue.getSourceType(),
  252 + dynamicValue.getSourceAttribute());
  253 + dynamicValues.computeIfAbsent(key, tmp -> new ArrayList<>()).add(dynamicValue);
  254 + }
  255 + break;
  256 + case COMPLEX:
  257 + ((ComplexFilterPredicate) predicate).getPredicates().forEach(this::registerDynamicValues);
  258 + }
  259 + }
  260 +
  261 + private Optional<DynamicValue<T>> getDynamicValueFromSimplePredicate(SimpleKeyFilterPredicate<T> predicate) {
  262 + if (predicate.getValue().getUserValue() == null) {
  263 + return Optional.ofNullable(predicate.getValue().getDynamicValue());
  264 + } else {
  265 + return Optional.empty();
  266 + }
  267 + }
  268 +
  269 + public String getSessionId() {
  270 + return sessionRef.getSessionId();
  271 + }
  272 +
  273 + public TenantId getTenantId() {
  274 + return sessionRef.getSecurityCtx().getTenantId();
  275 + }
  276 +
  277 + public CustomerId getCustomerId() {
  278 + return sessionRef.getSecurityCtx().getCustomerId();
  279 + }
  280 +
  281 + public UserId getUserId() {
  282 + return sessionRef.getSecurityCtx().getId();
  283 + }
  284 +
  285 + protected void clearDynamicValueSubscriptions() {
  286 + if (subToDynamicValueKeySet != null) {
  287 + for (Integer subId : subToDynamicValueKeySet) {
  288 + localSubscriptionService.cancelSubscription(sessionRef.getSessionId(), subId);
  289 + }
  290 + subToDynamicValueKeySet.clear();
  291 + }
  292 + }
  293 +
  294 + public void setRefreshTask(ScheduledFuture<?> task) {
  295 + this.refreshTask = task;
  296 + }
  297 +
  298 + public void cancelTasks() {
  299 + if (this.refreshTask != null) {
  300 + log.trace("[{}][{}] Canceling old refresh task", sessionRef.getSessionId(), cmdId);
  301 + this.refreshTask.cancel(true);
  302 + }
  303 + }
  304 +
  305 + @Data
  306 + public static class DynamicValueKey {
  307 + @Getter
  308 + private final FilterPredicateType predicateType;
  309 + @Getter
  310 + private final DynamicValueSourceType sourceType;
  311 + @Getter
  312 + private final String sourceAttribute;
  313 + }
  314 +
  315 +}
@@ -90,8 +90,7 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx<AlarmDataQuery> { @@ -90,8 +90,7 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx<AlarmDataQuery> {
90 AlarmDataUpdate update; 90 AlarmDataUpdate update;
91 if (!entitiesMap.isEmpty()) { 91 if (!entitiesMap.isEmpty()) {
92 long start = System.currentTimeMillis(); 92 long start = System.currentTimeMillis();
93 - PageData<AlarmData> alarms = alarmService.findAlarmDataByQueryForEntities(getTenantId(), getCustomerId(),  
94 - query, getOrderedEntityIds()); 93 + PageData<AlarmData> alarms = alarmService.findAlarmDataByQueryForEntities(getTenantId(), getCustomerId(), query, getOrderedEntityIds());
95 long end = System.currentTimeMillis(); 94 long end = System.currentTimeMillis();
96 stats.getAlarmQueryInvocationCnt().incrementAndGet(); 95 stats.getAlarmQueryInvocationCnt().incrementAndGet();
97 stats.getAlarmQueryTimeSpent().addAndGet(end - start); 96 stats.getAlarmQueryTimeSpent().addAndGet(end - start);
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.service.subscription;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.thingsboard.server.common.data.query.EntityCountQuery;
  20 +import org.thingsboard.server.common.data.query.EntityKeyType;
  21 +import org.thingsboard.server.dao.attributes.AttributesService;
  22 +import org.thingsboard.server.dao.entity.EntityService;
  23 +import org.thingsboard.server.service.telemetry.TelemetryWebSocketService;
  24 +import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef;
  25 +import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountUpdate;
  26 +import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate;
  27 +import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate;
  28 +
  29 +@Slf4j
  30 +public class TbEntityCountSubCtx extends TbAbstractSubCtx<EntityCountQuery> {
  31 +
  32 + private volatile int result;
  33 +
  34 + public TbEntityCountSubCtx(String serviceId, TelemetryWebSocketService wsService, EntityService entityService,
  35 + TbLocalSubscriptionService localSubscriptionService, AttributesService attributesService,
  36 + SubscriptionServiceStatistics stats, TelemetryWebSocketSessionRef sessionRef, int cmdId) {
  37 + super(serviceId, wsService, entityService, localSubscriptionService, attributesService, stats, sessionRef, cmdId);
  38 + }
  39 +
  40 + @Override
  41 + public void fetchData() {
  42 + result = (int) entityService.countEntitiesByQuery(getTenantId(), getCustomerId(), query);
  43 + wsService.sendWsMsg(sessionRef.getSessionId(), new EntityCountUpdate(cmdId, result));
  44 + }
  45 +
  46 + @Override
  47 + protected void update() {
  48 + int newCount = (int) entityService.countEntitiesByQuery(getTenantId(), getCustomerId(), query);
  49 + if (newCount != result) {
  50 + result = newCount;
  51 + wsService.sendWsMsg(sessionRef.getSessionId(), new EntityCountUpdate(cmdId, result));
  52 + }
  53 + }
  54 +
  55 +}
@@ -17,6 +17,7 @@ package org.thingsboard.server.service.subscription; @@ -17,6 +17,7 @@ package org.thingsboard.server.service.subscription;
17 17
18 import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef; 18 import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef;
19 import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataCmd; 19 import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataCmd;
  20 +import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountCmd;
20 import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd; 21 import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd;
21 import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUnsubscribeCmd; 22 import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUnsubscribeCmd;
22 import org.thingsboard.server.service.telemetry.cmd.v2.UnsubscribeCmd; 23 import org.thingsboard.server.service.telemetry.cmd.v2.UnsubscribeCmd;
@@ -25,6 +26,8 @@ public interface TbEntityDataSubscriptionService { @@ -25,6 +26,8 @@ public interface TbEntityDataSubscriptionService {
25 26
26 void handleCmd(TelemetryWebSocketSessionRef sessionId, EntityDataCmd cmd); 27 void handleCmd(TelemetryWebSocketSessionRef sessionId, EntityDataCmd cmd);
27 28
  29 + void handleCmd(TelemetryWebSocketSessionRef sessionId, EntityCountCmd cmd);
  30 +
28 void handleCmd(TelemetryWebSocketSessionRef sessionId, AlarmDataCmd cmd); 31 void handleCmd(TelemetryWebSocketSessionRef sessionId, AlarmDataCmd cmd);
29 32
30 void cancelSubscription(String sessionId, UnsubscribeCmd subscriptionId); 33 void cancelSubscription(String sessionId, UnsubscribeCmd subscriptionId);
@@ -51,22 +51,22 @@ import org.thingsboard.server.service.security.ValidationResult; @@ -51,22 +51,22 @@ import org.thingsboard.server.service.security.ValidationResult;
51 import org.thingsboard.server.service.security.ValidationResultCode; 51 import org.thingsboard.server.service.security.ValidationResultCode;
52 import org.thingsboard.server.service.security.model.UserPrincipal; 52 import org.thingsboard.server.service.security.model.UserPrincipal;
53 import org.thingsboard.server.service.security.permission.Operation; 53 import org.thingsboard.server.service.security.permission.Operation;
  54 +import org.thingsboard.server.service.subscription.TbAttributeSubscription;
  55 +import org.thingsboard.server.service.subscription.TbAttributeSubscriptionScope;
54 import org.thingsboard.server.service.subscription.TbEntityDataSubscriptionService; 56 import org.thingsboard.server.service.subscription.TbEntityDataSubscriptionService;
55 import org.thingsboard.server.service.subscription.TbLocalSubscriptionService; 57 import org.thingsboard.server.service.subscription.TbLocalSubscriptionService;
56 -import org.thingsboard.server.service.subscription.TbAttributeSubscriptionScope;  
57 -import org.thingsboard.server.service.subscription.TbAttributeSubscription;  
58 import org.thingsboard.server.service.subscription.TbTimeseriesSubscription; 58 import org.thingsboard.server.service.subscription.TbTimeseriesSubscription;
  59 +import org.thingsboard.server.service.telemetry.cmd.TelemetryPluginCmdsWrapper;
59 import org.thingsboard.server.service.telemetry.cmd.v1.AttributesSubscriptionCmd; 60 import org.thingsboard.server.service.telemetry.cmd.v1.AttributesSubscriptionCmd;
60 import org.thingsboard.server.service.telemetry.cmd.v1.GetHistoryCmd; 61 import org.thingsboard.server.service.telemetry.cmd.v1.GetHistoryCmd;
61 import org.thingsboard.server.service.telemetry.cmd.v1.SubscriptionCmd; 62 import org.thingsboard.server.service.telemetry.cmd.v1.SubscriptionCmd;
62 import org.thingsboard.server.service.telemetry.cmd.v1.TelemetryPluginCmd; 63 import org.thingsboard.server.service.telemetry.cmd.v1.TelemetryPluginCmd;
63 -import org.thingsboard.server.service.telemetry.cmd.TelemetryPluginCmdsWrapper;  
64 import org.thingsboard.server.service.telemetry.cmd.v1.TimeseriesSubscriptionCmd; 64 import org.thingsboard.server.service.telemetry.cmd.v1.TimeseriesSubscriptionCmd;
65 import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataCmd; 65 import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataCmd;
66 -import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataUnsubscribeCmd; 66 +import org.thingsboard.server.service.telemetry.cmd.v2.CmdUpdate;
67 import org.thingsboard.server.service.telemetry.cmd.v2.DataUpdate; 67 import org.thingsboard.server.service.telemetry.cmd.v2.DataUpdate;
  68 +import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountCmd;
68 import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd; 69 import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd;
69 -import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUnsubscribeCmd;  
70 import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate; 70 import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate;
71 import org.thingsboard.server.service.telemetry.cmd.v2.UnsubscribeCmd; 71 import org.thingsboard.server.service.telemetry.cmd.v2.UnsubscribeCmd;
72 import org.thingsboard.server.service.telemetry.exception.UnauthorizedException; 72 import org.thingsboard.server.service.telemetry.exception.UnauthorizedException;
@@ -216,12 +216,18 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi @@ -216,12 +216,18 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
216 if (cmdsWrapper.getAlarmDataCmds() != null) { 216 if (cmdsWrapper.getAlarmDataCmds() != null) {
217 cmdsWrapper.getAlarmDataCmds().forEach(cmd -> handleWsAlarmDataCmd(sessionRef, cmd)); 217 cmdsWrapper.getAlarmDataCmds().forEach(cmd -> handleWsAlarmDataCmd(sessionRef, cmd));
218 } 218 }
  219 + if (cmdsWrapper.getEntityCountCmds() != null) {
  220 + cmdsWrapper.getEntityCountCmds().forEach(cmd -> handleWsEntityCountCmd(sessionRef, cmd));
  221 + }
219 if (cmdsWrapper.getEntityDataUnsubscribeCmds() != null) { 222 if (cmdsWrapper.getEntityDataUnsubscribeCmds() != null) {
220 cmdsWrapper.getEntityDataUnsubscribeCmds().forEach(cmd -> handleWsDataUnsubscribeCmd(sessionRef, cmd)); 223 cmdsWrapper.getEntityDataUnsubscribeCmds().forEach(cmd -> handleWsDataUnsubscribeCmd(sessionRef, cmd));
221 } 224 }
222 if (cmdsWrapper.getAlarmDataUnsubscribeCmds() != null) { 225 if (cmdsWrapper.getAlarmDataUnsubscribeCmds() != null) {
223 cmdsWrapper.getAlarmDataUnsubscribeCmds().forEach(cmd -> handleWsDataUnsubscribeCmd(sessionRef, cmd)); 226 cmdsWrapper.getAlarmDataUnsubscribeCmds().forEach(cmd -> handleWsDataUnsubscribeCmd(sessionRef, cmd));
224 } 227 }
  228 + if (cmdsWrapper.getEntityCountUnsubscribeCmds() != null) {
  229 + cmdsWrapper.getEntityCountUnsubscribeCmds().forEach(cmd -> handleWsDataUnsubscribeCmd(sessionRef, cmd));
  230 + }
225 } 231 }
226 } catch (IOException e) { 232 } catch (IOException e) {
227 log.warn("Failed to decode subscription cmd: {}", e.getMessage(), e); 233 log.warn("Failed to decode subscription cmd: {}", e.getMessage(), e);
@@ -239,6 +245,16 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi @@ -239,6 +245,16 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
239 } 245 }
240 } 246 }
241 247
  248 + private void handleWsEntityCountCmd(TelemetryWebSocketSessionRef sessionRef, EntityCountCmd cmd) {
  249 + String sessionId = sessionRef.getSessionId();
  250 + log.debug("[{}] Processing: {}", sessionId, cmd);
  251 +
  252 + if (validateSessionMetadata(sessionRef, cmd.getCmdId(), sessionId)
  253 + && validateSubscriptionCmd(sessionRef, cmd)) {
  254 + entityDataSubService.handleCmd(sessionRef, cmd);
  255 + }
  256 + }
  257 +
242 private void handleWsAlarmDataCmd(TelemetryWebSocketSessionRef sessionRef, AlarmDataCmd cmd) { 258 private void handleWsAlarmDataCmd(TelemetryWebSocketSessionRef sessionRef, AlarmDataCmd cmd) {
243 String sessionId = sessionRef.getSessionId(); 259 String sessionId = sessionRef.getSessionId();
244 log.debug("[{}] Processing: {}", sessionId, cmd); 260 log.debug("[{}] Processing: {}", sessionId, cmd);
@@ -264,7 +280,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi @@ -264,7 +280,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
264 } 280 }
265 281
266 @Override 282 @Override
267 - public void sendWsMsg(String sessionId, DataUpdate update) { 283 + public void sendWsMsg(String sessionId, CmdUpdate update) {
268 sendWsMsg(sessionId, update.getCmdId(), update); 284 sendWsMsg(sessionId, update.getCmdId(), update);
269 } 285 }
270 286
@@ -679,6 +695,20 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi @@ -679,6 +695,20 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
679 return true; 695 return true;
680 } 696 }
681 697
  698 + private boolean validateSubscriptionCmd(TelemetryWebSocketSessionRef sessionRef, EntityCountCmd cmd) {
  699 + if (cmd.getCmdId() < 0) {
  700 + TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST,
  701 + "Cmd id is negative value!");
  702 + sendWsMsg(sessionRef, update);
  703 + return false;
  704 + } else if (cmd.getQuery() == null) {
  705 + TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST, "Query is empty!");
  706 + sendWsMsg(sessionRef, update);
  707 + return false;
  708 + }
  709 + return true;
  710 + }
  711 +
682 private boolean validateSubscriptionCmd(TelemetryWebSocketSessionRef sessionRef, AlarmDataCmd cmd) { 712 private boolean validateSubscriptionCmd(TelemetryWebSocketSessionRef sessionRef, AlarmDataCmd cmd) {
683 if (cmd.getCmdId() < 0) { 713 if (cmd.getCmdId() < 0) {
684 TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST, 714 TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST,
@@ -15,6 +15,7 @@ @@ -15,6 +15,7 @@
15 */ 15 */
16 package org.thingsboard.server.service.telemetry; 16 package org.thingsboard.server.service.telemetry;
17 17
  18 +import org.thingsboard.server.service.telemetry.cmd.v2.CmdUpdate;
18 import org.thingsboard.server.service.telemetry.cmd.v2.DataUpdate; 19 import org.thingsboard.server.service.telemetry.cmd.v2.DataUpdate;
19 import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate; 20 import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate;
20 21
@@ -29,6 +30,6 @@ public interface TelemetryWebSocketService { @@ -29,6 +30,6 @@ public interface TelemetryWebSocketService {
29 30
30 void sendWsMsg(String sessionId, TelemetrySubscriptionUpdate update); 31 void sendWsMsg(String sessionId, TelemetrySubscriptionUpdate update);
31 32
32 - void sendWsMsg(String sessionId, DataUpdate update); 33 + void sendWsMsg(String sessionId, CmdUpdate update);
33 34
34 } 35 }
@@ -21,6 +21,8 @@ import org.thingsboard.server.service.telemetry.cmd.v1.GetHistoryCmd; @@ -21,6 +21,8 @@ import org.thingsboard.server.service.telemetry.cmd.v1.GetHistoryCmd;
21 import org.thingsboard.server.service.telemetry.cmd.v1.TimeseriesSubscriptionCmd; 21 import org.thingsboard.server.service.telemetry.cmd.v1.TimeseriesSubscriptionCmd;
22 import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataCmd; 22 import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataCmd;
23 import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataUnsubscribeCmd; 23 import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataUnsubscribeCmd;
  24 +import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountCmd;
  25 +import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountUnsubscribeCmd;
24 import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd; 26 import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd;
25 import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUnsubscribeCmd; 27 import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUnsubscribeCmd;
26 28
@@ -46,4 +48,8 @@ public class TelemetryPluginCmdsWrapper { @@ -46,4 +48,8 @@ public class TelemetryPluginCmdsWrapper {
46 48
47 private List<AlarmDataUnsubscribeCmd> alarmDataUnsubscribeCmds; 49 private List<AlarmDataUnsubscribeCmd> alarmDataUnsubscribeCmds;
48 50
  51 + private List<EntityCountCmd> entityCountCmds;
  52 +
  53 + private List<EntityCountUnsubscribeCmd> entityCountUnsubscribeCmds;
  54 +
49 } 55 }
@@ -18,14 +18,14 @@ package org.thingsboard.server.service.telemetry.cmd.v2; @@ -18,14 +18,14 @@ package org.thingsboard.server.service.telemetry.cmd.v2;
18 import com.fasterxml.jackson.annotation.JsonCreator; 18 import com.fasterxml.jackson.annotation.JsonCreator;
19 import com.fasterxml.jackson.annotation.JsonProperty; 19 import com.fasterxml.jackson.annotation.JsonProperty;
20 import lombok.Getter; 20 import lombok.Getter;
21 -import lombok.NoArgsConstructor; 21 +import lombok.ToString;
22 import org.thingsboard.server.common.data.page.PageData; 22 import org.thingsboard.server.common.data.page.PageData;
23 import org.thingsboard.server.common.data.query.AlarmData; 23 import org.thingsboard.server.common.data.query.AlarmData;
24 -import org.thingsboard.server.common.data.query.EntityData;  
25 import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode; 24 import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode;
26 25
27 import java.util.List; 26 import java.util.List;
28 27
  28 +@ToString
29 public class AlarmDataUpdate extends DataUpdate<AlarmData> { 29 public class AlarmDataUpdate extends DataUpdate<AlarmData> {
30 30
31 @Getter 31 @Getter
@@ -44,8 +44,8 @@ public class AlarmDataUpdate extends DataUpdate<AlarmData> { @@ -44,8 +44,8 @@ public class AlarmDataUpdate extends DataUpdate<AlarmData> {
44 } 44 }
45 45
46 @Override 46 @Override
47 - public DataUpdateType getDataUpdateType() {  
48 - return DataUpdateType.ALARM_DATA; 47 + public CmdUpdateType getCmdUpdateType() {
  48 + return CmdUpdateType.ALARM_DATA;
49 } 49 }
50 50
51 @JsonCreator 51 @JsonCreator
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.service.telemetry.cmd.v2;
  17 +
  18 +import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
  19 +import lombok.AllArgsConstructor;
  20 +import lombok.Data;
  21 +
  22 +@Data
  23 +@AllArgsConstructor
  24 +@JsonIgnoreProperties(ignoreUnknown = true)
  25 +public abstract class CmdUpdate {
  26 +
  27 + private final int cmdId;
  28 + private final int errorCode;
  29 + private final String errorMsg;
  30 +
  31 + public abstract CmdUpdateType getCmdUpdateType();
  32 +
  33 +}
application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/CmdUpdateType.java renamed from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/DataUpdateType.java
@@ -15,7 +15,8 @@ @@ -15,7 +15,8 @@
15 */ 15 */
16 package org.thingsboard.server.service.telemetry.cmd.v2; 16 package org.thingsboard.server.service.telemetry.cmd.v2;
17 17
18 -public enum DataUpdateType { 18 +public enum CmdUpdateType {
19 ENTITY_DATA, 19 ENTITY_DATA,
20 - ALARM_DATA 20 + ALARM_DATA,
  21 + COUNT_DATA
21 } 22 }
@@ -15,24 +15,24 @@ @@ -15,24 +15,24 @@
15 */ 15 */
16 package org.thingsboard.server.service.telemetry.cmd.v2; 16 package org.thingsboard.server.service.telemetry.cmd.v2;
17 17
18 -import com.fasterxml.jackson.annotation.JsonIgnoreProperties;  
19 -import lombok.AllArgsConstructor;  
20 -import lombok.Data; 18 +import lombok.Getter;
21 import org.thingsboard.server.common.data.page.PageData; 19 import org.thingsboard.server.common.data.page.PageData;
22 import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode; 20 import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode;
23 21
24 import java.util.List; 22 import java.util.List;
25 23
26 -@Data  
27 -@AllArgsConstructor  
28 -@JsonIgnoreProperties(ignoreUnknown = true)  
29 -public abstract class DataUpdate<T> { 24 +public abstract class DataUpdate<T> extends CmdUpdate {
30 25
31 - private final int cmdId; 26 + @Getter
32 private final PageData<T> data; 27 private final PageData<T> data;
  28 + @Getter
33 private final List<T> update; 29 private final List<T> update;
34 - private final int errorCode;  
35 - private final String errorMsg; 30 +
  31 + public DataUpdate(int cmdId, PageData<T> data, List<T> update, int errorCode, String errorMsg) {
  32 + super(cmdId, errorCode, errorMsg);
  33 + this.data = data;
  34 + this.update = update;
  35 + }
36 36
37 public DataUpdate(int cmdId, PageData<T> data, List<T> update) { 37 public DataUpdate(int cmdId, PageData<T> data, List<T> update) {
38 this(cmdId, data, update, SubscriptionErrorCode.NO_ERROR.getCode(), null); 38 this(cmdId, data, update, SubscriptionErrorCode.NO_ERROR.getCode(), null);
@@ -42,5 +42,4 @@ public abstract class DataUpdate<T> { @@ -42,5 +42,4 @@ public abstract class DataUpdate<T> {
42 this(cmdId, null, null, errorCode, errorMsg); 42 this(cmdId, null, null, errorCode, errorMsg);
43 } 43 }
44 44
45 - public abstract DataUpdateType getDataUpdateType();  
46 } 45 }
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.service.telemetry.cmd.v2;
  17 +
  18 +import com.fasterxml.jackson.annotation.JsonCreator;
  19 +import com.fasterxml.jackson.annotation.JsonProperty;
  20 +import lombok.Getter;
  21 +import org.thingsboard.server.common.data.query.EntityCountQuery;
  22 +import org.thingsboard.server.common.data.query.EntityDataQuery;
  23 +
  24 +public class EntityCountCmd extends DataCmd {
  25 +
  26 + @Getter
  27 + private final EntityCountQuery query;
  28 +
  29 + @JsonCreator
  30 + public EntityCountCmd(@JsonProperty("cmdId") int cmdId,
  31 + @JsonProperty("query") EntityCountQuery query) {
  32 + super(cmdId);
  33 + this.query = query;
  34 + }
  35 +}
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.service.telemetry.cmd.v2;
  17 +
  18 +import lombok.Data;
  19 +
  20 +@Data
  21 +public class EntityCountUnsubscribeCmd implements UnsubscribeCmd {
  22 +
  23 + private final int cmdId;
  24 +
  25 +}
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.service.telemetry.cmd.v2;
  17 +
  18 +import com.fasterxml.jackson.annotation.JsonCreator;
  19 +import com.fasterxml.jackson.annotation.JsonProperty;
  20 +import lombok.Getter;
  21 +import lombok.ToString;
  22 +import org.thingsboard.server.common.data.page.PageData;
  23 +import org.thingsboard.server.common.data.query.EntityData;
  24 +import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode;
  25 +
  26 +import java.util.List;
  27 +
  28 +@ToString
  29 +public class EntityCountUpdate extends CmdUpdate {
  30 +
  31 + @Getter
  32 + private int count;
  33 +
  34 + public EntityCountUpdate(int cmdId, int count) {
  35 + super(cmdId, SubscriptionErrorCode.NO_ERROR.getCode(), null);
  36 + this.count = count;
  37 + }
  38 +
  39 + public EntityCountUpdate(int cmdId, int errorCode, String errorMsg) {
  40 + super(cmdId, errorCode, errorMsg);
  41 + }
  42 +
  43 + @Override
  44 + public CmdUpdateType getCmdUpdateType() {
  45 + return CmdUpdateType.COUNT_DATA;
  46 + }
  47 +
  48 + @JsonCreator
  49 + public EntityCountUpdate(@JsonProperty("cmdId") int cmdId,
  50 + @JsonProperty("count") int count,
  51 + @JsonProperty("errorCode") int errorCode,
  52 + @JsonProperty("errorMsg") String errorMsg) {
  53 + super(cmdId, errorCode, errorMsg);
  54 + this.count = count;
  55 + }
  56 +
  57 +}
@@ -16,16 +16,16 @@ @@ -16,16 +16,16 @@
16 package org.thingsboard.server.service.telemetry.cmd.v2; 16 package org.thingsboard.server.service.telemetry.cmd.v2;
17 17
18 import com.fasterxml.jackson.annotation.JsonCreator; 18 import com.fasterxml.jackson.annotation.JsonCreator;
19 -import com.fasterxml.jackson.annotation.JsonIgnoreProperties;  
20 import com.fasterxml.jackson.annotation.JsonProperty; 19 import com.fasterxml.jackson.annotation.JsonProperty;
21 import lombok.Getter; 20 import lombok.Getter;
  21 +import lombok.ToString;
22 import org.thingsboard.server.common.data.page.PageData; 22 import org.thingsboard.server.common.data.page.PageData;
23 import org.thingsboard.server.common.data.query.EntityData; 23 import org.thingsboard.server.common.data.query.EntityData;
24 import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode; 24 import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode;
25 25
26 import java.util.List; 26 import java.util.List;
27 27
28 - 28 +@ToString
29 public class EntityDataUpdate extends DataUpdate<EntityData> { 29 public class EntityDataUpdate extends DataUpdate<EntityData> {
30 30
31 @Getter 31 @Getter
@@ -41,8 +41,8 @@ public class EntityDataUpdate extends DataUpdate<EntityData> { @@ -41,8 +41,8 @@ public class EntityDataUpdate extends DataUpdate<EntityData> {
41 } 41 }
42 42
43 @Override 43 @Override
44 - public DataUpdateType getDataUpdateType() {  
45 - return DataUpdateType.ENTITY_DATA; 44 + public CmdUpdateType getCmdUpdateType() {
  45 + return CmdUpdateType.ENTITY_DATA;
46 } 46 }
47 47
48 @JsonCreator 48 @JsonCreator
@@ -15,8 +15,6 @@ @@ -15,8 +15,6 @@
15 */ 15 */
16 package org.thingsboard.server.service.telemetry.cmd.v2; 16 package org.thingsboard.server.service.telemetry.cmd.v2;
17 17
18 -import lombok.Data;  
19 -  
20 public interface UnsubscribeCmd { 18 public interface UnsubscribeCmd {
21 19
22 int getCmdId(); 20 int getCmdId();
@@ -30,7 +30,7 @@ @@ -30,7 +30,7 @@
30 <!-- <logger name="org.thingsboard.server.service.queue" level="TRACE" />--> 30 <!-- <logger name="org.thingsboard.server.service.queue" level="TRACE" />-->
31 <!-- <logger name="org.thingsboard.server.service.transport" level="TRACE" />--> 31 <!-- <logger name="org.thingsboard.server.service.transport" level="TRACE" />-->
32 <!-- <logger name="org.thingsboard.server.queue.memory.InMemoryStorage" level="DEBUG" />--> 32 <!-- <logger name="org.thingsboard.server.queue.memory.InMemoryStorage" level="DEBUG" />-->
33 - 33 +<!-- <logger name="org.thingsboard.server.service.ttl.AbstractCleanUpService" level="DEBUG" />-->
34 34
35 <!-- <logger name="org.thingsboard.server.service.subscription" level="TRACE"/>--> 35 <!-- <logger name="org.thingsboard.server.service.subscription" level="TRACE"/>-->
36 <!-- <logger name="org.thingsboard.server.service.telemetry" level="TRACE"/>--> 36 <!-- <logger name="org.thingsboard.server.service.telemetry" level="TRACE"/>-->
@@ -322,6 +322,9 @@ actors: @@ -322,6 +322,9 @@ actors:
322 cache: 322 cache:
323 # caffeine or redis 323 # caffeine or redis
324 type: "${CACHE_TYPE:caffeine}" 324 type: "${CACHE_TYPE:caffeine}"
  325 + attributes:
  326 + # 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'
  327 + enabled: "${CACHE_ATTRIBUTES_ENABLED:true}"
325 328
326 caffeine: 329 caffeine:
327 specs: 330 specs:
@@ -355,6 +358,9 @@ caffeine: @@ -355,6 +358,9 @@ caffeine:
355 deviceProfiles: 358 deviceProfiles:
356 timeToLiveInMinutes: 1440 359 timeToLiveInMinutes: 1440
357 maxSize: 0 360 maxSize: 0
  361 + attributes:
  362 + timeToLiveInMinutes: 1440
  363 + maxSize: 100000
358 364
359 redis: 365 redis:
360 # standalone or cluster 366 # standalone or cluster
@@ -539,6 +545,7 @@ transport: @@ -539,6 +545,7 @@ transport:
539 http: 545 http:
540 enabled: "${HTTP_ENABLED:true}" 546 enabled: "${HTTP_ENABLED:true}"
541 request_timeout: "${HTTP_REQUEST_TIMEOUT:60000}" 547 request_timeout: "${HTTP_REQUEST_TIMEOUT:60000}"
  548 + max_request_timeout: "${HTTP_MAX_REQUEST_TIMEOUT:300000}"
542 # Local MQTT transport parameters 549 # Local MQTT transport parameters
543 mqtt: 550 mqtt:
544 # Enable/disable mqtt transport protocol. 551 # Enable/disable mqtt transport protocol.
@@ -44,6 +44,7 @@ import org.thingsboard.server.common.data.query.EntityDataSortOrder; @@ -44,6 +44,7 @@ import org.thingsboard.server.common.data.query.EntityDataSortOrder;
44 import org.thingsboard.server.common.data.query.EntityKey; 44 import org.thingsboard.server.common.data.query.EntityKey;
45 import org.thingsboard.server.common.data.query.EntityKeyType; 45 import org.thingsboard.server.common.data.query.EntityKeyType;
46 import org.thingsboard.server.common.data.query.EntityListFilter; 46 import org.thingsboard.server.common.data.query.EntityListFilter;
  47 +import org.thingsboard.server.common.data.query.EntityTypeFilter;
47 import org.thingsboard.server.common.data.query.FilterPredicateValue; 48 import org.thingsboard.server.common.data.query.FilterPredicateValue;
48 import org.thingsboard.server.common.data.query.KeyFilter; 49 import org.thingsboard.server.common.data.query.KeyFilter;
49 import org.thingsboard.server.common.data.query.NumericFilterPredicate; 50 import org.thingsboard.server.common.data.query.NumericFilterPredicate;
@@ -132,6 +133,14 @@ public abstract class BaseEntityQueryControllerTest extends AbstractControllerTe @@ -132,6 +133,14 @@ public abstract class BaseEntityQueryControllerTest extends AbstractControllerTe
132 133
133 count = doPostWithResponse("/api/entitiesQuery/count", countQuery, Long.class); 134 count = doPostWithResponse("/api/entitiesQuery/count", countQuery, Long.class);
134 Assert.assertEquals(97, count.longValue()); 135 Assert.assertEquals(97, count.longValue());
  136 +
  137 + EntityTypeFilter filter2 = new EntityTypeFilter();
  138 + filter2.setEntityType(EntityType.DEVICE);
  139 +
  140 + EntityCountQuery countQuery2 = new EntityCountQuery(filter2);
  141 +
  142 + Long count2 = doPostWithResponse("/api/entitiesQuery/count", countQuery2, Long.class);
  143 + Assert.assertEquals(97, count2.longValue());
135 } 144 }
136 145
137 @Test 146 @Test
@@ -198,11 +207,31 @@ public abstract class BaseEntityQueryControllerTest extends AbstractControllerTe @@ -198,11 +207,31 @@ public abstract class BaseEntityQueryControllerTest extends AbstractControllerTe
198 Assert.assertEquals(11, data.getTotalElements()); 207 Assert.assertEquals(11, data.getTotalElements());
199 Assert.assertEquals("Device19", data.getData().get(0).getLatest().get(EntityKeyType.ENTITY_FIELD).get("name").getValue()); 208 Assert.assertEquals("Device19", data.getData().get(0).getLatest().get(EntityKeyType.ENTITY_FIELD).get("name").getValue());
200 209
  210 +
  211 + EntityTypeFilter filter2 = new EntityTypeFilter();
  212 + filter2.setEntityType(EntityType.DEVICE);
  213 +
  214 + EntityDataSortOrder sortOrder2 = new EntityDataSortOrder(
  215 + new EntityKey(EntityKeyType.ENTITY_FIELD, "createdTime"), EntityDataSortOrder.Direction.ASC
  216 + );
  217 + EntityDataPageLink pageLink2 = new EntityDataPageLink(10, 0, null, sortOrder2);
  218 + List<EntityKey> entityFields2 = Collections.singletonList(new EntityKey(EntityKeyType.ENTITY_FIELD, "name"));
  219 +
  220 + EntityDataQuery query2 = new EntityDataQuery(filter2, pageLink2, entityFields2, null, null);
  221 +
  222 + PageData<EntityData> data2 =
  223 + doPostWithTypedResponse("/api/entitiesQuery/find", query2, new TypeReference<PageData<EntityData>>() {
  224 + });
  225 +
  226 + Assert.assertEquals(97, data2.getTotalElements());
  227 + Assert.assertEquals(10, data2.getTotalPages());
  228 + Assert.assertTrue(data2.hasNext());
  229 + Assert.assertEquals(10, data2.getData().size());
  230 +
201 } 231 }
202 232
203 @Test 233 @Test
204 public void testFindEntityDataByQueryWithAttributes() throws Exception { 234 public void testFindEntityDataByQueryWithAttributes() throws Exception {
205 -  
206 List<Device> devices = new ArrayList<>(); 235 List<Device> devices = new ArrayList<>();
207 List<Long> temperatures = new ArrayList<>(); 236 List<Long> temperatures = new ArrayList<>();
208 List<Long> highTemperatures = new ArrayList<>(); 237 List<Long> highTemperatures = new ArrayList<>();
@@ -35,16 +35,23 @@ import org.thingsboard.server.common.data.kv.LongDataEntry; @@ -35,16 +35,23 @@ import org.thingsboard.server.common.data.kv.LongDataEntry;
35 import org.thingsboard.server.common.data.kv.TsKvEntry; 35 import org.thingsboard.server.common.data.kv.TsKvEntry;
36 import org.thingsboard.server.common.data.page.PageData; 36 import org.thingsboard.server.common.data.page.PageData;
37 import org.thingsboard.server.common.data.query.DeviceTypeFilter; 37 import org.thingsboard.server.common.data.query.DeviceTypeFilter;
  38 +import org.thingsboard.server.common.data.query.EntityCountQuery;
38 import org.thingsboard.server.common.data.query.EntityData; 39 import org.thingsboard.server.common.data.query.EntityData;
39 import org.thingsboard.server.common.data.query.EntityDataPageLink; 40 import org.thingsboard.server.common.data.query.EntityDataPageLink;
40 import org.thingsboard.server.common.data.query.EntityDataQuery; 41 import org.thingsboard.server.common.data.query.EntityDataQuery;
41 import org.thingsboard.server.common.data.query.EntityKey; 42 import org.thingsboard.server.common.data.query.EntityKey;
42 import org.thingsboard.server.common.data.query.EntityKeyType; 43 import org.thingsboard.server.common.data.query.EntityKeyType;
  44 +import org.thingsboard.server.common.data.query.EntityKeyValueType;
  45 +import org.thingsboard.server.common.data.query.FilterPredicateValue;
  46 +import org.thingsboard.server.common.data.query.KeyFilter;
  47 +import org.thingsboard.server.common.data.query.NumericFilterPredicate;
43 import org.thingsboard.server.common.data.query.TsValue; 48 import org.thingsboard.server.common.data.query.TsValue;
44 import org.thingsboard.server.common.data.security.Authority; 49 import org.thingsboard.server.common.data.security.Authority;
45 import org.thingsboard.server.service.subscription.TbAttributeSubscriptionScope; 50 import org.thingsboard.server.service.subscription.TbAttributeSubscriptionScope;
46 import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; 51 import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
47 import org.thingsboard.server.service.telemetry.cmd.TelemetryPluginCmdsWrapper; 52 import org.thingsboard.server.service.telemetry.cmd.TelemetryPluginCmdsWrapper;
  53 +import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountCmd;
  54 +import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountUpdate;
48 import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd; 55 import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd;
49 import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate; 56 import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate;
50 import org.thingsboard.server.service.telemetry.cmd.v2.EntityHistoryCmd; 57 import org.thingsboard.server.service.telemetry.cmd.v2.EntityHistoryCmd;
@@ -244,6 +251,98 @@ public class BaseWebsocketApiTest extends AbstractWebsocketTest { @@ -244,6 +251,98 @@ public class BaseWebsocketApiTest extends AbstractWebsocketTest {
244 } 251 }
245 252
246 @Test 253 @Test
  254 + public void testEntityCountWsCmd() throws Exception {
  255 + Device device = new Device();
  256 + device.setName("Device");
  257 + device.setType("default");
  258 + device.setLabel("testLabel" + (int) (Math.random() * 1000));
  259 + device = doPost("/api/device", device, Device.class);
  260 +
  261 + AttributeKvEntry dataPoint1 = new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("temperature", 42L));
  262 + sendAttributes(device, TbAttributeSubscriptionScope.SERVER_SCOPE, Collections.singletonList(dataPoint1));
  263 +
  264 + DeviceTypeFilter dtf1 = new DeviceTypeFilter();
  265 + dtf1.setDeviceNameFilter("D");
  266 + dtf1.setDeviceType("default");
  267 + EntityCountQuery edq1 = new EntityCountQuery(dtf1, Collections.emptyList());
  268 +
  269 + EntityCountCmd cmd1 = new EntityCountCmd(1, edq1);
  270 +
  271 + TelemetryPluginCmdsWrapper wrapper1 = new TelemetryPluginCmdsWrapper();
  272 + wrapper1.setEntityCountCmds(Collections.singletonList(cmd1));
  273 +
  274 + wsClient.send(mapper.writeValueAsString(wrapper1));
  275 + String msg1 = wsClient.waitForReply();
  276 + EntityCountUpdate update1 = mapper.readValue(msg1, EntityCountUpdate.class);
  277 + Assert.assertEquals(1, update1.getCmdId());
  278 + Assert.assertEquals(1, update1.getCount());
  279 +
  280 + DeviceTypeFilter dtf2 = new DeviceTypeFilter();
  281 + dtf2.setDeviceNameFilter("D");
  282 + dtf2.setDeviceType("non-existing-device-type");
  283 + EntityCountQuery edq2 = new EntityCountQuery(dtf2, Collections.emptyList());
  284 +
  285 + EntityCountCmd cmd2 = new EntityCountCmd(2, edq2);
  286 +
  287 + TelemetryPluginCmdsWrapper wrapper2 = new TelemetryPluginCmdsWrapper();
  288 + wrapper2.setEntityCountCmds(Collections.singletonList(cmd2));
  289 + wsClient.send(mapper.writeValueAsString(wrapper2));
  290 +
  291 + String msg2 = wsClient.waitForReply();
  292 + EntityCountUpdate update2 = mapper.readValue(msg2, EntityCountUpdate.class);
  293 + Assert.assertEquals(2, update2.getCmdId());
  294 + Assert.assertEquals(0, update2.getCount());
  295 +
  296 + KeyFilter highTemperatureFilter = new KeyFilter();
  297 + highTemperatureFilter.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "temperature"));
  298 + NumericFilterPredicate predicate = new NumericFilterPredicate();
  299 + predicate.setValue(FilterPredicateValue.fromDouble(40));
  300 + predicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
  301 + highTemperatureFilter.setPredicate(predicate);
  302 + highTemperatureFilter.setValueType(EntityKeyValueType.NUMERIC);
  303 +
  304 + DeviceTypeFilter dtf3 = new DeviceTypeFilter();
  305 + dtf3.setDeviceNameFilter("D");
  306 + dtf3.setDeviceType("default");
  307 + EntityCountQuery edq3 = new EntityCountQuery(dtf3, Collections.singletonList(highTemperatureFilter));
  308 +
  309 + EntityCountCmd cmd3 = new EntityCountCmd(3, edq3);
  310 +
  311 + TelemetryPluginCmdsWrapper wrapper3 = new TelemetryPluginCmdsWrapper();
  312 + wrapper3.setEntityCountCmds(Collections.singletonList(cmd3));
  313 + wsClient.send(mapper.writeValueAsString(wrapper3));
  314 +
  315 + String msg3 = wsClient.waitForReply();
  316 + EntityCountUpdate update3 = mapper.readValue(msg3, EntityCountUpdate.class);
  317 + Assert.assertEquals(3, update3.getCmdId());
  318 + Assert.assertEquals(1, update3.getCount());
  319 +
  320 + KeyFilter highTemperatureFilter2 = new KeyFilter();
  321 + highTemperatureFilter2.setKey(new EntityKey(EntityKeyType.ATTRIBUTE, "temperature"));
  322 + NumericFilterPredicate predicate2 = new NumericFilterPredicate();
  323 + predicate2.setValue(FilterPredicateValue.fromDouble(50));
  324 + predicate2.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
  325 + highTemperatureFilter2.setPredicate(predicate2);
  326 + highTemperatureFilter2.setValueType(EntityKeyValueType.NUMERIC);
  327 +
  328 + DeviceTypeFilter dtf4 = new DeviceTypeFilter();
  329 + dtf4.setDeviceNameFilter("D");
  330 + dtf4.setDeviceType("default");
  331 + EntityCountQuery edq4 = new EntityCountQuery(dtf4, Collections.singletonList(highTemperatureFilter2));
  332 +
  333 + EntityCountCmd cmd4 = new EntityCountCmd(4, edq4);
  334 +
  335 + TelemetryPluginCmdsWrapper wrapper4 = new TelemetryPluginCmdsWrapper();
  336 + wrapper4.setEntityCountCmds(Collections.singletonList(cmd4));
  337 + wsClient.send(mapper.writeValueAsString(wrapper4));
  338 +
  339 + String msg4 = wsClient.waitForReply();
  340 + EntityCountUpdate update4 = mapper.readValue(msg4, EntityCountUpdate.class);
  341 + Assert.assertEquals(4, update4.getCmdId());
  342 + Assert.assertEquals(0, update4.getCount());
  343 + }
  344 +
  345 + @Test
247 public void testEntityDataLatestWidgetFlow() throws Exception { 346 public void testEntityDataLatestWidgetFlow() throws Exception {
248 Device device = new Device(); 347 Device device = new Device();
249 device.setName("Device"); 348 device.setName("Device");
@@ -27,7 +27,7 @@ import java.util.Arrays; @@ -27,7 +27,7 @@ import java.util.Arrays;
27 @RunWith(ClasspathSuite.class) 27 @RunWith(ClasspathSuite.class)
28 @ClasspathSuite.ClassnameFilters({ 28 @ClasspathSuite.ClassnameFilters({
29 // "org.thingsboard.server.controller.sql.WebsocketApiSqlTest", 29 // "org.thingsboard.server.controller.sql.WebsocketApiSqlTest",
30 -// "org.thingsboard.server.controller.sql.TenantProfileControllerSqlTest", 30 +// "org.thingsboard.server.controller.sql.EntityQueryControllerSqlTest",
31 "org.thingsboard.server.controller.sql.*Test", 31 "org.thingsboard.server.controller.sql.*Test",
32 }) 32 })
33 public class ControllerSqlTestSuite { 33 public class ControllerSqlTestSuite {
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>3.3.0-SNAPSHOT</version> 23 + <version>3.2.2-SNAPSHOT</version>
24 <artifactId>common</artifactId> 24 <artifactId>common</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.common</groupId> 26 <groupId>org.thingsboard.common</groupId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>3.3.0-SNAPSHOT</version> 23 + <version>3.2.2-SNAPSHOT</version>
24 <artifactId>common</artifactId> 24 <artifactId>common</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.common</groupId> 26 <groupId>org.thingsboard.common</groupId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>3.3.0-SNAPSHOT</version> 23 + <version>3.2.2-SNAPSHOT</version>
24 <artifactId>common</artifactId> 24 <artifactId>common</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.common</groupId> 26 <groupId>org.thingsboard.common</groupId>
@@ -26,4 +26,5 @@ public class CacheConstants { @@ -26,4 +26,5 @@ public class CacheConstants {
26 public static final String SECURITY_SETTINGS_CACHE = "securitySettings"; 26 public static final String SECURITY_SETTINGS_CACHE = "securitySettings";
27 public static final String TENANT_PROFILE_CACHE = "tenantProfiles"; 27 public static final String TENANT_PROFILE_CACHE = "tenantProfiles";
28 public static final String DEVICE_PROFILE_CACHE = "deviceProfiles"; 28 public static final String DEVICE_PROFILE_CACHE = "deviceProfiles";
  29 + public static final String ATTRIBUTES_CACHE = "attributes";
29 } 30 }
@@ -19,7 +19,7 @@ import lombok.Data; @@ -19,7 +19,7 @@ import lombok.Data;
19 import org.thingsboard.server.common.data.EntityType; 19 import org.thingsboard.server.common.data.EntityType;
20 import org.thingsboard.server.common.data.relation.EntityRelation; 20 import org.thingsboard.server.common.data.relation.EntityRelation;
21 import org.thingsboard.server.common.data.relation.EntityRelationsQuery; 21 import org.thingsboard.server.common.data.relation.EntityRelationsQuery;
22 -import org.thingsboard.server.common.data.relation.EntityTypeFilter; 22 +import org.thingsboard.server.common.data.relation.RelationEntityTypeFilter;
23 import org.thingsboard.server.common.data.relation.RelationsSearchParameters; 23 import org.thingsboard.server.common.data.relation.RelationsSearchParameters;
24 24
25 import java.util.Collections; 25 import java.util.Collections;
@@ -39,7 +39,7 @@ public class AssetSearchQuery { @@ -39,7 +39,7 @@ public class AssetSearchQuery {
39 EntityRelationsQuery query = new EntityRelationsQuery(); 39 EntityRelationsQuery query = new EntityRelationsQuery();
40 query.setParameters(parameters); 40 query.setParameters(parameters);
41 query.setFilters( 41 query.setFilters(
42 - Collections.singletonList(new EntityTypeFilter(relationType == null ? EntityRelation.CONTAINS_TYPE : relationType, 42 + Collections.singletonList(new RelationEntityTypeFilter(relationType == null ? EntityRelation.CONTAINS_TYPE : relationType,
43 Collections.singletonList(EntityType.ASSET)))); 43 Collections.singletonList(EntityType.ASSET))));
44 return query; 44 return query;
45 } 45 }
@@ -19,7 +19,7 @@ import lombok.Data; @@ -19,7 +19,7 @@ import lombok.Data;
19 import org.thingsboard.server.common.data.EntityType; 19 import org.thingsboard.server.common.data.EntityType;
20 import org.thingsboard.server.common.data.relation.EntityRelation; 20 import org.thingsboard.server.common.data.relation.EntityRelation;
21 import org.thingsboard.server.common.data.relation.EntityRelationsQuery; 21 import org.thingsboard.server.common.data.relation.EntityRelationsQuery;
22 -import org.thingsboard.server.common.data.relation.EntityTypeFilter; 22 +import org.thingsboard.server.common.data.relation.RelationEntityTypeFilter;
23 import org.thingsboard.server.common.data.relation.RelationsSearchParameters; 23 import org.thingsboard.server.common.data.relation.RelationsSearchParameters;
24 24
25 import java.util.Collections; 25 import java.util.Collections;
@@ -36,7 +36,7 @@ public class DeviceSearchQuery { @@ -36,7 +36,7 @@ public class DeviceSearchQuery {
36 EntityRelationsQuery query = new EntityRelationsQuery(); 36 EntityRelationsQuery query = new EntityRelationsQuery();
37 query.setParameters(parameters); 37 query.setParameters(parameters);
38 query.setFilters( 38 query.setFilters(
39 - Collections.singletonList(new EntityTypeFilter(relationType == null ? EntityRelation.CONTAINS_TYPE : relationType, 39 + Collections.singletonList(new RelationEntityTypeFilter(relationType == null ? EntityRelation.CONTAINS_TYPE : relationType,
40 Collections.singletonList(EntityType.DEVICE)))); 40 Collections.singletonList(EntityType.DEVICE))));
41 return query; 41 return query;
42 } 42 }
@@ -26,7 +26,7 @@ import java.util.concurrent.TimeUnit; @@ -26,7 +26,7 @@ import java.util.concurrent.TimeUnit;
26 @JsonIgnoreProperties(ignoreUnknown = true) 26 @JsonIgnoreProperties(ignoreUnknown = true)
27 public class AlarmCondition { 27 public class AlarmCondition {
28 28
29 - private List<KeyFilter> condition; 29 + private List<AlarmConditionFilter> condition;
30 private AlarmConditionSpec spec; 30 private AlarmConditionSpec spec;
31 31
32 } 32 }
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.device.profile;
  17 +
  18 +import lombok.Data;
  19 +import org.thingsboard.server.common.data.query.EntityKeyValueType;
  20 +import org.thingsboard.server.common.data.query.KeyFilterPredicate;
  21 +
  22 +@Data
  23 +public class AlarmConditionFilter {
  24 +
  25 + private AlarmConditionFilterKey key;
  26 + private EntityKeyValueType valueType;
  27 + private Object value;
  28 + private KeyFilterPredicate predicate;
  29 +
  30 +}
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.device.profile;
  17 +
  18 +import lombok.Data;
  19 +
  20 +@Data
  21 +public class AlarmConditionFilterKey {
  22 +
  23 + private final AlarmConditionKeyType type;
  24 + private final String key;
  25 +
  26 +}
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.device.profile;
  17 +
  18 +public enum AlarmConditionKeyType {
  19 + ATTRIBUTE,
  20 + TIME_SERIES,
  21 + ENTITY_FIELD,
  22 + CONSTANT
  23 +}
@@ -19,7 +19,7 @@ import lombok.Data; @@ -19,7 +19,7 @@ import lombok.Data;
19 import org.thingsboard.server.common.data.EntityType; 19 import org.thingsboard.server.common.data.EntityType;
20 import org.thingsboard.server.common.data.relation.EntityRelation; 20 import org.thingsboard.server.common.data.relation.EntityRelation;
21 import org.thingsboard.server.common.data.relation.EntityRelationsQuery; 21 import org.thingsboard.server.common.data.relation.EntityRelationsQuery;
22 -import org.thingsboard.server.common.data.relation.EntityTypeFilter; 22 +import org.thingsboard.server.common.data.relation.RelationEntityTypeFilter;
23 import org.thingsboard.server.common.data.relation.RelationsSearchParameters; 23 import org.thingsboard.server.common.data.relation.RelationsSearchParameters;
24 24
25 import java.util.Collections; 25 import java.util.Collections;
@@ -36,7 +36,7 @@ public class EntityViewSearchQuery { @@ -36,7 +36,7 @@ public class EntityViewSearchQuery {
36 EntityRelationsQuery query = new EntityRelationsQuery(); 36 EntityRelationsQuery query = new EntityRelationsQuery();
37 query.setParameters(parameters); 37 query.setParameters(parameters);
38 query.setFilters( 38 query.setFilters(
39 - Collections.singletonList(new EntityTypeFilter(relationType == null ? EntityRelation.CONTAINS_TYPE : relationType, 39 + Collections.singletonList(new RelationEntityTypeFilter(relationType == null ? EntityRelation.CONTAINS_TYPE : relationType,
40 Collections.singletonList(EntityType.ENTITY_VIEW)))); 40 Collections.singletonList(EntityType.ENTITY_VIEW))));
41 return query; 41 return query;
42 } 42 }
@@ -29,15 +29,13 @@ public abstract class AbstractDataQuery<T extends EntityDataPageLink> extends En @@ -29,15 +29,13 @@ public abstract class AbstractDataQuery<T extends EntityDataPageLink> extends En
29 protected List<EntityKey> entityFields; 29 protected List<EntityKey> entityFields;
30 @Getter 30 @Getter
31 protected List<EntityKey> latestValues; 31 protected List<EntityKey> latestValues;
32 - @Getter  
33 - protected List<KeyFilter> keyFilters;  
34 32
35 public AbstractDataQuery() { 33 public AbstractDataQuery() {
36 super(); 34 super();
37 } 35 }
38 36
39 - public AbstractDataQuery(EntityFilter entityFilter) {  
40 - super(entityFilter); 37 + public AbstractDataQuery(EntityFilter entityFilter, List<KeyFilter> keyFilters) {
  38 + super(entityFilter, keyFilters);
41 } 39 }
42 40
43 public AbstractDataQuery(EntityFilter entityFilter, 41 public AbstractDataQuery(EntityFilter entityFilter,
@@ -45,11 +43,10 @@ public abstract class AbstractDataQuery<T extends EntityDataPageLink> extends En @@ -45,11 +43,10 @@ public abstract class AbstractDataQuery<T extends EntityDataPageLink> extends En
45 List<EntityKey> entityFields, 43 List<EntityKey> entityFields,
46 List<EntityKey> latestValues, 44 List<EntityKey> latestValues,
47 List<KeyFilter> keyFilters) { 45 List<KeyFilter> keyFilters) {
48 - super(entityFilter); 46 + super(entityFilter, keyFilters);
49 this.pageLink = pageLink; 47 this.pageLink = pageLink;
50 this.entityFields = entityFields; 48 this.entityFields = entityFields;
51 this.latestValues = latestValues; 49 this.latestValues = latestValues;
52 - this.keyFilters = keyFilters;  
53 } 50 }
54 51
55 } 52 }
@@ -30,8 +30,8 @@ public class AlarmDataQuery extends AbstractDataQuery<AlarmDataPageLink> { @@ -30,8 +30,8 @@ public class AlarmDataQuery extends AbstractDataQuery<AlarmDataPageLink> {
30 public AlarmDataQuery() { 30 public AlarmDataQuery() {
31 } 31 }
32 32
33 - public AlarmDataQuery(EntityFilter entityFilter) {  
34 - super(entityFilter); 33 + public AlarmDataQuery(EntityFilter entityFilter, List<KeyFilter> keyFilters) {
  34 + super(entityFilter, keyFilters);
35 } 35 }
36 36
37 public AlarmDataQuery(EntityFilter entityFilter, AlarmDataPageLink pageLink, List<EntityKey> entityFields, List<EntityKey> latestValues, List<KeyFilter> keyFilters, List<EntityKey> alarmFields) { 37 public AlarmDataQuery(EntityFilter entityFilter, AlarmDataPageLink pageLink, List<EntityKey> entityFields, List<EntityKey> latestValues, List<KeyFilter> keyFilters, List<EntityKey> alarmFields) {
@@ -17,14 +17,26 @@ package org.thingsboard.server.common.data.query; @@ -17,14 +17,26 @@ package org.thingsboard.server.common.data.query;
17 17
18 import lombok.Getter; 18 import lombok.Getter;
19 19
  20 +import java.util.Collections;
  21 +import java.util.List;
  22 +
20 public class EntityCountQuery { 23 public class EntityCountQuery {
21 24
22 @Getter 25 @Getter
23 private EntityFilter entityFilter; 26 private EntityFilter entityFilter;
24 27
25 - public EntityCountQuery() {} 28 + @Getter
  29 + protected List<KeyFilter> keyFilters;
  30 +
  31 + public EntityCountQuery() {
  32 + }
26 33
27 public EntityCountQuery(EntityFilter entityFilter) { 34 public EntityCountQuery(EntityFilter entityFilter) {
  35 + this(entityFilter, Collections.emptyList());
  36 + }
  37 +
  38 + public EntityCountQuery(EntityFilter entityFilter, List<KeyFilter> keyFilters) {
28 this.entityFilter = entityFilter; 39 this.entityFilter = entityFilter;
  40 + this.keyFilters = keyFilters;
29 } 41 }
30 } 42 }
@@ -27,8 +27,8 @@ public class EntityDataQuery extends AbstractDataQuery<EntityDataPageLink> { @@ -27,8 +27,8 @@ public class EntityDataQuery extends AbstractDataQuery<EntityDataPageLink> {
27 public EntityDataQuery() { 27 public EntityDataQuery() {
28 } 28 }
29 29
30 - public EntityDataQuery(EntityFilter entityFilter) {  
31 - super(entityFilter); 30 + public EntityDataQuery(EntityFilter entityFilter, List<KeyFilter> keyFilters) {
  31 + super(entityFilter, keyFilters);
32 } 32 }
33 33
34 public EntityDataQuery(EntityFilter entityFilter, EntityDataPageLink pageLink, List<EntityKey> entityFields, List<EntityKey> latestValues, List<KeyFilter> keyFilters) { 34 public EntityDataQuery(EntityFilter entityFilter, EntityDataPageLink pageLink, List<EntityKey> entityFields, List<EntityKey> latestValues, List<KeyFilter> keyFilters) {
@@ -29,6 +29,7 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo; @@ -29,6 +29,7 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo;
29 @JsonSubTypes.Type(value = SingleEntityFilter.class, name = "singleEntity"), 29 @JsonSubTypes.Type(value = SingleEntityFilter.class, name = "singleEntity"),
30 @JsonSubTypes.Type(value = EntityListFilter.class, name = "entityList"), 30 @JsonSubTypes.Type(value = EntityListFilter.class, name = "entityList"),
31 @JsonSubTypes.Type(value = EntityNameFilter.class, name = "entityName"), 31 @JsonSubTypes.Type(value = EntityNameFilter.class, name = "entityName"),
  32 + @JsonSubTypes.Type(value = EntityTypeFilter.class, name = "entityType"),
32 @JsonSubTypes.Type(value = AssetTypeFilter.class, name = "assetType"), 33 @JsonSubTypes.Type(value = AssetTypeFilter.class, name = "assetType"),
33 @JsonSubTypes.Type(value = DeviceTypeFilter.class, name = "deviceType"), 34 @JsonSubTypes.Type(value = DeviceTypeFilter.class, name = "deviceType"),
34 @JsonSubTypes.Type(value = EntityViewTypeFilter.class, name = "entityViewType"), 35 @JsonSubTypes.Type(value = EntityViewTypeFilter.class, name = "entityViewType"),
@@ -19,6 +19,7 @@ public enum EntityFilterType { @@ -19,6 +19,7 @@ public enum EntityFilterType {
19 SINGLE_ENTITY("singleEntity"), 19 SINGLE_ENTITY("singleEntity"),
20 ENTITY_LIST("entityList"), 20 ENTITY_LIST("entityList"),
21 ENTITY_NAME("entityName"), 21 ENTITY_NAME("entityName"),
  22 + ENTITY_TYPE("entityType"),
22 ASSET_TYPE("assetType"), 23 ASSET_TYPE("assetType"),
23 DEVICE_TYPE("deviceType"), 24 DEVICE_TYPE("deviceType"),
24 ENTITY_VIEW_TYPE("entityViewType"), 25 ENTITY_VIEW_TYPE("entityViewType"),
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.query;
  17 +
  18 +import lombok.Data;
  19 +import org.thingsboard.server.common.data.EntityType;
  20 +
  21 +@Data
  22 +public class EntityTypeFilter implements EntityFilter {
  23 + @Override
  24 + public EntityFilterType getType() {
  25 + return EntityFilterType.ENTITY_TYPE;
  26 + }
  27 +
  28 + private EntityType entityType;
  29 +
  30 +}
@@ -18,8 +18,7 @@ package org.thingsboard.server.common.data.query; @@ -18,8 +18,7 @@ package org.thingsboard.server.common.data.query;
18 import lombok.Data; 18 import lombok.Data;
19 import org.thingsboard.server.common.data.id.EntityId; 19 import org.thingsboard.server.common.data.id.EntityId;
20 import org.thingsboard.server.common.data.relation.EntitySearchDirection; 20 import org.thingsboard.server.common.data.relation.EntitySearchDirection;
21 -import org.thingsboard.server.common.data.relation.EntityTypeFilter;  
22 -import org.thingsboard.server.common.data.relation.RelationTypeGroup; 21 +import org.thingsboard.server.common.data.relation.RelationEntityTypeFilter;
23 22
24 import java.util.List; 23 import java.util.List;
25 24
@@ -33,7 +32,7 @@ public class RelationsQueryFilter implements EntityFilter { @@ -33,7 +32,7 @@ public class RelationsQueryFilter implements EntityFilter {
33 32
34 private EntityId rootEntity; 33 private EntityId rootEntity;
35 private EntitySearchDirection direction; 34 private EntitySearchDirection direction;
36 - private List<EntityTypeFilter> filters; 35 + private List<RelationEntityTypeFilter> filters;
37 private int maxLevel; 36 private int maxLevel;
38 private boolean fetchLastLevelOnly; 37 private boolean fetchLastLevelOnly;
39 38
@@ -26,6 +26,6 @@ import java.util.List; @@ -26,6 +26,6 @@ import java.util.List;
26 public class EntityRelationsQuery { 26 public class EntityRelationsQuery {
27 27
28 private RelationsSearchParameters parameters; 28 private RelationsSearchParameters parameters;
29 - private List<EntityTypeFilter> filters; 29 + private List<RelationEntityTypeFilter> filters;
30 30
31 } 31 }
common/data/src/main/java/org/thingsboard/server/common/data/relation/RelationEntityTypeFilter.java renamed from common/data/src/main/java/org/thingsboard/server/common/data/relation/EntityTypeFilter.java
@@ -26,7 +26,7 @@ import java.util.List; @@ -26,7 +26,7 @@ import java.util.List;
26 */ 26 */
27 @Data 27 @Data
28 @AllArgsConstructor 28 @AllArgsConstructor
29 -public class EntityTypeFilter { 29 +public class RelationEntityTypeFilter {
30 30
31 private String relationType; 31 private String relationType;
32 32
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>3.3.0-SNAPSHOT</version> 23 + <version>3.2.2-SNAPSHOT</version>
24 <artifactId>common</artifactId> 24 <artifactId>common</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.common</groupId> 26 <groupId>org.thingsboard.common</groupId>
@@ -120,7 +120,7 @@ public final class TbMsg implements Serializable { @@ -120,7 +120,7 @@ public final class TbMsg implements Serializable {
120 private TbMsg(String queueName, UUID id, long ts, String type, EntityId originator, TbMsgMetaData metaData, TbMsgDataType dataType, String data, 120 private TbMsg(String queueName, UUID id, long ts, String type, EntityId originator, TbMsgMetaData metaData, TbMsgDataType dataType, String data,
121 RuleChainId ruleChainId, RuleNodeId ruleNodeId, int ruleNodeExecCounter, TbMsgCallback callback) { 121 RuleChainId ruleChainId, RuleNodeId ruleNodeId, int ruleNodeExecCounter, TbMsgCallback callback) {
122 this.id = id; 122 this.id = id;
123 - this.queueName = queueName; 123 + this.queueName = queueName != null ? queueName : ServiceQueue.MAIN;
124 if (ts > 0) { 124 if (ts > 0) {
125 this.ts = ts; 125 this.ts = ts;
126 } else { 126 } else {
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>3.3.0-SNAPSHOT</version> 23 + <version>3.2.2-SNAPSHOT</version>
24 <artifactId>thingsboard</artifactId> 24 <artifactId>thingsboard</artifactId>
25 </parent> 25 </parent>
26 <artifactId>common</artifactId> 26 <artifactId>common</artifactId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>3.3.0-SNAPSHOT</version> 23 + <version>3.2.2-SNAPSHOT</version>
24 <artifactId>common</artifactId> 24 <artifactId>common</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.common</groupId> 26 <groupId>org.thingsboard.common</groupId>
@@ -22,7 +22,7 @@ @@ -22,7 +22,7 @@
22 <modelVersion>4.0.0</modelVersion> 22 <modelVersion>4.0.0</modelVersion>
23 <parent> 23 <parent>
24 <groupId>org.thingsboard</groupId> 24 <groupId>org.thingsboard</groupId>
25 - <version>3.3.0-SNAPSHOT</version> 25 + <version>3.2.2-SNAPSHOT</version>
26 <artifactId>common</artifactId> 26 <artifactId>common</artifactId>
27 </parent> 27 </parent>
28 <groupId>org.thingsboard.common</groupId> 28 <groupId>org.thingsboard.common</groupId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard.common</groupId> 22 <groupId>org.thingsboard.common</groupId>
23 - <version>3.3.0-SNAPSHOT</version> 23 + <version>3.2.2-SNAPSHOT</version>
24 <artifactId>transport</artifactId> 24 <artifactId>transport</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.common.transport</groupId> 26 <groupId>org.thingsboard.common.transport</groupId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard.common</groupId> 22 <groupId>org.thingsboard.common</groupId>
23 - <version>3.3.0-SNAPSHOT</version> 23 + <version>3.2.2-SNAPSHOT</version>
24 <artifactId>transport</artifactId> 24 <artifactId>transport</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.common.transport</groupId> 26 <groupId>org.thingsboard.common.transport</groupId>
@@ -17,9 +17,13 @@ package org.thingsboard.server.transport.http; @@ -17,9 +17,13 @@ package org.thingsboard.server.transport.http;
17 17
18 import lombok.Getter; 18 import lombok.Getter;
19 import lombok.extern.slf4j.Slf4j; 19 import lombok.extern.slf4j.Slf4j;
  20 +import org.apache.coyote.ProtocolHandler;
  21 +import org.apache.coyote.http11.Http11NioProtocol;
20 import org.springframework.beans.factory.annotation.Value; 22 import org.springframework.beans.factory.annotation.Value;
21 import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; 23 import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
22 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 24 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
  25 +import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer;
  26 +import org.springframework.context.annotation.Bean;
23 import org.springframework.stereotype.Component; 27 import org.springframework.stereotype.Component;
24 import org.thingsboard.server.common.transport.TransportContext; 28 import org.thingsboard.server.common.transport.TransportContext;
25 29
@@ -37,4 +41,18 @@ public class HttpTransportContext extends TransportContext { @@ -37,4 +41,18 @@ public class HttpTransportContext extends TransportContext {
37 @Value("${transport.http.request_timeout}") 41 @Value("${transport.http.request_timeout}")
38 private long defaultTimeout; 42 private long defaultTimeout;
39 43
  44 + @Getter
  45 + @Value("${transport.http.max_request_timeout}")
  46 + private long maxRequestTimeout;
  47 +
  48 + @Bean
  49 + public TomcatConnectorCustomizer tomcatAsyncTimeoutConnectorCustomizer() {
  50 + return connector -> {
  51 + ProtocolHandler handler = connector.getProtocolHandler();
  52 + if (handler instanceof Http11NioProtocol) {
  53 + log.trace("Setting async max request timeout {}", maxRequestTimeout);
  54 + connector.setAsyncTimeout(maxRequestTimeout);
  55 + }
  56 + };
  57 + }
40 } 58 }
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard.common</groupId> 22 <groupId>org.thingsboard.common</groupId>
23 - <version>3.3.0-SNAPSHOT</version> 23 + <version>3.2.2-SNAPSHOT</version>
24 <artifactId>transport</artifactId> 24 <artifactId>transport</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.common.transport</groupId> 26 <groupId>org.thingsboard.common.transport</groupId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>3.3.0-SNAPSHOT</version> 23 + <version>3.2.2-SNAPSHOT</version>
24 <artifactId>common</artifactId> 24 <artifactId>common</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.common</groupId> 26 <groupId>org.thingsboard.common</groupId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard.common</groupId> 22 <groupId>org.thingsboard.common</groupId>
23 - <version>3.3.0-SNAPSHOT</version> 23 + <version>3.2.2-SNAPSHOT</version>
24 <artifactId>transport</artifactId> 24 <artifactId>transport</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.common.transport</groupId> 26 <groupId>org.thingsboard.common.transport</groupId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>3.3.0-SNAPSHOT</version> 23 + <version>3.2.2-SNAPSHOT</version>
24 <artifactId>common</artifactId> 24 <artifactId>common</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.common</groupId> 26 <groupId>org.thingsboard.common</groupId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>3.3.0-SNAPSHOT</version> 23 + <version>3.2.2-SNAPSHOT</version>
24 <artifactId>thingsboard</artifactId> 24 <artifactId>thingsboard</artifactId>
25 </parent> 25 </parent>
26 <artifactId>dao</artifactId> 26 <artifactId>dao</artifactId>
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.attributes;
  17 +
  18 +import lombok.AllArgsConstructor;
  19 +import lombok.EqualsAndHashCode;
  20 +import lombok.Getter;
  21 +import org.thingsboard.server.common.data.id.EntityId;
  22 +
  23 +import java.io.Serializable;
  24 +
  25 +@EqualsAndHashCode
  26 +@Getter
  27 +@AllArgsConstructor
  28 +public class AttributeCacheKey implements Serializable {
  29 + private static final long serialVersionUID = 2013369077925351881L;
  30 +
  31 + private final String scope;
  32 + private final EntityId entityId;
  33 + private final String key;
  34 +
  35 + @Override
  36 + public String toString() {
  37 + return entityId + "_" + scope + "_" + key;
  38 + }
  39 +}
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.attributes;
  17 +
  18 +import org.thingsboard.server.common.data.id.EntityId;
  19 +import org.thingsboard.server.common.data.kv.AttributeKvEntry;
  20 +import org.thingsboard.server.dao.exception.IncorrectParameterException;
  21 +import org.thingsboard.server.dao.service.Validator;
  22 +
  23 +public class AttributeUtils {
  24 + public static void validate(EntityId id, String scope) {
  25 + Validator.validateId(id.getId(), "Incorrect id " + id);
  26 + Validator.validateString(scope, "Incorrect scope " + scope);
  27 + }
  28 +
  29 + public static void validate(AttributeKvEntry kvEntry) {
  30 + if (kvEntry == null) {
  31 + throw new IncorrectParameterException("Key value entry can't be null");
  32 + } else if (kvEntry.getDataType() == null) {
  33 + throw new IncorrectParameterException("Incorrect kvEntry. Data type can't be null");
  34 + } else {
  35 + Validator.validateString(kvEntry.getKey(), "Incorrect kvEntry. Key can't be empty");
  36 + Validator.validatePositiveNumber(kvEntry.getLastUpdateTs(), "Incorrect last update ts. Ts should be positive");
  37 + }
  38 + }
  39 +}
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.attributes;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
  20 +import org.springframework.cache.Cache;
  21 +import org.springframework.cache.CacheManager;
  22 +import org.springframework.context.annotation.Primary;
  23 +import org.springframework.stereotype.Service;
  24 +import org.thingsboard.server.common.data.kv.AttributeKvEntry;
  25 +
  26 +import static org.thingsboard.server.common.data.CacheConstants.ATTRIBUTES_CACHE;
  27 +
  28 +@Service
  29 +@ConditionalOnProperty(prefix = "cache.attributes", value = "enabled", havingValue = "true")
  30 +@Primary
  31 +@Slf4j
  32 +public class AttributesCacheWrapper {
  33 + private final Cache attributesCache;
  34 +
  35 + public AttributesCacheWrapper(CacheManager cacheManager) {
  36 + this.attributesCache = cacheManager.getCache(ATTRIBUTES_CACHE);
  37 + }
  38 +
  39 + public Cache.ValueWrapper get(AttributeCacheKey attributeCacheKey) {
  40 + try {
  41 + return attributesCache.get(attributeCacheKey);
  42 + } catch (Exception e) {
  43 + log.debug("Failed to retrieve element from cache for key {}. Reason - {}.", attributeCacheKey, e.getMessage());
  44 + return null;
  45 + }
  46 + }
  47 +
  48 + public void put(AttributeCacheKey attributeCacheKey, AttributeKvEntry attributeKvEntry) {
  49 + try {
  50 + attributesCache.put(attributeCacheKey, attributeKvEntry);
  51 + } catch (Exception e) {
  52 + log.debug("Failed to put element from cache for key {}. Reason - {}.", attributeCacheKey, e.getMessage());
  53 + }
  54 + }
  55 +
  56 + public void evict(AttributeCacheKey attributeCacheKey) {
  57 + try {
  58 + attributesCache.evict(attributeCacheKey);
  59 + } catch (Exception e) {
  60 + log.debug("Failed to evict element from cache for key {}. Reason - {}.", attributeCacheKey, e.getMessage());
  61 + }
  62 + }
  63 +}
@@ -15,31 +15,39 @@ @@ -15,31 +15,39 @@
15 */ 15 */
16 package org.thingsboard.server.dao.attributes; 16 package org.thingsboard.server.dao.attributes;
17 17
18 -import com.google.common.collect.Lists;  
19 import com.google.common.util.concurrent.Futures; 18 import com.google.common.util.concurrent.Futures;
20 import com.google.common.util.concurrent.ListenableFuture; 19 import com.google.common.util.concurrent.ListenableFuture;
21 -import org.springframework.beans.factory.annotation.Autowired; 20 +import lombok.extern.slf4j.Slf4j;
  21 +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
  22 +import org.springframework.context.annotation.Primary;
22 import org.springframework.stereotype.Service; 23 import org.springframework.stereotype.Service;
23 import org.thingsboard.server.common.data.EntityType; 24 import org.thingsboard.server.common.data.EntityType;
24 import org.thingsboard.server.common.data.id.DeviceProfileId; 25 import org.thingsboard.server.common.data.id.DeviceProfileId;
25 import org.thingsboard.server.common.data.id.EntityId; 26 import org.thingsboard.server.common.data.id.EntityId;
26 import org.thingsboard.server.common.data.id.TenantId; 27 import org.thingsboard.server.common.data.id.TenantId;
27 import org.thingsboard.server.common.data.kv.AttributeKvEntry; 28 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
28 -import org.thingsboard.server.dao.exception.IncorrectParameterException;  
29 import org.thingsboard.server.dao.service.Validator; 29 import org.thingsboard.server.dao.service.Validator;
30 30
31 import java.util.Collection; 31 import java.util.Collection;
32 import java.util.List; 32 import java.util.List;
33 import java.util.Optional; 33 import java.util.Optional;
  34 +import java.util.stream.Collectors;
  35 +
  36 +import static org.thingsboard.server.dao.attributes.AttributeUtils.validate;
34 37
35 /** 38 /**
36 * @author Andrew Shvayka 39 * @author Andrew Shvayka
37 */ 40 */
38 @Service 41 @Service
  42 +@ConditionalOnProperty(prefix = "cache.attributes", value = "enabled", havingValue = "false", matchIfMissing = true)
  43 +@Primary
  44 +@Slf4j
39 public class BaseAttributesService implements AttributesService { 45 public class BaseAttributesService implements AttributesService {
  46 + private final AttributesDao attributesDao;
40 47
41 - @Autowired  
42 - private AttributesDao attributesDao; 48 + public BaseAttributesService(AttributesDao attributesDao) {
  49 + this.attributesDao = attributesDao;
  50 + }
43 51
44 @Override 52 @Override
45 public ListenableFuture<Optional<AttributeKvEntry>> find(TenantId tenantId, EntityId entityId, String scope, String attributeKey) { 53 public ListenableFuture<Optional<AttributeKvEntry>> find(TenantId tenantId, EntityId entityId, String scope, String attributeKey) {
@@ -75,33 +83,14 @@ public class BaseAttributesService implements AttributesService { @@ -75,33 +83,14 @@ public class BaseAttributesService implements AttributesService {
75 public ListenableFuture<List<Void>> save(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes) { 83 public ListenableFuture<List<Void>> save(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes) {
76 validate(entityId, scope); 84 validate(entityId, scope);
77 attributes.forEach(attribute -> validate(attribute)); 85 attributes.forEach(attribute -> validate(attribute));
78 - List<ListenableFuture<Void>> futures = Lists.newArrayListWithExpectedSize(attributes.size());  
79 - for (AttributeKvEntry attribute : attributes) {  
80 - futures.add(attributesDao.save(tenantId, entityId, scope, attribute));  
81 - }  
82 - return Futures.allAsList(futures); 86 +
  87 + List<ListenableFuture<Void>> saveFutures = attributes.stream().map(attribute -> attributesDao.save(tenantId, entityId, scope, attribute)).collect(Collectors.toList());
  88 + return Futures.allAsList(saveFutures);
83 } 89 }
84 90
85 @Override 91 @Override
86 - public ListenableFuture<List<Void>> removeAll(TenantId tenantId, EntityId entityId, String scope, List<String> keys) { 92 + public ListenableFuture<List<Void>> removeAll(TenantId tenantId, EntityId entityId, String scope, List<String> attributeKeys) {
87 validate(entityId, scope); 93 validate(entityId, scope);
88 - return attributesDao.removeAll(tenantId, entityId, scope, keys);  
89 - }  
90 -  
91 - private static void validate(EntityId id, String scope) {  
92 - Validator.validateId(id.getId(), "Incorrect id " + id);  
93 - Validator.validateString(scope, "Incorrect scope " + scope);  
94 - }  
95 -  
96 - private static void validate(AttributeKvEntry kvEntry) {  
97 - if (kvEntry == null) {  
98 - throw new IncorrectParameterException("Key value entry can't be null");  
99 - } else if (kvEntry.getDataType() == null) {  
100 - throw new IncorrectParameterException("Incorrect kvEntry. Data type can't be null");  
101 - } else {  
102 - Validator.validateString(kvEntry.getKey(), "Incorrect kvEntry. Key can't be empty");  
103 - Validator.validatePositiveNumber(kvEntry.getLastUpdateTs(), "Incorrect last update ts. Ts should be positive");  
104 - } 94 + return attributesDao.removeAll(tenantId, entityId, scope, attributeKeys);
105 } 95 }
106 -  
107 } 96 }
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.attributes;
  17 +
  18 +import com.google.common.util.concurrent.Futures;
  19 +import com.google.common.util.concurrent.ListenableFuture;
  20 +import com.google.common.util.concurrent.MoreExecutors;
  21 +import lombok.extern.slf4j.Slf4j;
  22 +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
  23 +import org.springframework.cache.Cache;
  24 +import org.springframework.cache.CacheManager;
  25 +import org.springframework.context.annotation.Primary;
  26 +import org.springframework.stereotype.Service;
  27 +import org.thingsboard.server.common.data.EntityType;
  28 +import org.thingsboard.server.common.data.id.DeviceProfileId;
  29 +import org.thingsboard.server.common.data.id.EntityId;
  30 +import org.thingsboard.server.common.data.id.TenantId;
  31 +import org.thingsboard.server.common.data.kv.AttributeKvEntry;
  32 +import org.thingsboard.server.common.data.kv.KvEntry;
  33 +import org.thingsboard.server.common.stats.DefaultCounter;
  34 +import org.thingsboard.server.common.stats.StatsFactory;
  35 +import org.thingsboard.server.dao.service.Validator;
  36 +
  37 +import java.util.ArrayList;
  38 +import java.util.Collection;
  39 +import java.util.HashMap;
  40 +import java.util.HashSet;
  41 +import java.util.List;
  42 +import java.util.Map;
  43 +import java.util.Objects;
  44 +import java.util.Optional;
  45 +import java.util.Set;
  46 +import java.util.stream.Collectors;
  47 +
  48 +import static org.thingsboard.server.common.data.CacheConstants.ATTRIBUTES_CACHE;
  49 +import static org.thingsboard.server.dao.attributes.AttributeUtils.validate;
  50 +
  51 +@Service
  52 +@ConditionalOnProperty(prefix = "cache.attributes", value = "enabled", havingValue = "true")
  53 +@Primary
  54 +@Slf4j
  55 +public class CachedAttributesService implements AttributesService {
  56 + private static final String STATS_NAME = "attributes.cache";
  57 +
  58 + private final AttributesDao attributesDao;
  59 + private final AttributesCacheWrapper cacheWrapper;
  60 + private final DefaultCounter hitCounter;
  61 + private final DefaultCounter missCounter;
  62 +
  63 + public CachedAttributesService(AttributesDao attributesDao,
  64 + AttributesCacheWrapper cacheWrapper,
  65 + StatsFactory statsFactory) {
  66 + this.attributesDao = attributesDao;
  67 + this.cacheWrapper = cacheWrapper;
  68 +
  69 + this.hitCounter = statsFactory.createDefaultCounter(STATS_NAME, "result", "hit");
  70 + this.missCounter = statsFactory.createDefaultCounter(STATS_NAME, "result", "miss");
  71 + }
  72 +
  73 + @Override
  74 + public ListenableFuture<Optional<AttributeKvEntry>> find(TenantId tenantId, EntityId entityId, String scope, String attributeKey) {
  75 + validate(entityId, scope);
  76 + Validator.validateString(attributeKey, "Incorrect attribute key " + attributeKey);
  77 +
  78 + AttributeCacheKey attributeCacheKey = new AttributeCacheKey(scope, entityId, attributeKey);
  79 + Cache.ValueWrapper cachedAttributeValue = cacheWrapper.get(attributeCacheKey);
  80 + if (cachedAttributeValue != null) {
  81 + hitCounter.increment();
  82 + AttributeKvEntry cachedAttributeKvEntry = (AttributeKvEntry) cachedAttributeValue.get();
  83 + return Futures.immediateFuture(Optional.ofNullable(cachedAttributeKvEntry));
  84 + } else {
  85 + missCounter.increment();
  86 + ListenableFuture<Optional<AttributeKvEntry>> result = attributesDao.find(tenantId, entityId, scope, attributeKey);
  87 + return Futures.transform(result, foundAttrKvEntry -> {
  88 + // TODO: think if it's a good idea to store 'empty' attributes
  89 + cacheWrapper.put(attributeCacheKey, foundAttrKvEntry.orElse(null));
  90 + return foundAttrKvEntry;
  91 + }, MoreExecutors.directExecutor());
  92 + }
  93 + }
  94 +
  95 + @Override
  96 + public ListenableFuture<List<AttributeKvEntry>> find(TenantId tenantId, EntityId entityId, String scope, Collection<String> attributeKeys) {
  97 + validate(entityId, scope);
  98 + attributeKeys.forEach(attributeKey -> Validator.validateString(attributeKey, "Incorrect attribute key " + attributeKey));
  99 +
  100 + Map<String, Cache.ValueWrapper> wrappedCachedAttributes = findCachedAttributes(entityId, scope, attributeKeys);
  101 +
  102 + List<AttributeKvEntry> cachedAttributes = wrappedCachedAttributes.values().stream()
  103 + .map(wrappedCachedAttribute -> (AttributeKvEntry) wrappedCachedAttribute.get())
  104 + .filter(Objects::nonNull)
  105 + .collect(Collectors.toList());
  106 + if (wrappedCachedAttributes.size() == attributeKeys.size()) {
  107 + return Futures.immediateFuture(cachedAttributes);
  108 + }
  109 +
  110 + Set<String> notFoundAttributeKeys = new HashSet<>(attributeKeys);
  111 + notFoundAttributeKeys.removeAll(wrappedCachedAttributes.keySet());
  112 +
  113 + ListenableFuture<List<AttributeKvEntry>> result = attributesDao.find(tenantId, entityId, scope, notFoundAttributeKeys);
  114 + return Futures.transform(result, foundInDbAttributes -> mergeDbAndCacheAttributes(entityId, scope, cachedAttributes, notFoundAttributeKeys, foundInDbAttributes), MoreExecutors.directExecutor());
  115 +
  116 + }
  117 +
  118 + private Map<String, Cache.ValueWrapper> findCachedAttributes(EntityId entityId, String scope, Collection<String> attributeKeys) {
  119 + Map<String, Cache.ValueWrapper> cachedAttributes = new HashMap<>();
  120 + for (String attributeKey : attributeKeys) {
  121 + Cache.ValueWrapper cachedAttributeValue = cacheWrapper.get(new AttributeCacheKey(scope, entityId, attributeKey));
  122 + if (cachedAttributeValue != null) {
  123 + hitCounter.increment();
  124 + cachedAttributes.put(attributeKey, cachedAttributeValue);
  125 + } else {
  126 + missCounter.increment();
  127 + }
  128 + }
  129 + return cachedAttributes;
  130 + }
  131 +
  132 + private List<AttributeKvEntry> mergeDbAndCacheAttributes(EntityId entityId, String scope, List<AttributeKvEntry> cachedAttributes, Set<String> notFoundAttributeKeys, List<AttributeKvEntry> foundInDbAttributes) {
  133 + for (AttributeKvEntry foundInDbAttribute : foundInDbAttributes) {
  134 + AttributeCacheKey attributeCacheKey = new AttributeCacheKey(scope, entityId, foundInDbAttribute.getKey());
  135 + cacheWrapper.put(attributeCacheKey, foundInDbAttribute);
  136 + notFoundAttributeKeys.remove(foundInDbAttribute.getKey());
  137 + }
  138 + for (String key : notFoundAttributeKeys){
  139 + cacheWrapper.put(new AttributeCacheKey(scope, entityId, key), null);
  140 + }
  141 + List<AttributeKvEntry> mergedAttributes = new ArrayList<>(cachedAttributes);
  142 + mergedAttributes.addAll(foundInDbAttributes);
  143 + return mergedAttributes;
  144 + }
  145 +
  146 + @Override
  147 + public ListenableFuture<List<AttributeKvEntry>> findAll(TenantId tenantId, EntityId entityId, String scope) {
  148 + validate(entityId, scope);
  149 + return attributesDao.findAll(tenantId, entityId, scope);
  150 + }
  151 +
  152 + @Override
  153 + public List<String> findAllKeysByDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId) {
  154 + return attributesDao.findAllKeysByDeviceProfileId(tenantId, deviceProfileId);
  155 + }
  156 +
  157 + @Override
  158 + public List<String> findAllKeysByEntityIds(TenantId tenantId, EntityType entityType, List<EntityId> entityIds) {
  159 + return attributesDao.findAllKeysByEntityIds(tenantId, entityType, entityIds);
  160 + }
  161 +
  162 + @Override
  163 + public ListenableFuture<List<Void>> save(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes) {
  164 + validate(entityId, scope);
  165 + attributes.forEach(AttributeUtils::validate);
  166 +
  167 + List<ListenableFuture<Void>> saveFutures = attributes.stream().map(attribute -> attributesDao.save(tenantId, entityId, scope, attribute)).collect(Collectors.toList());
  168 + ListenableFuture<List<Void>> future = Futures.allAsList(saveFutures);
  169 +
  170 + // TODO: can do if (attributesCache.get() != null) attributesCache.put() instead, but will be more twice more requests to cache
  171 + List<String> attributeKeys = attributes.stream().map(KvEntry::getKey).collect(Collectors.toList());
  172 + future.addListener(() -> evictAttributesFromCache(tenantId, entityId, scope, attributeKeys), MoreExecutors.directExecutor());
  173 + return future;
  174 + }
  175 +
  176 + @Override
  177 + public ListenableFuture<List<Void>> removeAll(TenantId tenantId, EntityId entityId, String scope, List<String> attributeKeys) {
  178 + validate(entityId, scope);
  179 + ListenableFuture<List<Void>> future = attributesDao.removeAll(tenantId, entityId, scope, attributeKeys);
  180 + future.addListener(() -> evictAttributesFromCache(tenantId, entityId, scope, attributeKeys), MoreExecutors.directExecutor());
  181 + return future;
  182 + }
  183 +
  184 + private void evictAttributesFromCache(TenantId tenantId, EntityId entityId, String scope, List<String> attributeKeys) {
  185 + try {
  186 + for (String attributeKey : attributeKeys) {
  187 + cacheWrapper.evict(new AttributeCacheKey(scope, entityId, attributeKey));
  188 + }
  189 + } catch (Exception e) {
  190 + log.error("[{}][{}] Failed to remove values from cache.", tenantId, entityId, e);
  191 + }
  192 + }
  193 +}
@@ -35,7 +35,7 @@ import org.thingsboard.server.common.data.relation.EntityRelation; @@ -35,7 +35,7 @@ import org.thingsboard.server.common.data.relation.EntityRelation;
35 import org.thingsboard.server.common.data.relation.EntityRelationInfo; 35 import org.thingsboard.server.common.data.relation.EntityRelationInfo;
36 import org.thingsboard.server.common.data.relation.EntityRelationsQuery; 36 import org.thingsboard.server.common.data.relation.EntityRelationsQuery;
37 import org.thingsboard.server.common.data.relation.EntitySearchDirection; 37 import org.thingsboard.server.common.data.relation.EntitySearchDirection;
38 -import org.thingsboard.server.common.data.relation.EntityTypeFilter; 38 +import org.thingsboard.server.common.data.relation.RelationEntityTypeFilter;
39 import org.thingsboard.server.common.data.relation.RelationTypeGroup; 39 import org.thingsboard.server.common.data.relation.RelationTypeGroup;
40 import org.thingsboard.server.common.data.relation.RelationsSearchParameters; 40 import org.thingsboard.server.common.data.relation.RelationsSearchParameters;
41 import org.thingsboard.server.dao.entity.EntityService; 41 import org.thingsboard.server.dao.entity.EntityService;
@@ -457,7 +457,7 @@ public class BaseRelationService implements RelationService { @@ -457,7 +457,7 @@ public class BaseRelationService implements RelationService {
457 //boolean fetchLastLevelOnly = true; 457 //boolean fetchLastLevelOnly = true;
458 log.trace("Executing findByQuery [{}]", query); 458 log.trace("Executing findByQuery [{}]", query);
459 RelationsSearchParameters params = query.getParameters(); 459 RelationsSearchParameters params = query.getParameters();
460 - final List<EntityTypeFilter> filters = query.getFilters(); 460 + final List<RelationEntityTypeFilter> filters = query.getFilters();
461 if (filters == null || filters.isEmpty()) { 461 if (filters == null || filters.isEmpty()) {
462 log.debug("Filters are not set [{}]", query); 462 log.debug("Filters are not set [{}]", query);
463 } 463 }
@@ -575,8 +575,8 @@ public class BaseRelationService implements RelationService { @@ -575,8 +575,8 @@ public class BaseRelationService implements RelationService {
575 }; 575 };
576 } 576 }
577 577
578 - private boolean matchFilters(List<EntityTypeFilter> filters, EntityRelation relation, EntitySearchDirection direction) {  
579 - for (EntityTypeFilter filter : filters) { 578 + private boolean matchFilters(List<RelationEntityTypeFilter> filters, EntityRelation relation, EntitySearchDirection direction) {
  579 + for (RelationEntityTypeFilter filter : filters) {
580 if (match(filter, relation, direction)) { 580 if (match(filter, relation, direction)) {
581 return true; 581 return true;
582 } 582 }
@@ -584,7 +584,7 @@ public class BaseRelationService implements RelationService { @@ -584,7 +584,7 @@ public class BaseRelationService implements RelationService {
584 return false; 584 return false;
585 } 585 }
586 586
587 - private boolean match(EntityTypeFilter filter, EntityRelation relation, EntitySearchDirection direction) { 587 + private boolean match(RelationEntityTypeFilter filter, EntityRelation relation, EntitySearchDirection direction) {
588 if (StringUtils.isEmpty(filter.getRelationType()) || filter.getRelationType().equals(relation.getType())) { 588 if (StringUtils.isEmpty(filter.getRelationType()) || filter.getRelationType().equals(relation.getType())) {
589 if (filter.getEntityTypes() == null || filter.getEntityTypes().isEmpty()) { 589 if (filter.getEntityTypes() == null || filter.getEntityTypes().isEmpty()) {
590 return true; 590 return true;
@@ -40,12 +40,13 @@ import org.thingsboard.server.common.data.query.EntityKeyType; @@ -40,12 +40,13 @@ import org.thingsboard.server.common.data.query.EntityKeyType;
40 import org.thingsboard.server.common.data.query.EntityListFilter; 40 import org.thingsboard.server.common.data.query.EntityListFilter;
41 import org.thingsboard.server.common.data.query.EntityNameFilter; 41 import org.thingsboard.server.common.data.query.EntityNameFilter;
42 import org.thingsboard.server.common.data.query.EntitySearchQueryFilter; 42 import org.thingsboard.server.common.data.query.EntitySearchQueryFilter;
  43 +import org.thingsboard.server.common.data.query.EntityTypeFilter;
43 import org.thingsboard.server.common.data.query.EntityViewSearchQueryFilter; 44 import org.thingsboard.server.common.data.query.EntityViewSearchQueryFilter;
44 import org.thingsboard.server.common.data.query.EntityViewTypeFilter; 45 import org.thingsboard.server.common.data.query.EntityViewTypeFilter;
45 import org.thingsboard.server.common.data.query.RelationsQueryFilter; 46 import org.thingsboard.server.common.data.query.RelationsQueryFilter;
46 import org.thingsboard.server.common.data.query.SingleEntityFilter; 47 import org.thingsboard.server.common.data.query.SingleEntityFilter;
47 import org.thingsboard.server.common.data.relation.EntitySearchDirection; 48 import org.thingsboard.server.common.data.relation.EntitySearchDirection;
48 -import org.thingsboard.server.common.data.relation.EntityTypeFilter; 49 +import org.thingsboard.server.common.data.relation.RelationEntityTypeFilter;
49 50
50 import java.util.Arrays; 51 import java.util.Arrays;
51 import java.util.Collections; 52 import java.util.Collections;
@@ -249,18 +250,70 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { @@ -249,18 +250,70 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
249 public long countEntitiesByQuery(TenantId tenantId, CustomerId customerId, EntityCountQuery query) { 250 public long countEntitiesByQuery(TenantId tenantId, CustomerId customerId, EntityCountQuery query) {
250 EntityType entityType = resolveEntityType(query.getEntityFilter()); 251 EntityType entityType = resolveEntityType(query.getEntityFilter());
251 QueryContext ctx = new QueryContext(new QuerySecurityContext(tenantId, customerId, entityType)); 252 QueryContext ctx = new QueryContext(new QuerySecurityContext(tenantId, customerId, entityType));
252 - ctx.append("select count(e.id) from ");  
253 - ctx.append(addEntityTableQuery(ctx, query.getEntityFilter()));  
254 - ctx.append(" e where ");  
255 - ctx.append(buildEntityWhere(ctx, query.getEntityFilter(), Collections.emptyList()));  
256 - return transactionTemplate.execute(status -> {  
257 - long startTs = System.currentTimeMillis();  
258 - try {  
259 - return jdbcTemplate.queryForObject(ctx.getQuery(), ctx, Long.class);  
260 - } finally {  
261 - queryLog.logQuery(ctx, ctx.getQuery(), System.currentTimeMillis() - startTs); 253 + if (query.getKeyFilters() == null || query.getKeyFilters().isEmpty()) {
  254 + ctx.append("select count(e.id) from ");
  255 + ctx.append(addEntityTableQuery(ctx, query.getEntityFilter()));
  256 + ctx.append(" e where ");
  257 + ctx.append(buildEntityWhere(ctx, query.getEntityFilter(), Collections.emptyList()));
  258 + return transactionTemplate.execute(status -> {
  259 + long startTs = System.currentTimeMillis();
  260 + try {
  261 + return jdbcTemplate.queryForObject(ctx.getQuery(), ctx, Long.class);
  262 + } finally {
  263 + queryLog.logQuery(ctx, ctx.getQuery(), System.currentTimeMillis() - startTs);
  264 + }
  265 + });
  266 + } else {
  267 + List<EntityKeyMapping> mappings = EntityKeyMapping.prepareEntityCountKeyMapping(query);
  268 +
  269 + List<EntityKeyMapping> selectionMapping = mappings.stream().filter(EntityKeyMapping::isSelection)
  270 + .collect(Collectors.toList());
  271 + List<EntityKeyMapping> entityFieldsSelectionMapping = selectionMapping.stream().filter(mapping -> !mapping.isLatest())
  272 + .collect(Collectors.toList());
  273 +
  274 + List<EntityKeyMapping> filterMapping = mappings.stream().filter(EntityKeyMapping::hasFilter)
  275 + .collect(Collectors.toList());
  276 + List<EntityKeyMapping> entityFieldsFiltersMapping = filterMapping.stream().filter(mapping -> !mapping.isLatest())
  277 + .collect(Collectors.toList());
  278 +
  279 + List<EntityKeyMapping> allLatestMappings = mappings.stream().filter(EntityKeyMapping::isLatest)
  280 + .collect(Collectors.toList());
  281 +
  282 +
  283 + String entityWhereClause = DefaultEntityQueryRepository.this.buildEntityWhere(ctx, query.getEntityFilter(), entityFieldsFiltersMapping);
  284 + String latestJoinsCnt = EntityKeyMapping.buildLatestJoins(ctx, query.getEntityFilter(), entityType, allLatestMappings, true);
  285 + String entityFieldsSelection = EntityKeyMapping.buildSelections(entityFieldsSelectionMapping, query.getEntityFilter().getType(), entityType);
  286 + String entityTypeStr;
  287 + if (query.getEntityFilter().getType().equals(EntityFilterType.RELATIONS_QUERY)) {
  288 + entityTypeStr = "e.entity_type";
  289 + } else {
  290 + entityTypeStr = "'" + entityType.name() + "'";
262 } 291 }
263 - }); 292 + if (!StringUtils.isEmpty(entityFieldsSelection)) {
  293 + entityFieldsSelection = String.format("e.id id, %s entity_type, %s", entityTypeStr, entityFieldsSelection);
  294 + } else {
  295 + entityFieldsSelection = String.format("e.id id, %s entity_type", entityTypeStr);
  296 + }
  297 +
  298 + String fromClauseCount = String.format("from (select %s from (select %s from %s e where %s) entities %s ) result %s",
  299 + "entities.*",
  300 + entityFieldsSelection,
  301 + addEntityTableQuery(ctx, query.getEntityFilter()),
  302 + entityWhereClause,
  303 + latestJoinsCnt,
  304 + "");
  305 +
  306 + String countQuery = String.format("select count(id) %s", fromClauseCount);
  307 +
  308 + return transactionTemplate.execute(status -> {
  309 + long startTs = System.currentTimeMillis();
  310 + try {
  311 + return jdbcTemplate.queryForObject(countQuery, ctx, Long.class);
  312 + } finally {
  313 + queryLog.logQuery(ctx, ctx.getQuery(), System.currentTimeMillis() - startTs);
  314 + }
  315 + });
  316 + }
264 } 317 }
265 318
266 @Override 319 @Override
@@ -436,6 +489,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { @@ -436,6 +489,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
436 case ASSET_SEARCH_QUERY: 489 case ASSET_SEARCH_QUERY:
437 case ENTITY_VIEW_SEARCH_QUERY: 490 case ENTITY_VIEW_SEARCH_QUERY:
438 case API_USAGE_STATE: 491 case API_USAGE_STATE:
  492 + case ENTITY_TYPE:
439 return ""; 493 return "";
440 default: 494 default:
441 throw new RuntimeException("Not implemented!"); 495 throw new RuntimeException("Not implemented!");
@@ -521,7 +575,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { @@ -521,7 +575,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
521 boolean single = entityFilter.getFilters() != null && entityFilter.getFilters().size() == 1; 575 boolean single = entityFilter.getFilters() != null && entityFilter.getFilters().size() == 1;
522 if (entityFilter.getFilters() != null && !entityFilter.getFilters().isEmpty()) { 576 if (entityFilter.getFilters() != null && !entityFilter.getFilters().isEmpty()) {
523 int entityTypeFilterIdx = 0; 577 int entityTypeFilterIdx = 0;
524 - for (EntityTypeFilter etf : entityFilter.getFilters()) { 578 + for (RelationEntityTypeFilter etf : entityFilter.getFilters()) {
525 String etfCondition = buildEtfCondition(ctx, etf, entityFilter.getDirection(), entityTypeFilterIdx++); 579 String etfCondition = buildEtfCondition(ctx, etf, entityFilter.getDirection(), entityTypeFilterIdx++);
526 if (!etfCondition.isEmpty()) { 580 if (!etfCondition.isEmpty()) {
527 if (noConditions) { 581 if (noConditions) {
@@ -570,7 +624,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { @@ -570,7 +624,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
570 return "( " + selectFields + from + ")"; 624 return "( " + selectFields + from + ")";
571 } 625 }
572 626
573 - private String buildEtfCondition(QueryContext ctx, EntityTypeFilter etf, EntitySearchDirection direction, int entityTypeFilterIdx) { 627 + private String buildEtfCondition(QueryContext ctx, RelationEntityTypeFilter etf, EntitySearchDirection direction, int entityTypeFilterIdx) {
574 StringBuilder whereFilter = new StringBuilder(); 628 StringBuilder whereFilter = new StringBuilder();
575 String relationType = etf.getRelationType(); 629 String relationType = etf.getRelationType();
576 List<EntityType> entityTypes = etf.getEntityTypes(); 630 List<EntityType> entityTypes = etf.getEntityTypes();
@@ -676,6 +730,8 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { @@ -676,6 +730,8 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
676 return ((EntityListFilter) entityFilter).getEntityType(); 730 return ((EntityListFilter) entityFilter).getEntityType();
677 case ENTITY_NAME: 731 case ENTITY_NAME:
678 return ((EntityNameFilter) entityFilter).getEntityType(); 732 return ((EntityNameFilter) entityFilter).getEntityType();
  733 + case ENTITY_TYPE:
  734 + return ((EntityTypeFilter) entityFilter).getEntityType();
679 case ASSET_TYPE: 735 case ASSET_TYPE:
680 case ASSET_SEARCH_QUERY: 736 case ASSET_SEARCH_QUERY:
681 return EntityType.ASSET; 737 return EntityType.ASSET;
@@ -21,6 +21,7 @@ import org.thingsboard.server.common.data.DataConstants; @@ -21,6 +21,7 @@ import org.thingsboard.server.common.data.DataConstants;
21 import org.thingsboard.server.common.data.EntityType; 21 import org.thingsboard.server.common.data.EntityType;
22 import org.thingsboard.server.common.data.query.BooleanFilterPredicate; 22 import org.thingsboard.server.common.data.query.BooleanFilterPredicate;
23 import org.thingsboard.server.common.data.query.ComplexFilterPredicate; 23 import org.thingsboard.server.common.data.query.ComplexFilterPredicate;
  24 +import org.thingsboard.server.common.data.query.EntityCountQuery;
24 import org.thingsboard.server.common.data.query.EntityDataQuery; 25 import org.thingsboard.server.common.data.query.EntityDataQuery;
25 import org.thingsboard.server.common.data.query.EntityDataSortOrder; 26 import org.thingsboard.server.common.data.query.EntityDataSortOrder;
26 import org.thingsboard.server.common.data.query.EntityFilter; 27 import org.thingsboard.server.common.data.query.EntityFilter;
@@ -380,6 +381,30 @@ public class EntityKeyMapping { @@ -380,6 +381,30 @@ public class EntityKeyMapping {
380 return mappings; 381 return mappings;
381 } 382 }
382 383
  384 + public static List<EntityKeyMapping> prepareEntityCountKeyMapping(EntityCountQuery query) {
  385 + Map<EntityKey, List<KeyFilter>> filters =
  386 + query.getKeyFilters() != null ?
  387 + query.getKeyFilters().stream().collect(Collectors.groupingBy(KeyFilter::getKey)) : Collections.emptyMap();
  388 + int index = 2;
  389 + List<EntityKeyMapping> mappings = new ArrayList<>();
  390 + if (!filters.isEmpty()) {
  391 + for (EntityKey filterField : filters.keySet()) {
  392 + EntityKeyMapping mapping = new EntityKeyMapping();
  393 + mapping.setIndex(index);
  394 + mapping.setAlias(String.format("alias%s", index));
  395 + mapping.setKeyFilters(filters.get(filterField));
  396 + mapping.setLatest(!filterField.getType().equals(EntityKeyType.ENTITY_FIELD));
  397 + mapping.setSelection(false);
  398 + mapping.setEntityKey(filterField);
  399 + mappings.add(mapping);
  400 + index += 1;
  401 + }
  402 + }
  403 +
  404 + return mappings;
  405 + }
  406 +
  407 +
383 private String buildAttributeSelection() { 408 private String buildAttributeSelection() {
384 return buildTimeSeriesOrAttrSelection(true); 409 return buildTimeSeriesOrAttrSelection(true);
385 } 410 }
@@ -19,8 +19,10 @@ import com.google.common.util.concurrent.Futures; @@ -19,8 +19,10 @@ import com.google.common.util.concurrent.Futures;
19 import com.google.common.util.concurrent.ListenableFuture; 19 import com.google.common.util.concurrent.ListenableFuture;
20 import com.google.common.util.concurrent.MoreExecutors; 20 import com.google.common.util.concurrent.MoreExecutors;
21 import lombok.extern.slf4j.Slf4j; 21 import lombok.extern.slf4j.Slf4j;
  22 +import org.hibernate.exception.ConstraintViolationException;
22 import org.springframework.beans.factory.annotation.Autowired; 23 import org.springframework.beans.factory.annotation.Autowired;
23 import org.springframework.beans.factory.annotation.Value; 24 import org.springframework.beans.factory.annotation.Value;
  25 +import org.springframework.dao.DataIntegrityViolationException;
24 import org.springframework.stereotype.Component; 26 import org.springframework.stereotype.Component;
25 import org.thingsboard.server.common.data.id.EntityId; 27 import org.thingsboard.server.common.data.id.EntityId;
26 import org.thingsboard.server.common.data.id.TenantId; 28 import org.thingsboard.server.common.data.id.TenantId;
@@ -114,6 +116,14 @@ public class JpaPsqlTimeseriesDao extends AbstractChunkedAggregationTimeseriesDa @@ -114,6 +116,14 @@ public class JpaPsqlTimeseriesDao extends AbstractChunkedAggregationTimeseriesDa
114 partitioningRepository.save(psqlPartition); 116 partitioningRepository.save(psqlPartition);
115 log.trace("Adding partition to Set: {}", psqlPartition); 117 log.trace("Adding partition to Set: {}", psqlPartition);
116 partitions.put(psqlPartition.getStart(), psqlPartition); 118 partitions.put(psqlPartition.getStart(), psqlPartition);
  119 + } catch (DataIntegrityViolationException ex) {
  120 + log.trace("Error occurred during partition save:", ex);
  121 + if (ex.getCause() instanceof ConstraintViolationException) {
  122 + log.warn("Saving partition [{}] rejected. Timeseries data will save to the ts_kv_indefinite (DEFAULT) partition.", psqlPartition.getPartitionDate());
  123 + partitions.put(psqlPartition.getStart(), psqlPartition);
  124 + } else {
  125 + throw new RuntimeException(ex);
  126 + }
117 } finally { 127 } finally {
118 partitionCreationLock.unlock(); 128 partitionCreationLock.unlock();
119 } 129 }
@@ -15,9 +15,6 @@ @@ -15,9 +15,6 @@
15 */ 15 */
16 package org.thingsboard.server.dao.service; 16 package org.thingsboard.server.dao.service;
17 17
18 -import com.fasterxml.jackson.core.JsonProcessingException;  
19 -import com.fasterxml.jackson.databind.JsonMappingException;  
20 -import com.fasterxml.jackson.databind.ObjectMapper;  
21 import com.google.common.util.concurrent.Futures; 18 import com.google.common.util.concurrent.Futures;
22 import com.google.common.util.concurrent.ListenableFuture; 19 import com.google.common.util.concurrent.ListenableFuture;
23 import org.junit.After; 20 import org.junit.After;
@@ -31,25 +28,48 @@ import org.thingsboard.server.common.data.Device; @@ -31,25 +28,48 @@ import org.thingsboard.server.common.data.Device;
31 import org.thingsboard.server.common.data.EntityType; 28 import org.thingsboard.server.common.data.EntityType;
32 import org.thingsboard.server.common.data.Tenant; 29 import org.thingsboard.server.common.data.Tenant;
33 import org.thingsboard.server.common.data.asset.Asset; 30 import org.thingsboard.server.common.data.asset.Asset;
34 -import org.thingsboard.server.common.data.id.*;  
35 -import org.thingsboard.server.common.data.kv.*; 31 +import org.thingsboard.server.common.data.id.CustomerId;
  32 +import org.thingsboard.server.common.data.id.DeviceId;
  33 +import org.thingsboard.server.common.data.id.EntityId;
  34 +import org.thingsboard.server.common.data.id.TenantId;
  35 +import org.thingsboard.server.common.data.kv.AttributeKvEntry;
  36 +import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
  37 +import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
  38 +import org.thingsboard.server.common.data.kv.DoubleDataEntry;
  39 +import org.thingsboard.server.common.data.kv.KvEntry;
  40 +import org.thingsboard.server.common.data.kv.LongDataEntry;
  41 +import org.thingsboard.server.common.data.kv.StringDataEntry;
36 import org.thingsboard.server.common.data.page.PageData; 42 import org.thingsboard.server.common.data.page.PageData;
37 -import org.thingsboard.server.common.data.query.*; 43 +import org.thingsboard.server.common.data.query.AssetSearchQueryFilter;
  44 +import org.thingsboard.server.common.data.query.DeviceSearchQueryFilter;
  45 +import org.thingsboard.server.common.data.query.DeviceTypeFilter;
  46 +import org.thingsboard.server.common.data.query.EntityCountQuery;
  47 +import org.thingsboard.server.common.data.query.EntityData;
  48 +import org.thingsboard.server.common.data.query.EntityDataPageLink;
  49 +import org.thingsboard.server.common.data.query.EntityDataQuery;
  50 +import org.thingsboard.server.common.data.query.EntityDataSortOrder;
  51 +import org.thingsboard.server.common.data.query.EntityKey;
  52 +import org.thingsboard.server.common.data.query.EntityKeyType;
  53 +import org.thingsboard.server.common.data.query.EntityListFilter;
  54 +import org.thingsboard.server.common.data.query.FilterPredicateValue;
  55 +import org.thingsboard.server.common.data.query.KeyFilter;
  56 +import org.thingsboard.server.common.data.query.NumericFilterPredicate;
  57 +import org.thingsboard.server.common.data.query.RelationsQueryFilter;
  58 +import org.thingsboard.server.common.data.query.StringFilterPredicate;
38 import org.thingsboard.server.common.data.relation.EntityRelation; 59 import org.thingsboard.server.common.data.relation.EntityRelation;
39 import org.thingsboard.server.common.data.relation.EntitySearchDirection; 60 import org.thingsboard.server.common.data.relation.EntitySearchDirection;
40 -import org.thingsboard.server.common.data.relation.EntityTypeFilter; 61 +import org.thingsboard.server.common.data.relation.RelationEntityTypeFilter;
41 import org.thingsboard.server.common.data.relation.RelationTypeGroup; 62 import org.thingsboard.server.common.data.relation.RelationTypeGroup;
42 -import org.thingsboard.server.common.data.rule.RuleChain;  
43 -import org.thingsboard.server.common.data.rule.RuleChainMetaData;  
44 -import org.thingsboard.server.common.data.rule.RuleNode;  
45 import org.thingsboard.server.dao.attributes.AttributesService; 63 import org.thingsboard.server.dao.attributes.AttributesService;
46 import org.thingsboard.server.dao.model.sqlts.ts.TsKvEntity; 64 import org.thingsboard.server.dao.model.sqlts.ts.TsKvEntity;
47 -import org.thingsboard.server.dao.rule.RuleChainService;  
48 import org.thingsboard.server.dao.timeseries.TimeseriesService; 65 import org.thingsboard.server.dao.timeseries.TimeseriesService;
49 -import org.thingsboard.server.dao.util.DaoTestUtil;  
50 -import org.thingsboard.server.dao.util.SqlDbType;  
51 66
52 -import java.util.*; 67 +import java.util.ArrayList;
  68 +import java.util.Arrays;
  69 +import java.util.Collections;
  70 +import java.util.Comparator;
  71 +import java.util.List;
  72 +import java.util.Random;
53 import java.util.concurrent.ExecutionException; 73 import java.util.concurrent.ExecutionException;
54 import java.util.stream.Collectors; 74 import java.util.stream.Collectors;
55 75
@@ -140,13 +160,13 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest { @@ -140,13 +160,13 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
140 long count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery); 160 long count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);
141 Assert.assertEquals(30, count); 161 Assert.assertEquals(30, count);
142 162
143 - filter.setFilters(Collections.singletonList(new EntityTypeFilter("Contains", Collections.singletonList(EntityType.DEVICE)))); 163 + filter.setFilters(Collections.singletonList(new RelationEntityTypeFilter("Contains", Collections.singletonList(EntityType.DEVICE))));
144 count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery); 164 count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);
145 Assert.assertEquals(25, count); 165 Assert.assertEquals(25, count);
146 166
147 filter.setRootEntity(devices.get(0).getId()); 167 filter.setRootEntity(devices.get(0).getId());
148 filter.setDirection(EntitySearchDirection.TO); 168 filter.setDirection(EntitySearchDirection.TO);
149 - filter.setFilters(Collections.singletonList(new EntityTypeFilter("Manages", Collections.singletonList(EntityType.TENANT)))); 169 + filter.setFilters(Collections.singletonList(new RelationEntityTypeFilter("Manages", Collections.singletonList(EntityType.TENANT))));
150 count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery); 170 count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);
151 Assert.assertEquals(1, count); 171 Assert.assertEquals(1, count);
152 172
@@ -208,7 +228,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest { @@ -208,7 +228,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
208 RelationsQueryFilter filter = new RelationsQueryFilter(); 228 RelationsQueryFilter filter = new RelationsQueryFilter();
209 filter.setRootEntity(tenantId); 229 filter.setRootEntity(tenantId);
210 filter.setDirection(EntitySearchDirection.FROM); 230 filter.setDirection(EntitySearchDirection.FROM);
211 - filter.setFilters(Collections.singletonList(new EntityTypeFilter("Contains", Collections.singletonList(EntityType.DEVICE)))); 231 + filter.setFilters(Collections.singletonList(new RelationEntityTypeFilter("Contains", Collections.singletonList(EntityType.DEVICE))));
212 232
213 EntityDataSortOrder sortOrder = new EntityDataSortOrder( 233 EntityDataSortOrder sortOrder = new EntityDataSortOrder(
214 new EntityKey(EntityKeyType.ENTITY_FIELD, "createdTime"), EntityDataSortOrder.Direction.ASC 234 new EntityKey(EntityKeyType.ENTITY_FIELD, "createdTime"), EntityDataSortOrder.Direction.ASC
@@ -26,7 +26,7 @@ import org.thingsboard.server.common.data.id.DeviceId; @@ -26,7 +26,7 @@ import org.thingsboard.server.common.data.id.DeviceId;
26 import org.thingsboard.server.common.data.relation.EntityRelation; 26 import org.thingsboard.server.common.data.relation.EntityRelation;
27 import org.thingsboard.server.common.data.relation.EntityRelationsQuery; 27 import org.thingsboard.server.common.data.relation.EntityRelationsQuery;
28 import org.thingsboard.server.common.data.relation.EntitySearchDirection; 28 import org.thingsboard.server.common.data.relation.EntitySearchDirection;
29 -import org.thingsboard.server.common.data.relation.EntityTypeFilter; 29 +import org.thingsboard.server.common.data.relation.RelationEntityTypeFilter;
30 import org.thingsboard.server.common.data.relation.RelationTypeGroup; 30 import org.thingsboard.server.common.data.relation.RelationTypeGroup;
31 import org.thingsboard.server.common.data.relation.RelationsSearchParameters; 31 import org.thingsboard.server.common.data.relation.RelationsSearchParameters;
32 import org.thingsboard.server.dao.exception.DataValidationException; 32 import org.thingsboard.server.dao.exception.DataValidationException;
@@ -221,7 +221,7 @@ public abstract class BaseRelationServiceTest extends AbstractServiceTest { @@ -221,7 +221,7 @@ public abstract class BaseRelationServiceTest extends AbstractServiceTest {
221 221
222 EntityRelationsQuery query = new EntityRelationsQuery(); 222 EntityRelationsQuery query = new EntityRelationsQuery();
223 query.setParameters(new RelationsSearchParameters(assetA, EntitySearchDirection.FROM, -1, false)); 223 query.setParameters(new RelationsSearchParameters(assetA, EntitySearchDirection.FROM, -1, false));
224 - query.setFilters(Collections.singletonList(new EntityTypeFilter(EntityRelation.CONTAINS_TYPE, Collections.singletonList(EntityType.ASSET)))); 224 + query.setFilters(Collections.singletonList(new RelationEntityTypeFilter(EntityRelation.CONTAINS_TYPE, Collections.singletonList(EntityType.ASSET))));
225 List<EntityRelation> relations = relationService.findByQuery(SYSTEM_TENANT_ID, query).get(); 225 List<EntityRelation> relations = relationService.findByQuery(SYSTEM_TENANT_ID, query).get();
226 Assert.assertEquals(3, relations.size()); 226 Assert.assertEquals(3, relations.size());
227 Assert.assertTrue(relations.contains(relationA)); 227 Assert.assertTrue(relations.contains(relationA));
@@ -255,7 +255,7 @@ public abstract class BaseRelationServiceTest extends AbstractServiceTest { @@ -255,7 +255,7 @@ public abstract class BaseRelationServiceTest extends AbstractServiceTest {
255 255
256 EntityRelationsQuery query = new EntityRelationsQuery(); 256 EntityRelationsQuery query = new EntityRelationsQuery();
257 query.setParameters(new RelationsSearchParameters(assetA, EntitySearchDirection.FROM, -1, false)); 257 query.setParameters(new RelationsSearchParameters(assetA, EntitySearchDirection.FROM, -1, false));
258 - query.setFilters(Collections.singletonList(new EntityTypeFilter(EntityRelation.CONTAINS_TYPE, Collections.singletonList(EntityType.ASSET)))); 258 + query.setFilters(Collections.singletonList(new RelationEntityTypeFilter(EntityRelation.CONTAINS_TYPE, Collections.singletonList(EntityType.ASSET))));
259 List<EntityRelation> relations = relationService.findByQuery(SYSTEM_TENANT_ID, query).get(); 259 List<EntityRelation> relations = relationService.findByQuery(SYSTEM_TENANT_ID, query).get();
260 Assert.assertEquals(2, relations.size()); 260 Assert.assertEquals(2, relations.size());
261 Assert.assertTrue(relations.contains(relationAB)); 261 Assert.assertTrue(relations.contains(relationAB));
@@ -21,7 +21,7 @@ @@ -21,7 +21,7 @@
21 21
22 <parent> 22 <parent>
23 <groupId>org.thingsboard</groupId> 23 <groupId>org.thingsboard</groupId>
24 - <version>3.3.0-SNAPSHOT</version> 24 + <version>3.2.2-SNAPSHOT</version>
25 <artifactId>msa</artifactId> 25 <artifactId>msa</artifactId>
26 </parent> 26 </parent>
27 <groupId>org.thingsboard.msa</groupId> 27 <groupId>org.thingsboard.msa</groupId>
1 { 1 {
2 "name": "thingsboard-js-executor", 2 "name": "thingsboard-js-executor",
3 "private": true, 3 "private": true,
4 - "version": "3.3.0", 4 + "version": "3.2.2",
5 "description": "ThingsBoard JavaScript Executor Microservice", 5 "description": "ThingsBoard JavaScript Executor Microservice",
6 "main": "server.js", 6 "main": "server.js",
7 "bin": "server.js", 7 "bin": "server.js",
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>3.3.0-SNAPSHOT</version> 23 + <version>3.2.2-SNAPSHOT</version>
24 <artifactId>msa</artifactId> 24 <artifactId>msa</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.msa</groupId> 26 <groupId>org.thingsboard.msa</groupId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>3.3.0-SNAPSHOT</version> 23 + <version>3.2.2-SNAPSHOT</version>
24 <artifactId>thingsboard</artifactId> 24 <artifactId>thingsboard</artifactId>
25 </parent> 25 </parent>
26 <artifactId>msa</artifactId> 26 <artifactId>msa</artifactId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>3.3.0-SNAPSHOT</version> 23 + <version>3.2.2-SNAPSHOT</version>
24 <artifactId>msa</artifactId> 24 <artifactId>msa</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.msa</groupId> 26 <groupId>org.thingsboard.msa</groupId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>3.3.0-SNAPSHOT</version> 23 + <version>3.2.2-SNAPSHOT</version>
24 <artifactId>msa</artifactId> 24 <artifactId>msa</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.msa</groupId> 26 <groupId>org.thingsboard.msa</groupId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard.msa</groupId> 22 <groupId>org.thingsboard.msa</groupId>
23 - <version>3.3.0-SNAPSHOT</version> 23 + <version>3.2.2-SNAPSHOT</version>
24 <artifactId>transport</artifactId> 24 <artifactId>transport</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.msa.transport</groupId> 26 <groupId>org.thingsboard.msa.transport</groupId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard.msa</groupId> 22 <groupId>org.thingsboard.msa</groupId>
23 - <version>3.3.0-SNAPSHOT</version> 23 + <version>3.2.2-SNAPSHOT</version>
24 <artifactId>transport</artifactId> 24 <artifactId>transport</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.msa.transport</groupId> 26 <groupId>org.thingsboard.msa.transport</groupId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard.msa</groupId> 22 <groupId>org.thingsboard.msa</groupId>
23 - <version>3.3.0-SNAPSHOT</version> 23 + <version>3.2.2-SNAPSHOT</version>
24 <artifactId>transport</artifactId> 24 <artifactId>transport</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.msa.transport</groupId> 26 <groupId>org.thingsboard.msa.transport</groupId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>3.3.0-SNAPSHOT</version> 23 + <version>3.2.2-SNAPSHOT</version>
24 <artifactId>msa</artifactId> 24 <artifactId>msa</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.msa</groupId> 26 <groupId>org.thingsboard.msa</groupId>
1 { 1 {
2 "name": "thingsboard-web-ui", 2 "name": "thingsboard-web-ui",
3 "private": true, 3 "private": true,
4 - "version": "3.3.0", 4 + "version": "3.2.2",
5 "description": "ThingsBoard Web UI Microservice", 5 "description": "ThingsBoard Web UI Microservice",
6 "main": "server.js", 6 "main": "server.js",
7 "bin": "server.js", 7 "bin": "server.js",
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>3.3.0-SNAPSHOT</version> 23 + <version>3.2.2-SNAPSHOT</version>
24 <artifactId>msa</artifactId> 24 <artifactId>msa</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.msa</groupId> 26 <groupId>org.thingsboard.msa</groupId>
@@ -19,11 +19,11 @@ @@ -19,11 +19,11 @@
19 <modelVersion>4.0.0</modelVersion> 19 <modelVersion>4.0.0</modelVersion>
20 <parent> 20 <parent>
21 <groupId>org.thingsboard</groupId> 21 <groupId>org.thingsboard</groupId>
22 - <version>3.3.0-SNAPSHOT</version> 22 + <version>3.2.2-SNAPSHOT</version>
23 <artifactId>thingsboard</artifactId> 23 <artifactId>thingsboard</artifactId>
24 </parent> 24 </parent>
25 <artifactId>netty-mqtt</artifactId> 25 <artifactId>netty-mqtt</artifactId>
26 - <version>3.3.0-SNAPSHOT</version> 26 + <version>3.2.2-SNAPSHOT</version>
27 <packaging>jar</packaging> 27 <packaging>jar</packaging>
28 28
29 <name>Netty MQTT Client</name> 29 <name>Netty MQTT Client</name>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <groupId>org.thingsboard</groupId> 21 <groupId>org.thingsboard</groupId>
22 <artifactId>thingsboard</artifactId> 22 <artifactId>thingsboard</artifactId>
23 - <version>3.3.0-SNAPSHOT</version> 23 + <version>3.2.2-SNAPSHOT</version>
24 <packaging>pom</packaging> 24 <packaging>pom</packaging>
25 25
26 <name>Thingsboard</name> 26 <name>Thingsboard</name>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>3.3.0-SNAPSHOT</version> 23 + <version>3.2.2-SNAPSHOT</version>
24 <artifactId>thingsboard</artifactId> 24 <artifactId>thingsboard</artifactId>
25 </parent> 25 </parent>
26 <artifactId>rest-client</artifactId> 26 <artifactId>rest-client</artifactId>
@@ -2207,7 +2207,7 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { @@ -2207,7 +2207,7 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable {
2207 Map<String, String> params = new HashMap<>(); 2207 Map<String, String> params = new HashMap<>();
2208 addPageLinkToParam(params, pageLink); 2208 addPageLinkToParam(params, pageLink);
2209 return restTemplate.exchange( 2209 return restTemplate.exchange(
2210 - baseURL + "/api/tenantProfiles" + getUrlParams(pageLink), 2210 + baseURL + "/api/tenantProfiles?" + getUrlParams(pageLink),
2211 HttpMethod.GET, 2211 HttpMethod.GET,
2212 HttpEntity.EMPTY, 2212 HttpEntity.EMPTY,
2213 new ParameterizedTypeReference<PageData<TenantProfile>>() { 2213 new ParameterizedTypeReference<PageData<TenantProfile>>() {
@@ -2218,7 +2218,7 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { @@ -2218,7 +2218,7 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable {
2218 Map<String, String> params = new HashMap<>(); 2218 Map<String, String> params = new HashMap<>();
2219 addPageLinkToParam(params, pageLink); 2219 addPageLinkToParam(params, pageLink);
2220 return restTemplate.exchange( 2220 return restTemplate.exchange(
2221 - baseURL + "/api/tenantProfileInfos" + getUrlParams(pageLink), 2221 + baseURL + "/api/tenantProfileInfos?" + getUrlParams(pageLink),
2222 HttpMethod.GET, 2222 HttpMethod.GET,
2223 HttpEntity.EMPTY, 2223 HttpEntity.EMPTY,
2224 new ParameterizedTypeReference<PageData<EntityInfo>>() { 2224 new ParameterizedTypeReference<PageData<EntityInfo>>() {
@@ -2275,7 +2275,7 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { @@ -2275,7 +2275,7 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable {
2275 Map<String, String> params = new HashMap<>(); 2275 Map<String, String> params = new HashMap<>();
2276 addPageLinkToParam(params, pageLink); 2276 addPageLinkToParam(params, pageLink);
2277 return restTemplate.exchange( 2277 return restTemplate.exchange(
2278 - baseURL + "/api/users" + getUrlParams(pageLink), 2278 + baseURL + "/api/users?" + getUrlParams(pageLink),
2279 HttpMethod.GET, 2279 HttpMethod.GET,
2280 HttpEntity.EMPTY, 2280 HttpEntity.EMPTY,
2281 new ParameterizedTypeReference<PageData<User>>() { 2281 new ParameterizedTypeReference<PageData<User>>() {
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>3.3.0-SNAPSHOT</version> 23 + <version>3.2.2-SNAPSHOT</version>
24 <artifactId>thingsboard</artifactId> 24 <artifactId>thingsboard</artifactId>
25 </parent> 25 </parent>
26 <artifactId>rule-engine</artifactId> 26 <artifactId>rule-engine</artifactId>