Commit a3cb9724e3a99b3db6b86c5aeffeafc2aecdc822
Committed by
GitHub
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); | ... | ... |
application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java
... | ... | @@ -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 | } | ... | ... |
application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractSubCtx.java
0 → 100644
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); | ... | ... |
application/src/main/java/org/thingsboard/server/service/subscription/TbEntityCountSubCtx.java
0 → 100644
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,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 | } | ... | ... |
application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityCountCmd.java
0 → 100644
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 | +} | ... | ... |
application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityCountUpdate.java
0 → 100644
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 | ... | ... |
... | ... | @@ -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 | ... | ... |
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
... | ... | @@ -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)); | ... | ... |
... | ... | @@ -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> | ... | ... |
... | ... | @@ -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> | ... | ... |