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 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>3.3.0-SNAPSHOT</version>
  23 + <version>3.2.2-SNAPSHOT</version>
24 24 <artifactId>thingsboard</artifactId>
25 25 </parent>
26 26 <artifactId>application</artifactId>
... ...
... ... @@ -47,7 +47,7 @@
47 47 "resources": [],
48 48 "templateHtml": "<tb-timeseries-table-widget \n [ctx]=\"ctx\">\n</tb-timeseries-table-widget>",
49 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 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 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 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 134 }
135 135 }
136 136 ]
137   -}
\ No newline at end of file
  137 +}
... ...
... ... @@ -84,11 +84,12 @@ BEGIN
84 84 END IF;
85 85 END IF;
86 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 93 END IF;
93 94 END LOOP;
94 95 END IF;
... ...
... ... @@ -37,6 +37,7 @@ import org.thingsboard.server.common.data.Customer;
37 37 import org.thingsboard.server.common.data.DataConstants;
38 38 import org.thingsboard.server.common.data.Device;
39 39 import org.thingsboard.server.common.data.DeviceProfile;
  40 +import org.thingsboard.server.common.data.EntityType;
40 41 import org.thingsboard.server.common.data.TenantProfile;
41 42 import org.thingsboard.server.common.data.alarm.Alarm;
42 43 import org.thingsboard.server.common.data.asset.Asset;
... ... @@ -278,7 +279,21 @@ class DefaultTbContext implements TbContext {
278 279 }
279 280
280 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 299 public TbMsg assetCreatedMsg(Asset asset, RuleNodeId ruleNodeId) {
... ... @@ -286,12 +301,31 @@ class DefaultTbContext implements TbContext {
286 301 }
287 302
288 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 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 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 329 } catch (JsonProcessingException | IllegalArgumentException e) {
296 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 215 User user = userService.findUserById(TenantId.SYS_TENANT_ID, credentials.getUserId());
216 216 UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.USER_NAME, user.getEmail());
217 217 SecurityUser securityUser = new SecurityUser(user, credentials.isEnabled(), principal);
  218 + userService.setUserCredentialsEnabled(user.getTenantId(), user.getId(), true);
218 219 String baseUrl = systemSecurityService.getBaseUrl(user.getTenantId(), user.getCustomerId(), request);
219 220 String loginUrl = String.format("%s/login", baseUrl);
220 221 String email = user.getEmail();
... ...
... ... @@ -94,12 +94,24 @@ public class UserController extends BaseController {
94 94 processDashboardIdFromAdditionalInfo((ObjectNode) user.getAdditionalInfo(), DEFAULT_DASHBOARD);
95 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 101 return user;
98 102 } catch (Exception e) {
99 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 115 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
104 116 @RequestMapping(value = "/user/tokenAccessEnabled", method = RequestMethod.GET)
105 117 @ResponseBody
... ... @@ -193,13 +205,13 @@ public class UserController extends BaseController {
193 205 user.getId(), user);
194 206
195 207 UserCredentials userCredentials = userService.findUserCredentialsByUserId(getCurrentUser().getTenantId(), user.getId());
196   - if (!userCredentials.isEnabled()) {
  208 + if (!userCredentials.isEnabled() && userCredentials.getActivateToken() != null) {
197 209 String baseUrl = systemSecurityService.getBaseUrl(getTenantId(), getCurrentUser().getCustomerId(), request);
198 210 String activateUrl = String.format(ACTIVATE_URL_PATTERN, baseUrl,
199 211 userCredentials.getActivateToken());
200 212 mailService.sendActivationEmail(activateUrl, email);
201 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 216 } catch (Exception e) {
205 217 throw handleException(e);
... ... @@ -218,13 +230,13 @@ public class UserController extends BaseController {
218 230 User user = checkUserId(userId, Operation.READ);
219 231 SecurityUser authUser = getCurrentUser();
220 232 UserCredentials userCredentials = userService.findUserCredentialsByUserId(authUser.getTenantId(), user.getId());
221   - if (!userCredentials.isEnabled()) {
  233 + if (!userCredentials.isEnabled() && userCredentials.getActivateToken() != null) {
222 234 String baseUrl = systemSecurityService.getBaseUrl(getTenantId(), getCurrentUser().getCustomerId(), request);
223 235 String activateUrl = String.format(ACTIVATE_URL_PATTERN, baseUrl,
224 236 userCredentials.getActivateToken());
225 237 return activateUrl;
226 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 241 } catch (Exception e) {
230 242 throw handleException(e);
... ...
... ... @@ -186,7 +186,11 @@ public class ThingsboardInstallService {
186 186 log.info("Upgrading ThingsBoard from version 3.2.0 to 3.2.1 ...");
187 187 databaseEntitiesUpgradeService.upgradeDatabase("3.2.0");
188 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 194 log.info("Updating system data...");
191 195 systemDataLoaderService.updateSystemWidgets();
192 196 break;
... ...
... ... @@ -50,6 +50,7 @@ public class CassandraTsDatabaseUpgradeService extends AbstractCassandraDatabase
50 50 break;
51 51 case "2.5.0":
52 52 case "3.1.1":
  53 + case "3.2.1":
53 54 break;
54 55 default:
55 56 throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion);
... ...
... ... @@ -36,6 +36,9 @@ import org.thingsboard.server.common.data.TenantProfile;
36 36 import org.thingsboard.server.common.data.User;
37 37 import org.thingsboard.server.common.data.alarm.AlarmSeverity;
38 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 42 import org.thingsboard.server.common.data.device.profile.AlarmRule;
40 43 import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration;
41 44 import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration;
... ... @@ -290,16 +293,16 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
290 293 AlarmCondition temperatureCondition = new AlarmCondition();
291 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 298 temperatureAlarmFlagAttributeFilter.setValueType(EntityKeyValueType.BOOLEAN);
296 299 BooleanFilterPredicate temperatureAlarmFlagAttributePredicate = new BooleanFilterPredicate();
297 300 temperatureAlarmFlagAttributePredicate.setOperation(BooleanFilterPredicate.BooleanOperation.EQUAL);
298 301 temperatureAlarmFlagAttributePredicate.setValue(new FilterPredicateValue<>(Boolean.TRUE));
299 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 306 temperatureTimeseriesFilter.setValueType(EntityKeyValueType.NUMERIC);
304 307 NumericFilterPredicate temperatureTimeseriesFilterPredicate = new NumericFilterPredicate();
305 308 temperatureTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
... ... @@ -317,8 +320,8 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
317 320 AlarmCondition clearTemperatureCondition = new AlarmCondition();
318 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 325 clearTemperatureTimeseriesFilter.setValueType(EntityKeyValueType.NUMERIC);
323 326 NumericFilterPredicate clearTemperatureTimeseriesFilterPredicate = new NumericFilterPredicate();
324 327 clearTemperatureTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.LESS_OR_EQUAL);
... ... @@ -340,16 +343,16 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
340 343 AlarmCondition humidityCondition = new AlarmCondition();
341 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 348 humidityAlarmFlagAttributeFilter.setValueType(EntityKeyValueType.BOOLEAN);
346 349 BooleanFilterPredicate humidityAlarmFlagAttributePredicate = new BooleanFilterPredicate();
347 350 humidityAlarmFlagAttributePredicate.setOperation(BooleanFilterPredicate.BooleanOperation.EQUAL);
348 351 humidityAlarmFlagAttributePredicate.setValue(new FilterPredicateValue<>(Boolean.TRUE));
349 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 356 humidityTimeseriesFilter.setValueType(EntityKeyValueType.NUMERIC);
354 357 NumericFilterPredicate humidityTimeseriesFilterPredicate = new NumericFilterPredicate();
355 358 humidityTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.LESS);
... ... @@ -368,8 +371,8 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
368 371 AlarmCondition clearHumidityCondition = new AlarmCondition();
369 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 376 clearHumidityTimeseriesFilter.setValueType(EntityKeyValueType.NUMERIC);
374 377 NumericFilterPredicate clearHumidityTimeseriesFilterPredicate = new NumericFilterPredicate();
375 378 clearHumidityTimeseriesFilterPredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER_OR_EQUAL);
... ...
... ... @@ -196,11 +196,17 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe
196 196 }
197 197 break;
198 198 case "3.1.1":
  199 + case "3.2.1":
199 200 try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
200 201 log.info("Load TTL functions ...");
201 202 loadSql(conn, LOAD_TTL_FUNCTIONS_SQL);
202 203 log.info("Load Drop Partitions functions ...");
203 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 211 break;
206 212 default:
... ...
... ... @@ -178,6 +178,7 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr
178 178 }
179 179 break;
180 180 case "3.1.1":
  181 + case "3.2.1":
181 182 break;
182 183 default:
183 184 throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
... ...
... ... @@ -145,7 +145,7 @@ public class DefaultTbClusterService implements TbClusterService {
145 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 149 log.trace("PUSHING msg: {} to:{}", tbMsg, tpi);
150 150 ToRuleEngineMsg msg = ToRuleEngineMsg.newBuilder()
151 151 .setTenantIdMSB(tenantId.getId().getMostSignificantBits())
... ...
... ... @@ -55,31 +55,22 @@ public class TbCoreConsumerStats {
55 55 public TbCoreConsumerStats(StatsFactory statsFactory) {
56 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 76 public void log(TransportProtos.TransportToDeviceActorMsg msg) {
... ...
... ... @@ -24,7 +24,7 @@ import java.util.regex.Pattern;
24 24 @Slf4j
25 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 29 private static final int MAX_SMS_MESSAGE_LENGTH = 1600;
30 30 private static final int MAX_SMS_SEGMENT_LENGTH = 70;
... ...
... ... @@ -19,21 +19,34 @@ import com.twilio.http.TwilioRestClient;
19 19 import com.twilio.rest.api.v2010.account.Message;
20 20 import com.twilio.type.PhoneNumber;
21 21 import org.apache.commons.lang3.StringUtils;
  22 +import org.thingsboard.rule.engine.api.sms.exception.SmsParseException;
22 23 import org.thingsboard.server.common.data.sms.config.TwilioSmsProviderConfiguration;
23 24 import org.thingsboard.rule.engine.api.sms.exception.SmsException;
24 25 import org.thingsboard.rule.engine.api.sms.exception.SmsSendException;
25 26 import org.thingsboard.server.service.sms.AbstractSmsSender;
26 27
  28 +import java.util.regex.Pattern;
  29 +
27 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 34 private TwilioRestClient twilioRestClient;
30 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 45 public TwilioSmsSender(TwilioSmsProviderConfiguration config) {
33 46 if (StringUtils.isEmpty(config.getAccountSid()) || StringUtils.isEmpty(config.getAccountToken()) || StringUtils.isEmpty(config.getNumberFrom())) {
34 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 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 54 import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef;
55 55 import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataCmd;
56 56 import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataUpdate;
  57 +import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountCmd;
57 58 import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd;
58 59 import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate;
59 60 import org.thingsboard.server.service.telemetry.cmd.v2.EntityHistoryCmd;
... ... @@ -92,7 +93,7 @@ import java.util.stream.Collectors;
92 93 public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubscriptionService {
93 94
94 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 98 @Autowired
98 99 private TelemetryWebSocketService wsService;
... ... @@ -202,7 +203,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
202 203 //TODO: validate number of dynamic page links against rate limits. Ignore dynamic flag if limit is reached.
203 204 TbEntityDataSubCtx finalCtx = ctx;
204 205 ScheduledFuture<?> task = scheduler.scheduleWithFixedDelay(
205   - () -> refreshDynamicQuery(tenantId, customerId, finalCtx),
  206 + () -> refreshDynamicQuery(finalCtx),
206 207 dynamicPageLinkRefreshInterval, dynamicPageLinkRefreshInterval, TimeUnit.SECONDS);
207 208 finalCtx.setRefreshTask(task);
208 209 }
... ... @@ -236,6 +237,26 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
236 237 }
237 238
238 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 260 public void handleCmd(TelemetryWebSocketSessionRef session, AlarmDataCmd cmd) {
240 261 TbAlarmDataSubCtx ctx = getSubCtx(session.getSessionId(), cmd.getCmdId());
241 262 if (ctx == null) {
... ... @@ -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 292 try {
272 293 long start = System.currentTimeMillis();
273 294 finalCtx.update();
... ... @@ -299,7 +320,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
299 320 }
300 321
301 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 324 TbEntityDataSubCtx ctx = new TbEntityDataSubCtx(serviceId, wsService, entityService, localSubscriptionService,
304 325 attributesService, stats, sessionRef, cmd.getCmdId(), maxEntitiesPerDataSubscription);
305 326 if (cmd.getQuery() != null) {
... ... @@ -309,8 +330,20 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
309 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 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 347 TbAlarmDataSubCtx ctx = new TbAlarmDataSubCtx(serviceId, wsService, entityService, localSubscriptionService,
315 348 attributesService, stats, alarmService, sessionRef, cmd.getCmdId(), maxEntitiesPerAlarmSubscription);
316 349 ctx.setAndResolveQuery(cmd.getQuery());
... ... @@ -319,8 +352,8 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
319 352 }
320 353
321 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 357 if (sessionSubs != null) {
325 358 return (T) sessionSubs.get(cmdId);
326 359 } else {
... ... @@ -464,17 +497,16 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
464 497 cleanupAndCancel(getSubCtx(sessionId, cmd.getCmdId()));
465 498 }
466 499
467   - private void cleanupAndCancel(TbAbstractDataSubCtx ctx) {
  500 + private void cleanupAndCancel(TbAbstractSubCtx ctx) {
468 501 if (ctx != null) {
469 502 ctx.cancelTasks();
470   - ctx.clearEntitySubscriptions();
471   - ctx.clearDynamicValueSubscriptions();
  503 + ctx.clearSubscriptions();
472 504 }
473 505 }
474 506
475 507 @Override
476 508 public void cancelAllSessionSubscriptions(String sessionId) {
477   - Map<Integer, TbAbstractDataSubCtx> sessionSubs = subscriptionsBySessionId.remove(sessionId);
  509 + Map<Integer, TbAbstractSubCtx> sessionSubs = subscriptionsBySessionId.remove(sessionId);
478 510 if (sessionSubs != null) {
479 511 sessionSubs.values().forEach(this::cleanupAndCancel);
480 512 }
... ...
... ... @@ -15,32 +15,16 @@
15 15 */
16 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 18 import lombok.Getter;
23   -import lombok.Setter;
24 19 import lombok.extern.slf4j.Slf4j;
25   -import org.thingsboard.server.common.data.id.CustomerId;
26 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 21 import org.thingsboard.server.common.data.page.PageData;
31 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 23 import org.thingsboard.server.common.data.query.EntityData;
36 24 import org.thingsboard.server.common.data.query.EntityDataPageLink;
37 25 import org.thingsboard.server.common.data.query.EntityDataQuery;
38 26 import org.thingsboard.server.common.data.query.EntityKey;
39 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 28 import org.thingsboard.server.common.data.query.TsValue;
45 29 import org.thingsboard.server.dao.attributes.AttributesService;
46 30 import org.thingsboard.server.dao.entity.EntityService;
... ... @@ -52,140 +36,25 @@ import java.util.ArrayList;
52 36 import java.util.Arrays;
53 37 import java.util.Collections;
54 38 import java.util.HashMap;
55   -import java.util.HashSet;
56 39 import java.util.List;
57 40 import java.util.Map;
58   -import java.util.Optional;
59   -import java.util.Set;
60 41 import java.util.concurrent.ConcurrentHashMap;
61   -import java.util.concurrent.ExecutionException;
62   -import java.util.concurrent.ScheduledFuture;
63 42 import java.util.function.Function;
64 43 import java.util.stream.Collectors;
65 44
66 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 48 protected final Map<Integer, EntityId> subToEntityIdMap;
79   - protected final Set<Integer> subToDynamicValueKeySet;
80   - @Getter
81   - protected final Map<DynamicValueKey, List<DynamicValue>> dynamicValues;
82 49 @Getter
83 50 protected PageData<EntityData> data;
84   - @Getter
85   - @Setter
86   - protected T query;
87   - @Setter
88   - protected volatile ScheduledFuture<?> refreshTask;
89 51
90 52 public TbAbstractDataSubCtx(String serviceId, TelemetryWebSocketService wsService,
91 53 EntityService entityService, TbLocalSubscriptionService localSubscriptionService,
92 54 AttributesService attributesService, SubscriptionServiceStatistics stats,
93 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 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 60 public void fetchData() {
... ... @@ -231,104 +100,10 @@ public abstract class TbAbstractDataSubCtx<T extends AbstractDataQuery<? extends
231 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 109 public void clearEntitySubscriptions() {
... ... @@ -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 118 public void createSubscriptions(List<EntityKey> keys, boolean resultToLatestValues) {
364 119 Map<EntityKeyType, List<EntityKey>> keysByType = getEntityKeyByTypeMap(keys);
365 120 for (EntityData entityData : data.getData()) {
... ... @@ -459,14 +214,4 @@ public abstract class TbAbstractDataSubCtx<T extends AbstractDataQuery<? extends
459 214
460 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 90 AlarmDataUpdate update;
91 91 if (!entitiesMap.isEmpty()) {
92 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 94 long end = System.currentTimeMillis();
96 95 stats.getAlarmQueryInvocationCnt().incrementAndGet();
97 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 17
18 18 import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef;
19 19 import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataCmd;
  20 +import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountCmd;
20 21 import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd;
21 22 import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUnsubscribeCmd;
22 23 import org.thingsboard.server.service.telemetry.cmd.v2.UnsubscribeCmd;
... ... @@ -25,6 +26,8 @@ public interface TbEntityDataSubscriptionService {
25 26
26 27 void handleCmd(TelemetryWebSocketSessionRef sessionId, EntityDataCmd cmd);
27 28
  29 + void handleCmd(TelemetryWebSocketSessionRef sessionId, EntityCountCmd cmd);
  30 +
28 31 void handleCmd(TelemetryWebSocketSessionRef sessionId, AlarmDataCmd cmd);
29 32
30 33 void cancelSubscription(String sessionId, UnsubscribeCmd subscriptionId);
... ...
... ... @@ -51,22 +51,22 @@ import org.thingsboard.server.service.security.ValidationResult;
51 51 import org.thingsboard.server.service.security.ValidationResultCode;
52 52 import org.thingsboard.server.service.security.model.UserPrincipal;
53 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 56 import org.thingsboard.server.service.subscription.TbEntityDataSubscriptionService;
55 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 58 import org.thingsboard.server.service.subscription.TbTimeseriesSubscription;
  59 +import org.thingsboard.server.service.telemetry.cmd.TelemetryPluginCmdsWrapper;
59 60 import org.thingsboard.server.service.telemetry.cmd.v1.AttributesSubscriptionCmd;
60 61 import org.thingsboard.server.service.telemetry.cmd.v1.GetHistoryCmd;
61 62 import org.thingsboard.server.service.telemetry.cmd.v1.SubscriptionCmd;
62 63 import org.thingsboard.server.service.telemetry.cmd.v1.TelemetryPluginCmd;
63   -import org.thingsboard.server.service.telemetry.cmd.TelemetryPluginCmdsWrapper;
64 64 import org.thingsboard.server.service.telemetry.cmd.v1.TimeseriesSubscriptionCmd;
65 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 67 import org.thingsboard.server.service.telemetry.cmd.v2.DataUpdate;
  68 +import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountCmd;
68 69 import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd;
69   -import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUnsubscribeCmd;
70 70 import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate;
71 71 import org.thingsboard.server.service.telemetry.cmd.v2.UnsubscribeCmd;
72 72 import org.thingsboard.server.service.telemetry.exception.UnauthorizedException;
... ... @@ -216,12 +216,18 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
216 216 if (cmdsWrapper.getAlarmDataCmds() != null) {
217 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 222 if (cmdsWrapper.getEntityDataUnsubscribeCmds() != null) {
220 223 cmdsWrapper.getEntityDataUnsubscribeCmds().forEach(cmd -> handleWsDataUnsubscribeCmd(sessionRef, cmd));
221 224 }
222 225 if (cmdsWrapper.getAlarmDataUnsubscribeCmds() != null) {
223 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 232 } catch (IOException e) {
227 233 log.warn("Failed to decode subscription cmd: {}", e.getMessage(), e);
... ... @@ -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 258 private void handleWsAlarmDataCmd(TelemetryWebSocketSessionRef sessionRef, AlarmDataCmd cmd) {
243 259 String sessionId = sessionRef.getSessionId();
244 260 log.debug("[{}] Processing: {}", sessionId, cmd);
... ... @@ -264,7 +280,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
264 280 }
265 281
266 282 @Override
267   - public void sendWsMsg(String sessionId, DataUpdate update) {
  283 + public void sendWsMsg(String sessionId, CmdUpdate update) {
268 284 sendWsMsg(sessionId, update.getCmdId(), update);
269 285 }
270 286
... ... @@ -679,6 +695,20 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
679 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 712 private boolean validateSubscriptionCmd(TelemetryWebSocketSessionRef sessionRef, AlarmDataCmd cmd) {
683 713 if (cmd.getCmdId() < 0) {
684 714 TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST,
... ...
... ... @@ -15,6 +15,7 @@
15 15 */
16 16 package org.thingsboard.server.service.telemetry;
17 17
  18 +import org.thingsboard.server.service.telemetry.cmd.v2.CmdUpdate;
18 19 import org.thingsboard.server.service.telemetry.cmd.v2.DataUpdate;
19 20 import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate;
20 21
... ... @@ -29,6 +30,6 @@ public interface TelemetryWebSocketService {
29 30
30 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 21 import org.thingsboard.server.service.telemetry.cmd.v1.TimeseriesSubscriptionCmd;
22 22 import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataCmd;
23 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 26 import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd;
25 27 import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUnsubscribeCmd;
26 28
... ... @@ -46,4 +48,8 @@ public class TelemetryPluginCmdsWrapper {
46 48
47 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 18 import com.fasterxml.jackson.annotation.JsonCreator;
19 19 import com.fasterxml.jackson.annotation.JsonProperty;
20 20 import lombok.Getter;
21   -import lombok.NoArgsConstructor;
  21 +import lombok.ToString;
22 22 import org.thingsboard.server.common.data.page.PageData;
23 23 import org.thingsboard.server.common.data.query.AlarmData;
24   -import org.thingsboard.server.common.data.query.EntityData;
25 24 import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode;
26 25
27 26 import java.util.List;
28 27
  28 +@ToString
29 29 public class AlarmDataUpdate extends DataUpdate<AlarmData> {
30 30
31 31 @Getter
... ... @@ -44,8 +44,8 @@ public class AlarmDataUpdate extends DataUpdate<AlarmData> {
44 44 }
45 45
46 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 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 15 */
16 16 package org.thingsboard.server.service.telemetry.cmd.v2;
17 17
18   -public enum DataUpdateType {
  18 +public enum CmdUpdateType {
19 19 ENTITY_DATA,
20   - ALARM_DATA
  20 + ALARM_DATA,
  21 + COUNT_DATA
21 22 }
... ...
... ... @@ -15,24 +15,24 @@
15 15 */
16 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 19 import org.thingsboard.server.common.data.page.PageData;
22 20 import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode;
23 21
24 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 27 private final PageData<T> data;
  28 + @Getter
33 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 37 public DataUpdate(int cmdId, PageData<T> data, List<T> update) {
38 38 this(cmdId, data, update, SubscriptionErrorCode.NO_ERROR.getCode(), null);
... ... @@ -42,5 +42,4 @@ public abstract class DataUpdate<T> {
42 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 package org.thingsboard.server.service.telemetry.cmd.v2;
17 17
18 18 import com.fasterxml.jackson.annotation.JsonCreator;
19   -import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
20 19 import com.fasterxml.jackson.annotation.JsonProperty;
21 20 import lombok.Getter;
  21 +import lombok.ToString;
22 22 import org.thingsboard.server.common.data.page.PageData;
23 23 import org.thingsboard.server.common.data.query.EntityData;
24 24 import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode;
25 25
26 26 import java.util.List;
27 27
28   -
  28 +@ToString
29 29 public class EntityDataUpdate extends DataUpdate<EntityData> {
30 30
31 31 @Getter
... ... @@ -41,8 +41,8 @@ public class EntityDataUpdate extends DataUpdate<EntityData> {
41 41 }
42 42
43 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 48 @JsonCreator
... ...
... ... @@ -15,8 +15,6 @@
15 15 */
16 16 package org.thingsboard.server.service.telemetry.cmd.v2;
17 17
18   -import lombok.Data;
19   -
20 18 public interface UnsubscribeCmd {
21 19
22 20 int getCmdId();
... ...
... ... @@ -30,7 +30,7 @@
30 30 <!-- <logger name="org.thingsboard.server.service.queue" level="TRACE" />-->
31 31 <!-- <logger name="org.thingsboard.server.service.transport" level="TRACE" />-->
32 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 35 <!-- <logger name="org.thingsboard.server.service.subscription" level="TRACE"/>-->
36 36 <!-- <logger name="org.thingsboard.server.service.telemetry" level="TRACE"/>-->
... ...
... ... @@ -322,6 +322,9 @@ actors:
322 322 cache:
323 323 # caffeine or redis
324 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 329 caffeine:
327 330 specs:
... ... @@ -355,6 +358,9 @@ caffeine:
355 358 deviceProfiles:
356 359 timeToLiveInMinutes: 1440
357 360 maxSize: 0
  361 + attributes:
  362 + timeToLiveInMinutes: 1440
  363 + maxSize: 100000
358 364
359 365 redis:
360 366 # standalone or cluster
... ... @@ -539,6 +545,7 @@ transport:
539 545 http:
540 546 enabled: "${HTTP_ENABLED:true}"
541 547 request_timeout: "${HTTP_REQUEST_TIMEOUT:60000}"
  548 + max_request_timeout: "${HTTP_MAX_REQUEST_TIMEOUT:300000}"
542 549 # Local MQTT transport parameters
543 550 mqtt:
544 551 # Enable/disable mqtt transport protocol.
... ...
... ... @@ -44,6 +44,7 @@ import org.thingsboard.server.common.data.query.EntityDataSortOrder;
44 44 import org.thingsboard.server.common.data.query.EntityKey;
45 45 import org.thingsboard.server.common.data.query.EntityKeyType;
46 46 import org.thingsboard.server.common.data.query.EntityListFilter;
  47 +import org.thingsboard.server.common.data.query.EntityTypeFilter;
47 48 import org.thingsboard.server.common.data.query.FilterPredicateValue;
48 49 import org.thingsboard.server.common.data.query.KeyFilter;
49 50 import org.thingsboard.server.common.data.query.NumericFilterPredicate;
... ... @@ -132,6 +133,14 @@ public abstract class BaseEntityQueryControllerTest extends AbstractControllerTe
132 133
133 134 count = doPostWithResponse("/api/entitiesQuery/count", countQuery, Long.class);
134 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 146 @Test
... ... @@ -198,11 +207,31 @@ public abstract class BaseEntityQueryControllerTest extends AbstractControllerTe
198 207 Assert.assertEquals(11, data.getTotalElements());
199 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 233 @Test
204 234 public void testFindEntityDataByQueryWithAttributes() throws Exception {
205   -
206 235 List<Device> devices = new ArrayList<>();
207 236 List<Long> temperatures = new ArrayList<>();
208 237 List<Long> highTemperatures = new ArrayList<>();
... ...
... ... @@ -35,16 +35,23 @@ import org.thingsboard.server.common.data.kv.LongDataEntry;
35 35 import org.thingsboard.server.common.data.kv.TsKvEntry;
36 36 import org.thingsboard.server.common.data.page.PageData;
37 37 import org.thingsboard.server.common.data.query.DeviceTypeFilter;
  38 +import org.thingsboard.server.common.data.query.EntityCountQuery;
38 39 import org.thingsboard.server.common.data.query.EntityData;
39 40 import org.thingsboard.server.common.data.query.EntityDataPageLink;
40 41 import org.thingsboard.server.common.data.query.EntityDataQuery;
41 42 import org.thingsboard.server.common.data.query.EntityKey;
42 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 48 import org.thingsboard.server.common.data.query.TsValue;
44 49 import org.thingsboard.server.common.data.security.Authority;
45 50 import org.thingsboard.server.service.subscription.TbAttributeSubscriptionScope;
46 51 import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
47 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 55 import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd;
49 56 import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate;
50 57 import org.thingsboard.server.service.telemetry.cmd.v2.EntityHistoryCmd;
... ... @@ -244,6 +251,98 @@ public class BaseWebsocketApiTest extends AbstractWebsocketTest {
244 251 }
245 252
246 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 346 public void testEntityDataLatestWidgetFlow() throws Exception {
248 347 Device device = new Device();
249 348 device.setName("Device");
... ...
... ... @@ -27,7 +27,7 @@ import java.util.Arrays;
27 27 @RunWith(ClasspathSuite.class)
28 28 @ClasspathSuite.ClassnameFilters({
29 29 // "org.thingsboard.server.controller.sql.WebsocketApiSqlTest",
30   -// "org.thingsboard.server.controller.sql.TenantProfileControllerSqlTest",
  30 +// "org.thingsboard.server.controller.sql.EntityQueryControllerSqlTest",
31 31 "org.thingsboard.server.controller.sql.*Test",
32 32 })
33 33 public class ControllerSqlTestSuite {
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>3.3.0-SNAPSHOT</version>
  23 + <version>3.2.2-SNAPSHOT</version>
24 24 <artifactId>common</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.common</groupId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>3.3.0-SNAPSHOT</version>
  23 + <version>3.2.2-SNAPSHOT</version>
24 24 <artifactId>common</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.common</groupId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>3.3.0-SNAPSHOT</version>
  23 + <version>3.2.2-SNAPSHOT</version>
24 24 <artifactId>common</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.common</groupId>
... ...
... ... @@ -26,4 +26,5 @@ public class CacheConstants {
26 26 public static final String SECURITY_SETTINGS_CACHE = "securitySettings";
27 27 public static final String TENANT_PROFILE_CACHE = "tenantProfiles";
28 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 19 import org.thingsboard.server.common.data.EntityType;
20 20 import org.thingsboard.server.common.data.relation.EntityRelation;
21 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 23 import org.thingsboard.server.common.data.relation.RelationsSearchParameters;
24 24
25 25 import java.util.Collections;
... ... @@ -39,7 +39,7 @@ public class AssetSearchQuery {
39 39 EntityRelationsQuery query = new EntityRelationsQuery();
40 40 query.setParameters(parameters);
41 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 43 Collections.singletonList(EntityType.ASSET))));
44 44 return query;
45 45 }
... ...
... ... @@ -19,7 +19,7 @@ import lombok.Data;
19 19 import org.thingsboard.server.common.data.EntityType;
20 20 import org.thingsboard.server.common.data.relation.EntityRelation;
21 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 23 import org.thingsboard.server.common.data.relation.RelationsSearchParameters;
24 24
25 25 import java.util.Collections;
... ... @@ -36,7 +36,7 @@ public class DeviceSearchQuery {
36 36 EntityRelationsQuery query = new EntityRelationsQuery();
37 37 query.setParameters(parameters);
38 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 40 Collections.singletonList(EntityType.DEVICE))));
41 41 return query;
42 42 }
... ...
... ... @@ -26,7 +26,7 @@ import java.util.concurrent.TimeUnit;
26 26 @JsonIgnoreProperties(ignoreUnknown = true)
27 27 public class AlarmCondition {
28 28
29   - private List<KeyFilter> condition;
  29 + private List<AlarmConditionFilter> condition;
30 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 19 import org.thingsboard.server.common.data.EntityType;
20 20 import org.thingsboard.server.common.data.relation.EntityRelation;
21 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 23 import org.thingsboard.server.common.data.relation.RelationsSearchParameters;
24 24
25 25 import java.util.Collections;
... ... @@ -36,7 +36,7 @@ public class EntityViewSearchQuery {
36 36 EntityRelationsQuery query = new EntityRelationsQuery();
37 37 query.setParameters(parameters);
38 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 40 Collections.singletonList(EntityType.ENTITY_VIEW))));
41 41 return query;
42 42 }
... ...
... ... @@ -29,15 +29,13 @@ public abstract class AbstractDataQuery<T extends EntityDataPageLink> extends En
29 29 protected List<EntityKey> entityFields;
30 30 @Getter
31 31 protected List<EntityKey> latestValues;
32   - @Getter
33   - protected List<KeyFilter> keyFilters;
34 32
35 33 public AbstractDataQuery() {
36 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 41 public AbstractDataQuery(EntityFilter entityFilter,
... ... @@ -45,11 +43,10 @@ public abstract class AbstractDataQuery<T extends EntityDataPageLink> extends En
45 43 List<EntityKey> entityFields,
46 44 List<EntityKey> latestValues,
47 45 List<KeyFilter> keyFilters) {
48   - super(entityFilter);
  46 + super(entityFilter, keyFilters);
49 47 this.pageLink = pageLink;
50 48 this.entityFields = entityFields;
51 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 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 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 17
18 18 import lombok.Getter;
19 19
  20 +import java.util.Collections;
  21 +import java.util.List;
  22 +
20 23 public class EntityCountQuery {
21 24
22 25 @Getter
23 26 private EntityFilter entityFilter;
24 27
25   - public EntityCountQuery() {}
  28 + @Getter
  29 + protected List<KeyFilter> keyFilters;
  30 +
  31 + public EntityCountQuery() {
  32 + }
26 33
27 34 public EntityCountQuery(EntityFilter entityFilter) {
  35 + this(entityFilter, Collections.emptyList());
  36 + }
  37 +
  38 + public EntityCountQuery(EntityFilter entityFilter, List<KeyFilter> keyFilters) {
28 39 this.entityFilter = entityFilter;
  40 + this.keyFilters = keyFilters;
29 41 }
30 42 }
... ...
... ... @@ -27,8 +27,8 @@ public class EntityDataQuery extends AbstractDataQuery<EntityDataPageLink> {
27 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 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 29 @JsonSubTypes.Type(value = SingleEntityFilter.class, name = "singleEntity"),
30 30 @JsonSubTypes.Type(value = EntityListFilter.class, name = "entityList"),
31 31 @JsonSubTypes.Type(value = EntityNameFilter.class, name = "entityName"),
  32 + @JsonSubTypes.Type(value = EntityTypeFilter.class, name = "entityType"),
32 33 @JsonSubTypes.Type(value = AssetTypeFilter.class, name = "assetType"),
33 34 @JsonSubTypes.Type(value = DeviceTypeFilter.class, name = "deviceType"),
34 35 @JsonSubTypes.Type(value = EntityViewTypeFilter.class, name = "entityViewType"),
... ...
... ... @@ -19,6 +19,7 @@ public enum EntityFilterType {
19 19 SINGLE_ENTITY("singleEntity"),
20 20 ENTITY_LIST("entityList"),
21 21 ENTITY_NAME("entityName"),
  22 + ENTITY_TYPE("entityType"),
22 23 ASSET_TYPE("assetType"),
23 24 DEVICE_TYPE("deviceType"),
24 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 18 import lombok.Data;
19 19 import org.thingsboard.server.common.data.id.EntityId;
20 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 23 import java.util.List;
25 24
... ... @@ -33,7 +32,7 @@ public class RelationsQueryFilter implements EntityFilter {
33 32
34 33 private EntityId rootEntity;
35 34 private EntitySearchDirection direction;
36   - private List<EntityTypeFilter> filters;
  35 + private List<RelationEntityTypeFilter> filters;
37 36 private int maxLevel;
38 37 private boolean fetchLastLevelOnly;
39 38
... ...
... ... @@ -26,6 +26,6 @@ import java.util.List;
26 26 public class EntityRelationsQuery {
27 27
28 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 26 */
27 27 @Data
28 28 @AllArgsConstructor
29   -public class EntityTypeFilter {
  29 +public class RelationEntityTypeFilter {
30 30
31 31 private String relationType;
32 32
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>3.3.0-SNAPSHOT</version>
  23 + <version>3.2.2-SNAPSHOT</version>
24 24 <artifactId>common</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.common</groupId>
... ...
... ... @@ -120,7 +120,7 @@ public final class TbMsg implements Serializable {
120 120 private TbMsg(String queueName, UUID id, long ts, String type, EntityId originator, TbMsgMetaData metaData, TbMsgDataType dataType, String data,
121 121 RuleChainId ruleChainId, RuleNodeId ruleNodeId, int ruleNodeExecCounter, TbMsgCallback callback) {
122 122 this.id = id;
123   - this.queueName = queueName;
  123 + this.queueName = queueName != null ? queueName : ServiceQueue.MAIN;
124 124 if (ts > 0) {
125 125 this.ts = ts;
126 126 } else {
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>3.3.0-SNAPSHOT</version>
  23 + <version>3.2.2-SNAPSHOT</version>
24 24 <artifactId>thingsboard</artifactId>
25 25 </parent>
26 26 <artifactId>common</artifactId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>3.3.0-SNAPSHOT</version>
  23 + <version>3.2.2-SNAPSHOT</version>
24 24 <artifactId>common</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.common</groupId>
... ...
... ... @@ -22,7 +22,7 @@
22 22 <modelVersion>4.0.0</modelVersion>
23 23 <parent>
24 24 <groupId>org.thingsboard</groupId>
25   - <version>3.3.0-SNAPSHOT</version>
  25 + <version>3.2.2-SNAPSHOT</version>
26 26 <artifactId>common</artifactId>
27 27 </parent>
28 28 <groupId>org.thingsboard.common</groupId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard.common</groupId>
23   - <version>3.3.0-SNAPSHOT</version>
  23 + <version>3.2.2-SNAPSHOT</version>
24 24 <artifactId>transport</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.common.transport</groupId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard.common</groupId>
23   - <version>3.3.0-SNAPSHOT</version>
  23 + <version>3.2.2-SNAPSHOT</version>
24 24 <artifactId>transport</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.common.transport</groupId>
... ...
... ... @@ -17,9 +17,13 @@ package org.thingsboard.server.transport.http;
17 17
18 18 import lombok.Getter;
19 19 import lombok.extern.slf4j.Slf4j;
  20 +import org.apache.coyote.ProtocolHandler;
  21 +import org.apache.coyote.http11.Http11NioProtocol;
20 22 import org.springframework.beans.factory.annotation.Value;
21 23 import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
22 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 27 import org.springframework.stereotype.Component;
24 28 import org.thingsboard.server.common.transport.TransportContext;
25 29
... ... @@ -37,4 +41,18 @@ public class HttpTransportContext extends TransportContext {
37 41 @Value("${transport.http.request_timeout}")
38 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 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard.common</groupId>
23   - <version>3.3.0-SNAPSHOT</version>
  23 + <version>3.2.2-SNAPSHOT</version>
24 24 <artifactId>transport</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.common.transport</groupId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>3.3.0-SNAPSHOT</version>
  23 + <version>3.2.2-SNAPSHOT</version>
24 24 <artifactId>common</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.common</groupId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard.common</groupId>
23   - <version>3.3.0-SNAPSHOT</version>
  23 + <version>3.2.2-SNAPSHOT</version>
24 24 <artifactId>transport</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.common.transport</groupId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>3.3.0-SNAPSHOT</version>
  23 + <version>3.2.2-SNAPSHOT</version>
24 24 <artifactId>common</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.common</groupId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>3.3.0-SNAPSHOT</version>
  23 + <version>3.2.2-SNAPSHOT</version>
24 24 <artifactId>thingsboard</artifactId>
25 25 </parent>
26 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 15 */
16 16 package org.thingsboard.server.dao.attributes;
17 17
18   -import com.google.common.collect.Lists;
19 18 import com.google.common.util.concurrent.Futures;
20 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 23 import org.springframework.stereotype.Service;
23 24 import org.thingsboard.server.common.data.EntityType;
24 25 import org.thingsboard.server.common.data.id.DeviceProfileId;
25 26 import org.thingsboard.server.common.data.id.EntityId;
26 27 import org.thingsboard.server.common.data.id.TenantId;
27 28 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
28   -import org.thingsboard.server.dao.exception.IncorrectParameterException;
29 29 import org.thingsboard.server.dao.service.Validator;
30 30
31 31 import java.util.Collection;
32 32 import java.util.List;
33 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 39 * @author Andrew Shvayka
37 40 */
38 41 @Service
  42 +@ConditionalOnProperty(prefix = "cache.attributes", value = "enabled", havingValue = "false", matchIfMissing = true)
  43 +@Primary
  44 +@Slf4j
39 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 52 @Override
45 53 public ListenableFuture<Optional<AttributeKvEntry>> find(TenantId tenantId, EntityId entityId, String scope, String attributeKey) {
... ... @@ -75,33 +83,14 @@ public class BaseAttributesService implements AttributesService {
75 83 public ListenableFuture<List<Void>> save(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes) {
76 84 validate(entityId, scope);
77 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 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 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 35 import org.thingsboard.server.common.data.relation.EntityRelationInfo;
36 36 import org.thingsboard.server.common.data.relation.EntityRelationsQuery;
37 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 39 import org.thingsboard.server.common.data.relation.RelationTypeGroup;
40 40 import org.thingsboard.server.common.data.relation.RelationsSearchParameters;
41 41 import org.thingsboard.server.dao.entity.EntityService;
... ... @@ -457,7 +457,7 @@ public class BaseRelationService implements RelationService {
457 457 //boolean fetchLastLevelOnly = true;
458 458 log.trace("Executing findByQuery [{}]", query);
459 459 RelationsSearchParameters params = query.getParameters();
460   - final List<EntityTypeFilter> filters = query.getFilters();
  460 + final List<RelationEntityTypeFilter> filters = query.getFilters();
461 461 if (filters == null || filters.isEmpty()) {
462 462 log.debug("Filters are not set [{}]", query);
463 463 }
... ... @@ -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 580 if (match(filter, relation, direction)) {
581 581 return true;
582 582 }
... ... @@ -584,7 +584,7 @@ public class BaseRelationService implements RelationService {
584 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 588 if (StringUtils.isEmpty(filter.getRelationType()) || filter.getRelationType().equals(relation.getType())) {
589 589 if (filter.getEntityTypes() == null || filter.getEntityTypes().isEmpty()) {
590 590 return true;
... ...
... ... @@ -40,12 +40,13 @@ import org.thingsboard.server.common.data.query.EntityKeyType;
40 40 import org.thingsboard.server.common.data.query.EntityListFilter;
41 41 import org.thingsboard.server.common.data.query.EntityNameFilter;
42 42 import org.thingsboard.server.common.data.query.EntitySearchQueryFilter;
  43 +import org.thingsboard.server.common.data.query.EntityTypeFilter;
43 44 import org.thingsboard.server.common.data.query.EntityViewSearchQueryFilter;
44 45 import org.thingsboard.server.common.data.query.EntityViewTypeFilter;
45 46 import org.thingsboard.server.common.data.query.RelationsQueryFilter;
46 47 import org.thingsboard.server.common.data.query.SingleEntityFilter;
47 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 51 import java.util.Arrays;
51 52 import java.util.Collections;
... ... @@ -249,18 +250,70 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
249 250 public long countEntitiesByQuery(TenantId tenantId, CustomerId customerId, EntityCountQuery query) {
250 251 EntityType entityType = resolveEntityType(query.getEntityFilter());
251 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 319 @Override
... ... @@ -436,6 +489,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
436 489 case ASSET_SEARCH_QUERY:
437 490 case ENTITY_VIEW_SEARCH_QUERY:
438 491 case API_USAGE_STATE:
  492 + case ENTITY_TYPE:
439 493 return "";
440 494 default:
441 495 throw new RuntimeException("Not implemented!");
... ... @@ -521,7 +575,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
521 575 boolean single = entityFilter.getFilters() != null && entityFilter.getFilters().size() == 1;
522 576 if (entityFilter.getFilters() != null && !entityFilter.getFilters().isEmpty()) {
523 577 int entityTypeFilterIdx = 0;
524   - for (EntityTypeFilter etf : entityFilter.getFilters()) {
  578 + for (RelationEntityTypeFilter etf : entityFilter.getFilters()) {
525 579 String etfCondition = buildEtfCondition(ctx, etf, entityFilter.getDirection(), entityTypeFilterIdx++);
526 580 if (!etfCondition.isEmpty()) {
527 581 if (noConditions) {
... ... @@ -570,7 +624,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
570 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 628 StringBuilder whereFilter = new StringBuilder();
575 629 String relationType = etf.getRelationType();
576 630 List<EntityType> entityTypes = etf.getEntityTypes();
... ... @@ -676,6 +730,8 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
676 730 return ((EntityListFilter) entityFilter).getEntityType();
677 731 case ENTITY_NAME:
678 732 return ((EntityNameFilter) entityFilter).getEntityType();
  733 + case ENTITY_TYPE:
  734 + return ((EntityTypeFilter) entityFilter).getEntityType();
679 735 case ASSET_TYPE:
680 736 case ASSET_SEARCH_QUERY:
681 737 return EntityType.ASSET;
... ...
... ... @@ -21,6 +21,7 @@ import org.thingsboard.server.common.data.DataConstants;
21 21 import org.thingsboard.server.common.data.EntityType;
22 22 import org.thingsboard.server.common.data.query.BooleanFilterPredicate;
23 23 import org.thingsboard.server.common.data.query.ComplexFilterPredicate;
  24 +import org.thingsboard.server.common.data.query.EntityCountQuery;
24 25 import org.thingsboard.server.common.data.query.EntityDataQuery;
25 26 import org.thingsboard.server.common.data.query.EntityDataSortOrder;
26 27 import org.thingsboard.server.common.data.query.EntityFilter;
... ... @@ -380,6 +381,30 @@ public class EntityKeyMapping {
380 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 408 private String buildAttributeSelection() {
384 409 return buildTimeSeriesOrAttrSelection(true);
385 410 }
... ...
... ... @@ -19,8 +19,10 @@ import com.google.common.util.concurrent.Futures;
19 19 import com.google.common.util.concurrent.ListenableFuture;
20 20 import com.google.common.util.concurrent.MoreExecutors;
21 21 import lombok.extern.slf4j.Slf4j;
  22 +import org.hibernate.exception.ConstraintViolationException;
22 23 import org.springframework.beans.factory.annotation.Autowired;
23 24 import org.springframework.beans.factory.annotation.Value;
  25 +import org.springframework.dao.DataIntegrityViolationException;
24 26 import org.springframework.stereotype.Component;
25 27 import org.thingsboard.server.common.data.id.EntityId;
26 28 import org.thingsboard.server.common.data.id.TenantId;
... ... @@ -114,6 +116,14 @@ public class JpaPsqlTimeseriesDao extends AbstractChunkedAggregationTimeseriesDa
114 116 partitioningRepository.save(psqlPartition);
115 117 log.trace("Adding partition to Set: {}", psqlPartition);
116 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 127 } finally {
118 128 partitionCreationLock.unlock();
119 129 }
... ...
... ... @@ -15,9 +15,6 @@
15 15 */
16 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 18 import com.google.common.util.concurrent.Futures;
22 19 import com.google.common.util.concurrent.ListenableFuture;
23 20 import org.junit.After;
... ... @@ -31,25 +28,48 @@ import org.thingsboard.server.common.data.Device;
31 28 import org.thingsboard.server.common.data.EntityType;
32 29 import org.thingsboard.server.common.data.Tenant;
33 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 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 59 import org.thingsboard.server.common.data.relation.EntityRelation;
39 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 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 63 import org.thingsboard.server.dao.attributes.AttributesService;
46 64 import org.thingsboard.server.dao.model.sqlts.ts.TsKvEntity;
47   -import org.thingsboard.server.dao.rule.RuleChainService;
48 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 73 import java.util.concurrent.ExecutionException;
54 74 import java.util.stream.Collectors;
55 75
... ... @@ -140,13 +160,13 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
140 160 long count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);
141 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 164 count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);
145 165 Assert.assertEquals(25, count);
146 166
147 167 filter.setRootEntity(devices.get(0).getId());
148 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 170 count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);
151 171 Assert.assertEquals(1, count);
152 172
... ... @@ -208,7 +228,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
208 228 RelationsQueryFilter filter = new RelationsQueryFilter();
209 229 filter.setRootEntity(tenantId);
210 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 233 EntityDataSortOrder sortOrder = new EntityDataSortOrder(
214 234 new EntityKey(EntityKeyType.ENTITY_FIELD, "createdTime"), EntityDataSortOrder.Direction.ASC
... ...
... ... @@ -26,7 +26,7 @@ import org.thingsboard.server.common.data.id.DeviceId;
26 26 import org.thingsboard.server.common.data.relation.EntityRelation;
27 27 import org.thingsboard.server.common.data.relation.EntityRelationsQuery;
28 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 30 import org.thingsboard.server.common.data.relation.RelationTypeGroup;
31 31 import org.thingsboard.server.common.data.relation.RelationsSearchParameters;
32 32 import org.thingsboard.server.dao.exception.DataValidationException;
... ... @@ -221,7 +221,7 @@ public abstract class BaseRelationServiceTest extends AbstractServiceTest {
221 221
222 222 EntityRelationsQuery query = new EntityRelationsQuery();
223 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 225 List<EntityRelation> relations = relationService.findByQuery(SYSTEM_TENANT_ID, query).get();
226 226 Assert.assertEquals(3, relations.size());
227 227 Assert.assertTrue(relations.contains(relationA));
... ... @@ -255,7 +255,7 @@ public abstract class BaseRelationServiceTest extends AbstractServiceTest {
255 255
256 256 EntityRelationsQuery query = new EntityRelationsQuery();
257 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 259 List<EntityRelation> relations = relationService.findByQuery(SYSTEM_TENANT_ID, query).get();
260 260 Assert.assertEquals(2, relations.size());
261 261 Assert.assertTrue(relations.contains(relationAB));
... ...
... ... @@ -21,7 +21,7 @@
21 21
22 22 <parent>
23 23 <groupId>org.thingsboard</groupId>
24   - <version>3.3.0-SNAPSHOT</version>
  24 + <version>3.2.2-SNAPSHOT</version>
25 25 <artifactId>msa</artifactId>
26 26 </parent>
27 27 <groupId>org.thingsboard.msa</groupId>
... ...
1 1 {
2 2 "name": "thingsboard-js-executor",
3 3 "private": true,
4   - "version": "3.3.0",
  4 + "version": "3.2.2",
5 5 "description": "ThingsBoard JavaScript Executor Microservice",
6 6 "main": "server.js",
7 7 "bin": "server.js",
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>3.3.0-SNAPSHOT</version>
  23 + <version>3.2.2-SNAPSHOT</version>
24 24 <artifactId>msa</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.msa</groupId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>3.3.0-SNAPSHOT</version>
  23 + <version>3.2.2-SNAPSHOT</version>
24 24 <artifactId>thingsboard</artifactId>
25 25 </parent>
26 26 <artifactId>msa</artifactId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>3.3.0-SNAPSHOT</version>
  23 + <version>3.2.2-SNAPSHOT</version>
24 24 <artifactId>msa</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.msa</groupId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>3.3.0-SNAPSHOT</version>
  23 + <version>3.2.2-SNAPSHOT</version>
24 24 <artifactId>msa</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.msa</groupId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard.msa</groupId>
23   - <version>3.3.0-SNAPSHOT</version>
  23 + <version>3.2.2-SNAPSHOT</version>
24 24 <artifactId>transport</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.msa.transport</groupId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard.msa</groupId>
23   - <version>3.3.0-SNAPSHOT</version>
  23 + <version>3.2.2-SNAPSHOT</version>
24 24 <artifactId>transport</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.msa.transport</groupId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard.msa</groupId>
23   - <version>3.3.0-SNAPSHOT</version>
  23 + <version>3.2.2-SNAPSHOT</version>
24 24 <artifactId>transport</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.msa.transport</groupId>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>3.3.0-SNAPSHOT</version>
  23 + <version>3.2.2-SNAPSHOT</version>
24 24 <artifactId>msa</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.msa</groupId>
... ...
1 1 {
2 2 "name": "thingsboard-web-ui",
3 3 "private": true,
4   - "version": "3.3.0",
  4 + "version": "3.2.2",
5 5 "description": "ThingsBoard Web UI Microservice",
6 6 "main": "server.js",
7 7 "bin": "server.js",
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>3.3.0-SNAPSHOT</version>
  23 + <version>3.2.2-SNAPSHOT</version>
24 24 <artifactId>msa</artifactId>
25 25 </parent>
26 26 <groupId>org.thingsboard.msa</groupId>
... ...
... ... @@ -19,11 +19,11 @@
19 19 <modelVersion>4.0.0</modelVersion>
20 20 <parent>
21 21 <groupId>org.thingsboard</groupId>
22   - <version>3.3.0-SNAPSHOT</version>
  22 + <version>3.2.2-SNAPSHOT</version>
23 23 <artifactId>thingsboard</artifactId>
24 24 </parent>
25 25 <artifactId>netty-mqtt</artifactId>
26   - <version>3.3.0-SNAPSHOT</version>
  26 + <version>3.2.2-SNAPSHOT</version>
27 27 <packaging>jar</packaging>
28 28
29 29 <name>Netty MQTT Client</name>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <groupId>org.thingsboard</groupId>
22 22 <artifactId>thingsboard</artifactId>
23   - <version>3.3.0-SNAPSHOT</version>
  23 + <version>3.2.2-SNAPSHOT</version>
24 24 <packaging>pom</packaging>
25 25
26 26 <name>Thingsboard</name>
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>3.3.0-SNAPSHOT</version>
  23 + <version>3.2.2-SNAPSHOT</version>
24 24 <artifactId>thingsboard</artifactId>
25 25 </parent>
26 26 <artifactId>rest-client</artifactId>
... ...
... ... @@ -2207,7 +2207,7 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable {
2207 2207 Map<String, String> params = new HashMap<>();
2208 2208 addPageLinkToParam(params, pageLink);
2209 2209 return restTemplate.exchange(
2210   - baseURL + "/api/tenantProfiles" + getUrlParams(pageLink),
  2210 + baseURL + "/api/tenantProfiles?" + getUrlParams(pageLink),
2211 2211 HttpMethod.GET,
2212 2212 HttpEntity.EMPTY,
2213 2213 new ParameterizedTypeReference<PageData<TenantProfile>>() {
... ... @@ -2218,7 +2218,7 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable {
2218 2218 Map<String, String> params = new HashMap<>();
2219 2219 addPageLinkToParam(params, pageLink);
2220 2220 return restTemplate.exchange(
2221   - baseURL + "/api/tenantProfileInfos" + getUrlParams(pageLink),
  2221 + baseURL + "/api/tenantProfileInfos?" + getUrlParams(pageLink),
2222 2222 HttpMethod.GET,
2223 2223 HttpEntity.EMPTY,
2224 2224 new ParameterizedTypeReference<PageData<EntityInfo>>() {
... ... @@ -2275,7 +2275,7 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable {
2275 2275 Map<String, String> params = new HashMap<>();
2276 2276 addPageLinkToParam(params, pageLink);
2277 2277 return restTemplate.exchange(
2278   - baseURL + "/api/users" + getUrlParams(pageLink),
  2278 + baseURL + "/api/users?" + getUrlParams(pageLink),
2279 2279 HttpMethod.GET,
2280 2280 HttpEntity.EMPTY,
2281 2281 new ParameterizedTypeReference<PageData<User>>() {
... ...
... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>3.3.0-SNAPSHOT</version>
  23 + <version>3.2.2-SNAPSHOT</version>
24 24 <artifactId>thingsboard</artifactId>
25 25 </parent>
26 26 <artifactId>rule-engine</artifactId>
... ...