Commit f673b0944bd08c144b0ffb8d09ce6c29ac25a082

Authored by Andrew Shvayka
Committed by GitHub
2 parents 5a724799 37b88908

Merge pull request #4248 from ViacheslavKlimov/feature/merge-3.3-into-snmp

Merge develop/3.3 into develop/snmp
Showing 100 changed files with 2174 additions and 575 deletions

Too many changes to show.

To preserve performance only 100 of 703 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.2.1-SNAPSHOT</version>
  23 + <version>3.3.0-SNAPSHOT</version>
24 24 <artifactId>thingsboard</artifactId>
25 25 </parent>
26 26 <artifactId>application</artifactId>
... ... @@ -87,6 +87,10 @@
87 87 </dependency>
88 88 <dependency>
89 89 <groupId>org.thingsboard.common.transport</groupId>
  90 + <artifactId>lwm2m</artifactId>
  91 + </dependency>
  92 + <dependency>
  93 + <groupId>org.thingsboard.common.transport</groupId>
90 94 <artifactId>snmp</artifactId>
91 95 </dependency>
92 96 <dependency>
... ... @@ -279,7 +283,7 @@
279 283 </dependency>
280 284 <dependency>
281 285 <groupId>org.mockito</groupId>
282   - <artifactId>mockito-all</artifactId>
  286 + <artifactId>mockito-core</artifactId>
283 287 <scope>test</scope>
284 288 </dependency>
285 289 <dependency>
... ...
... ... @@ -15,11 +15,10 @@
15 15 #
16 16
17 17 export JAVA_OPTS="$JAVA_OPTS -Dplatform=@pkg.platform@ -Dinstall.data_dir=@pkg.installFolder@/data"
18   -export JAVA_OPTS="$JAVA_OPTS -Xloggc:@pkg.logFolder@/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDetails -XX:+PrintGCDateStamps"
19   -export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10"
20   -export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark"
21   -export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled"
22   -export JAVA_OPTS="$JAVA_OPTS -XX:+CMSEdenChunksRecordAlways -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly"
  18 +export JAVA_OPTS="$JAVA_OPTS -Xlog:gc*,heap*,age*,safepoint=debug:file=@pkg.logFolder@/gc.log:time,uptime,level,tags:filecount=10,filesize=10M"
  19 +export JAVA_OPTS="$JAVA_OPTS -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError"
  20 +export JAVA_OPTS="$JAVA_OPTS -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark"
  21 +export JAVA_OPTS="$JAVA_OPTS -XX:+UseG1GC -XX:MaxGCPauseMillis=500 -XX:+UseStringDeduplication -XX:+ParallelRefProcEnabled -XX:MaxTenuringThreshold=10"
23 22 export LOG_FILENAME=${pkg.name}.out
24 23 export LOADER_PATH=${pkg.installFolder}/conf,${pkg.installFolder}/extensions
25 24 export SQL_DATA_FOLDER=${pkg.installFolder}/data/sql
... ...
... ... @@ -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 +}
... ...
... ... @@ -26,22 +26,6 @@
26 26 }
27 27 },
28 28 {
29   - "alias": "basic_timeseries",
30   - "name": "Timeseries - Flot",
31   - "descriptor": {
32   - "type": "timeseries",
33   - "sizeX": 8,
34   - "sizeY": 5,
35   - "resources": [],
36   - "templateHtml": "",
37   - "templateCss": ".legend {\n font-size: 13px;\n line-height: 10px;\n}\n\n.legend table { \n border-spacing: 0px;\n border-collapse: separate;\n}\n\n.mouse-events .flot-overlay {\n cursor: crosshair; \n}\n\n",
38   - "controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.onEditModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n return TbFlot.settingsSchema('graph');\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.datakeySettingsSchema(true, 'graph');\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\n",
39   - "settingsSchema": "{}",
40   - "dataKeySettingsSchema": "{}",
41   - "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":true,\"fillLines\":true,\"showPoints\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":true,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"shadowSize\":4,\"fontColor\":\"#545454\",\"fontSize\":10,\"xaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"grid\":{\"color\":\"#545454\",\"tickColor\":\"#DDDDDD\",\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1},\"legend\":{\"show\":true,\"position\":\"nw\",\"backgroundColor\":\"#f0f0f0\",\"backgroundOpacity\":0.85,\"labelBoxBorderColor\":\"rgba(1, 1, 1, 0.45)\"},\"decimals\":1,\"stack\":false,\"tooltipIndividual\":false},\"title\":\"Timeseries - Flot\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null}"
42   - }
43   - },
44   - {
45 29 "alias": "doughnut_chart_js",
46 30 "name": "Doughnut - Chart.js",
47 31 "descriptor": {
... ... @@ -71,7 +55,7 @@
71 55 "resources": [],
72 56 "templateHtml": "",
73 57 "templateCss": ".legend {\n font-size: 13px;\n line-height: 10px;\n}\n\n.legend table { \n border-spacing: 0px;\n border-collapse: separate;\n}\n\n.pie-label {\n font-size: 12px;\n font-family: 'Roboto';\n font-weight: bold;\n text-align: center;\n padding: 2px;\n color: white;\n}\n",
74   - "controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx, 'pie'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.onEditModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n return TbFlot.pieSettingsSchema();\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.pieDatakeySettingsSchema();\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\nself.actionSources = function() {\n return {\n 'sliceClick': {\n name: 'widget-action.pie-slice-click',\n multiple: false\n }\n };\n}\n",
  58 + "controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx, 'pie'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.onEditModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n return TbFlot.pieSettingsSchema();\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.pieDatakeySettingsSchema();\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\nself.actionSources = function() {\n return {\n 'sliceClick': {\n name: 'widget-action.pie-slice-click',\n multiple: false\n }\n };\n}\n",
75 59 "settingsSchema": "{}\n",
76 60 "dataKeySettingsSchema": "{}\n",
77 61 "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.6114638304362894,\"funcBody\":\"var value = (prevValue-20) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+20;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Third\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.9955906536344441,\"funcBody\":\"var value = (prevValue-40) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+40;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fourth\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.9430835931647599,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"radius\":1,\"fontColor\":\"#545454\",\"fontSize\":10,\"decimals\":1,\"legend\":{\"show\":true,\"position\":\"nw\",\"labelBoxBorderColor\":\"#CCCCCC\",\"backgroundColor\":\"#F0F0F0\",\"backgroundOpacity\":0.85},\"innerRadius\":0,\"showLabels\":true,\"showPercentages\":true,\"stroke\":{\"width\":5},\"tilt\":1,\"animatedPie\":false},\"title\":\"Pie - Flot\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"
... ... @@ -138,8 +122,8 @@
138 122 }
139 123 },
140 124 {
141   - "alias": "timeseries_bars_flot",
142   - "name": "Timeseries Bars - Flot",
  125 + "alias": "state_chart",
  126 + "name": "State Chart",
143 127 "descriptor": {
144 128 "type": "timeseries",
145 129 "sizeX": 8,
... ... @@ -147,15 +131,15 @@
147 131 "resources": [],
148 132 "templateHtml": "",
149 133 "templateCss": ".legend {\n font-size: 13px;\n line-height: 10px;\n}\n\n.legend table { \n border-spacing: 0px;\n border-collapse: separate;\n}\n\n.mouse-events .flot-overlay {\n cursor: crosshair; \n}\n\n",
150   - "controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx, 'bar'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.onEditModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n return TbFlot.settingsSchema('bar');\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.datakeySettingsSchema(false, 'bar');\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\n",
  134 + "controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx, 'state'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.typeParameters = function() {\n return {\n stateData: true\n };\n}\n\nself.onEditModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n return TbFlot.settingsSchema('graph');\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.datakeySettingsSchema(true, 'graph');\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\n",
151 135 "settingsSchema": "{}",
152 136 "dataKeySettingsSchema": "{}",
153   - "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":false,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":false,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000},\"aggregation\":{\"limit\":200,\"type\":\"AVG\"}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"shadowSize\":4,\"fontColor\":\"#545454\",\"fontSize\":10,\"xaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"grid\":{\"color\":\"#545454\",\"tickColor\":\"#DDDDDD\",\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1},\"stack\":true,\"tooltipIndividual\":false,\"defaultBarWidth\":600},\"title\":\"Timeseries Bars - Flot\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null,\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":true,\"actions\":{}}"
  137 + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Switch 1\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":true,\"fillLines\":true,\"showPoints\":false,\"axisPosition\":\"left\",\"showSeparateAxis\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"return Math.random() > 0.5 ? 1 : 0;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Switch 2\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":true,\"fillLines\":false,\"showPoints\":false,\"axisPosition\":\"left\"},\"_hash\":0.12775350966079668,\"funcBody\":\"return Math.random() <= 0.5 ? 1 : 0;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"shadowSize\":4,\"fontColor\":\"#545454\",\"fontSize\":10,\"xaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"showLabels\":true,\"color\":\"#545454\",\"ticksFormatter\":\"if (value > 0 && value <= 1) {\\n return 'On';\\n} else if (value === 0) {\\n return 'Off';\\n} else {\\n return '';\\n}\"},\"grid\":{\"color\":\"#545454\",\"tickColor\":\"#DDDDDD\",\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1},\"stack\":false,\"tooltipIndividual\":false,\"tooltipValueFormatter\":\"if (value > 0 && value <= 1) {\\n return 'On';\\n} else if (value === 0) {\\n return 'Off';\\n} else {\\n return '';\\n}\",\"smoothLines\":false},\"title\":\"State Chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null,\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":true,\"actions\":{},\"legendConfig\":{\"direction\":\"column\",\",position\":\"bottom\",\"showMin\":false,\"showMax\":false,\"showAvg\":false,\"showTotal\":false}}"
154 138 }
155 139 },
156 140 {
157   - "alias": "state_chart",
158   - "name": "State Chart",
  141 + "alias": "basic_timeseries",
  142 + "name": "Timeseries - Flot",
159 143 "descriptor": {
160 144 "type": "timeseries",
161 145 "sizeX": 8,
... ... @@ -163,11 +147,27 @@
163 147 "resources": [],
164 148 "templateHtml": "",
165 149 "templateCss": ".legend {\n font-size: 13px;\n line-height: 10px;\n}\n\n.legend table { \n border-spacing: 0px;\n border-collapse: separate;\n}\n\n.mouse-events .flot-overlay {\n cursor: crosshair; \n}\n\n",
166   - "controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx, 'state'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.typeParameters = function() {\n return {\n stateData: true\n };\n}\n\nself.onEditModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n return TbFlot.settingsSchema('graph');\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.datakeySettingsSchema(true, 'graph');\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\n",
  150 + "controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.onEditModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n return TbFlot.settingsSchema('graph');\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.datakeySettingsSchema(true, 'graph');\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\n",
167 151 "settingsSchema": "{}",
168 152 "dataKeySettingsSchema": "{}",
169   - "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Switch 1\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":true,\"fillLines\":true,\"showPoints\":false,\"axisPosition\":\"left\",\"showSeparateAxis\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"return Math.random() > 0.5 ? 1 : 0;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Switch 2\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":true,\"fillLines\":false,\"showPoints\":false,\"axisPosition\":\"left\"},\"_hash\":0.12775350966079668,\"funcBody\":\"return Math.random() <= 0.5 ? 1 : 0;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"shadowSize\":4,\"fontColor\":\"#545454\",\"fontSize\":10,\"xaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"showLabels\":true,\"color\":\"#545454\",\"ticksFormatter\":\"if (value > 0 && value <= 1) {\\n return 'On';\\n} else if (value === 0) {\\n return 'Off';\\n} else {\\n return '';\\n}\"},\"grid\":{\"color\":\"#545454\",\"tickColor\":\"#DDDDDD\",\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1},\"stack\":false,\"tooltipIndividual\":false,\"tooltipValueFormatter\":\"if (value > 0 && value <= 1) {\\n return 'On';\\n} else if (value === 0) {\\n return 'Off';\\n} else {\\n return '';\\n}\",\"smoothLines\":false},\"title\":\"State Chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null,\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":true,\"actions\":{},\"legendConfig\":{\"direction\":\"column\",\",position\":\"bottom\",\"showMin\":false,\"showMax\":false,\"showAvg\":false,\"showTotal\":false}}"
  153 + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":true,\"fillLines\":true,\"showPoints\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":true,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"shadowSize\":4,\"fontColor\":\"#545454\",\"fontSize\":10,\"xaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"grid\":{\"color\":\"#545454\",\"tickColor\":\"#DDDDDD\",\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1},\"legend\":{\"show\":true,\"position\":\"nw\",\"backgroundColor\":\"#f0f0f0\",\"backgroundOpacity\":0.85,\"labelBoxBorderColor\":\"rgba(1, 1, 1, 0.45)\"},\"decimals\":1,\"stack\":false,\"tooltipIndividual\":false},\"title\":\"Timeseries - Flot\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null}"
  154 + }
  155 + },
  156 + {
  157 + "alias": "timeseries_bars_flot",
  158 + "name": "Timeseries Bars - Flot",
  159 + "descriptor": {
  160 + "type": "timeseries",
  161 + "sizeX": 8,
  162 + "sizeY": 5,
  163 + "resources": [],
  164 + "templateHtml": "",
  165 + "templateCss": ".legend {\n font-size: 13px;\n line-height: 10px;\n}\n\n.legend table { \n border-spacing: 0px;\n border-collapse: separate;\n}\n\n.mouse-events .flot-overlay {\n cursor: crosshair; \n}\n\n",
  166 + "controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx, 'bar'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.onEditModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n return TbFlot.settingsSchema('bar');\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.datakeySettingsSchema(false, 'bar');\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\n",
  167 + "settingsSchema": "{}",
  168 + "dataKeySettingsSchema": "{}",
  169 + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":false,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":false,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000},\"aggregation\":{\"limit\":200,\"type\":\"AVG\"}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"shadowSize\":4,\"fontColor\":\"#545454\",\"fontSize\":10,\"xaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"grid\":{\"color\":\"#545454\",\"tickColor\":\"#DDDDDD\",\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1},\"stack\":true,\"tooltipIndividual\":false,\"defaultBarWidth\":600},\"title\":\"Timeseries Bars - Flot\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null,\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":true,\"actions\":{}}"
170 170 }
171 171 }
172 172 ]
173   -}
  173 +}
\ No newline at end of file
... ...
  1 +{
  2 + "widgetsBundle": {
  3 + "alias": "navigation_widgets",
  4 + "title": "Navigation widgets",
  5 + "image": null
  6 + },
  7 + "widgetTypes": [
  8 + {
  9 + "alias": "navigation_cards",
  10 + "name": "Navigation cards",
  11 + "descriptor": {
  12 + "type": "static",
  13 + "sizeX": 7,
  14 + "sizeY": 6,
  15 + "resources": [],
  16 + "templateHtml": "<tb-navigation-cards-widget [ctx]=\"ctx\"></tb-navigation-cards-widget>",
  17 + "templateCss": "/*#widget-container {\n overflow-y: auto;\n box-sizing: content-box !important;\n cursor: auto;\n}*/\n\n#widget-container #container {\n overflow-y: auto;\n box-sizing: content-box;\n cursor: auto;\n}",
  18 + "controllerScript": "self.onInit = function() {\n self.ctx.$scope.navigationCardsWidget.resize();\n}\n\nself.onResize = function() {\n self.ctx.$scope.navigationCardsWidget.resize();\n}\n\nself.onDestroy = function() {\n}\n",
  19 + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"filterType\": {\n \"title\": \"Filter type\",\n \"type\": \"string\",\n \"default\": \"all\"\n },\n \"filter\": {\n \"title\": \"Items\",\n \"type\": \"array\"\n }\n },\n \"required\": []\n },\n \"form\": [\n {\n \"key\": \"filterType\",\n \"type\": \"radios\",\n \"direction\": \"row\",\n \"titleMap\": [\n {\n \"value\": \"all\",\n \"name\": \"All items\"\n },\n {\n \"value\": \"include\",\n \"name\": \"Include items\"\n },\n {\n \"value\": \"exclude\",\n \"name\": \"Exclude items\"\n }\n ]\n },\n {\n \"key\": \"filter\",\n \"type\": \"rc-select\",\n \"condition\": \"model.filterType !== 'all'\",\n \"tags\": true,\n \"placeholder\": \"Enter urls to filter\",\n \"items\": [{\"value\": \"/devices\", \"label\": \"/devices\"}, {\"value\": \"/assets\", \"label\": \"/assets\"}, {\"value\": \"/deviceProfies\", \"label\": \"/deviceProfies\"}]\n }\n ]\n}\n",
  20 + "dataKeySettingsSchema": "{}\n",
  21 + "defaultConfig": "{\"datasources\":[{\"type\":\"static\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgba(255,255,255,0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"filterType\":\"all\"},\"title\":\"Navigation cards\",\"dropShadow\":false,\"showTitleIcon\":false,\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"showLegend\":false}"
  22 + }
  23 + },
  24 + {
  25 + "alias": "navigation_card",
  26 + "name": "Navigation card",
  27 + "descriptor": {
  28 + "type": "static",
  29 + "sizeX": 2.5,
  30 + "sizeY": 2,
  31 + "resources": [],
  32 + "templateHtml": "<tb-navigation-card-widget [ctx]=\"ctx\"></tb-navigation-card-widget>",
  33 + "templateCss": "",
  34 + "controllerScript": "self.onInit = function() {\n\n}\n\n\nself.onDestroy = function() {\n}\n",
  35 + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"name\": {\n \"title\": \"Title\",\n \"type\": \"string\",\n \"default\": \"{i18n:device.devices}\"\n },\n \"icon\": {\n \"title\": \"icon\",\n \"type\": \"string\",\n \"default\": \"devices_other\"\n },\n \"path\": {\n \"title\": \"Navigation path\",\n \"type\": \"string\",\n \"default\": \"/devices\"\n }\n },\n \"required\": [\"name\", \"icon\", \"path\"]\n },\n \"form\": [\n \"name\",\n {\n \"key\": \"icon\",\n \"type\": \"icon\"\n },\n \"path\"\n ]\n}\n",
  36 + "dataKeySettingsSchema": "{}\n",
  37 + "defaultConfig": "{\"datasources\":[{\"type\":\"static\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgba(255,255,255,0)\",\"color\":\"rgba(255,255,255,0.87)\",\"padding\":\"8px\",\"settings\":{\"name\":\"{i18n:device.devices}\",\"icon\":\"devices_other\",\"path\":\"/devices\"},\"title\":\"Navigation card\",\"dropShadow\":false,\"showTitleIcon\":false,\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"showLegend\":false}"
  38 + }
  39 + }
  40 + ]
  41 +}
\ No newline at end of file
... ...
... ... @@ -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;
... ...
... ... @@ -30,7 +30,8 @@ import java.util.Arrays;
30 30 "org.thingsboard.server.service.component",
31 31 "org.thingsboard.server.service.install",
32 32 "org.thingsboard.server.dao",
33   - "org.thingsboard.server.common.stats"})
  33 + "org.thingsboard.server.common.stats",
  34 + "org.thingsboard.server.cache"})
34 35 public class ThingsboardInstallApplication {
35 36
36 37 private static final String SPRING_CONFIG_NAME_KEY = "--spring.config.name";
... ...
... ... @@ -134,12 +134,12 @@ public class AppActor extends ContextAwareActor {
134 134
135 135 private void onQueueToRuleEngineMsg(QueueToRuleEngineMsg msg) {
136 136 if (TenantId.SYS_TENANT_ID.equals(msg.getTenantId())) {
137   - msg.getTbMsg().getCallback().onFailure(new RuleEngineException("Message has system tenant id!"));
  137 + msg.getMsg().getCallback().onFailure(new RuleEngineException("Message has system tenant id!"));
138 138 } else {
139 139 if (!deletedTenants.contains(msg.getTenantId())) {
140 140 getOrCreateTenantActor(msg.getTenantId()).tell(msg);
141 141 } else {
142   - msg.getTbMsg().getCallback().onSuccess();
  142 + msg.getMsg().getCallback().onSuccess();
143 143 }
144 144 }
145 145 }
... ...
... ... @@ -62,7 +62,7 @@ public class DeviceActor extends ContextAwareActor {
62 62 processor.processAttributesUpdate(ctx, (DeviceAttributesEventNotificationMsg) msg);
63 63 break;
64 64 case DEVICE_CREDENTIALS_UPDATE_TO_DEVICE_ACTOR_MSG:
65   - processor.processCredentialsUpdate();
  65 + processor.processCredentialsUpdate(msg);
66 66 break;
67 67 case DEVICE_NAME_OR_TYPE_UPDATE_TO_DEVICE_ACTOR_MSG:
68 68 processor.processNameOrTypeUpdate((DeviceNameOrTypeUpdateMsg) msg);
... ...
... ... @@ -24,6 +24,7 @@ import lombok.extern.slf4j.Slf4j;
24 24 import org.apache.commons.collections.CollectionUtils;
25 25 import org.thingsboard.rule.engine.api.RpcError;
26 26 import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg;
  27 +import org.thingsboard.rule.engine.api.msg.DeviceCredentialsUpdateNotificationMsg;
27 28 import org.thingsboard.rule.engine.api.msg.DeviceNameOrTypeUpdateMsg;
28 29 import org.thingsboard.server.actors.ActorSystemContext;
29 30 import org.thingsboard.server.actors.TbActorCtx;
... ... @@ -36,6 +37,9 @@ import org.thingsboard.server.common.data.kv.AttributeKey;
36 37 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
37 38 import org.thingsboard.server.common.data.kv.KvEntry;
38 39 import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody;
  40 +import org.thingsboard.server.common.data.security.DeviceCredentials;
  41 +import org.thingsboard.server.common.data.security.DeviceCredentialsType;
  42 +import org.thingsboard.server.common.msg.TbActorMsg;
39 43 import org.thingsboard.server.common.msg.TbMsgMetaData;
40 44 import org.thingsboard.server.common.msg.queue.TbCallback;
41 45 import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
... ... @@ -61,6 +65,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseM
61 65 import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg;
62 66 import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg;
63 67 import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg;
  68 +import org.thingsboard.server.gen.transport.TransportProtos.ToTransportUpdateCredentialsProto;
64 69 import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto;
65 70 import org.thingsboard.server.service.rpc.FromDeviceRpcResponse;
66 71 import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg;
... ... @@ -450,11 +455,19 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
450 455 dumpSessions();
451 456 }
452 457
453   - void processCredentialsUpdate() {
454   - sessions.forEach(this::notifyTransportAboutClosedSession);
455   - attributeSubscriptions.clear();
456   - rpcSubscriptions.clear();
457   - dumpSessions();
  458 + void processCredentialsUpdate(TbActorMsg msg) {
  459 + if (((DeviceCredentialsUpdateNotificationMsg) msg).getDeviceCredentials().getCredentialsType() == DeviceCredentialsType.LWM2M_CREDENTIALS) {
  460 + log.info("1) LwM2Mtype: ");
  461 + sessions.forEach((k, v) -> {
  462 + notifyTransportAboutProfileUpdate(k, v, ((DeviceCredentialsUpdateNotificationMsg) msg).getDeviceCredentials());
  463 + });
  464 + } else {
  465 + sessions.forEach(this::notifyTransportAboutClosedSession);
  466 + attributeSubscriptions.clear();
  467 + rpcSubscriptions.clear();
  468 + dumpSessions();
  469 +
  470 + }
458 471 }
459 472
460 473 private void notifyTransportAboutClosedSession(UUID sessionId, SessionInfoMetaData sessionMd) {
... ... @@ -465,6 +478,18 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
465 478 systemContext.getTbCoreToTransportService().process(sessionMd.getSessionInfo().getNodeId(), msg);
466 479 }
467 480
  481 + void notifyTransportAboutProfileUpdate(UUID sessionId, SessionInfoMetaData sessionMd, DeviceCredentials deviceCredentials) {
  482 + log.info("2) LwM2Mtype: ");
  483 + TransportProtos.ToTransportUpdateCredentialsProto.Builder notification = TransportProtos.ToTransportUpdateCredentialsProto.newBuilder();
  484 + notification.addCredentialsId(deviceCredentials.getCredentialsId());
  485 + notification.addCredentialsValue(deviceCredentials.getCredentialsValue());
  486 + ToTransportMsg msg = ToTransportMsg.newBuilder()
  487 + .setSessionIdMSB(sessionId.getMostSignificantBits())
  488 + .setSessionIdLSB(sessionId.getLeastSignificantBits())
  489 + .setToTransportUpdateCredentialsNotification(notification).build();
  490 + systemContext.getTbCoreToTransportService().process(sessionMd.getSessionInfo().getNodeId(), msg);
  491 + }
  492 +
468 493 void processNameOrTypeUpdate(DeviceNameOrTypeUpdateMsg msg) {
469 494 this.deviceName = msg.getDeviceName();
470 495 this.deviceType = msg.getDeviceType();
... ...
... ... @@ -19,7 +19,6 @@ import com.fasterxml.jackson.core.JsonProcessingException;
19 19 import com.fasterxml.jackson.databind.ObjectMapper;
20 20 import io.netty.channel.EventLoopGroup;
21 21 import lombok.extern.slf4j.Slf4j;
22   -import org.springframework.data.redis.core.RedisTemplate;
23 22 import org.springframework.util.StringUtils;
24 23 import org.thingsboard.common.util.ListeningExecutor;
25 24 import org.thingsboard.rule.engine.api.MailService;
... ... @@ -34,11 +33,11 @@ import org.thingsboard.rule.engine.api.TbRelationTypes;
34 33 import org.thingsboard.rule.engine.api.sms.SmsSenderFactory;
35 34 import org.thingsboard.server.actors.ActorSystemContext;
36 35 import org.thingsboard.server.actors.TbActorRef;
37   -import org.thingsboard.server.common.data.ApiUsageRecordKey;
38 36 import org.thingsboard.server.common.data.Customer;
39 37 import org.thingsboard.server.common.data.DataConstants;
40 38 import org.thingsboard.server.common.data.Device;
41 39 import org.thingsboard.server.common.data.DeviceProfile;
  40 +import org.thingsboard.server.common.data.EntityType;
42 41 import org.thingsboard.server.common.data.TenantProfile;
43 42 import org.thingsboard.server.common.data.alarm.Alarm;
44 43 import org.thingsboard.server.common.data.asset.Asset;
... ... @@ -90,10 +89,12 @@ class DefaultTbContext implements TbContext {
90 89 public final static ObjectMapper mapper = new ObjectMapper();
91 90
92 91 private final ActorSystemContext mainCtx;
  92 + private final String ruleChainName;
93 93 private final RuleNodeCtx nodeCtx;
94 94
95   - public DefaultTbContext(ActorSystemContext mainCtx, RuleNodeCtx nodeCtx) {
  95 + public DefaultTbContext(ActorSystemContext mainCtx, String ruleChainName, RuleNodeCtx nodeCtx) {
96 96 this.mainCtx = mainCtx;
  97 + this.ruleChainName = ruleChainName;
97 98 this.nodeCtx = nodeCtx;
98 99 }
99 100
... ... @@ -117,13 +118,13 @@ class DefaultTbContext implements TbContext {
117 118 relationTypes.forEach(relationType -> mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), msg, relationType, th));
118 119 }
119 120 msg.getCallback().onProcessingEnd(nodeCtx.getSelf().getId());
120   - nodeCtx.getChainActor().tell(new RuleNodeToRuleChainTellNextMsg(nodeCtx.getSelf().getId(), relationTypes, msg, th != null ? th.getMessage() : null));
  121 + nodeCtx.getChainActor().tell(new RuleNodeToRuleChainTellNextMsg(nodeCtx.getSelf().getRuleChainId(), nodeCtx.getSelf().getId(), relationTypes, msg, th != null ? th.getMessage() : null));
121 122 }
122 123
123 124 @Override
124 125 public void tellSelf(TbMsg msg, long delayMs) {
125 126 //TODO: add persistence layer
126   - scheduleMsgWithDelay(new RuleNodeToSelfMsg(msg), delayMs, nodeCtx.getSelfActor());
  127 + scheduleMsgWithDelay(new RuleNodeToSelfMsg(this, msg), delayMs, nodeCtx.getSelfActor());
127 128 }
128 129
129 130 @Override
... ... @@ -254,7 +255,8 @@ class DefaultTbContext implements TbContext {
254 255 } else {
255 256 failureMessage = null;
256 257 }
257   - nodeCtx.getChainActor().tell(new RuleNodeToRuleChainTellNextMsg(nodeCtx.getSelf().getId(), Collections.singleton(TbRelationTypes.FAILURE),
  258 + nodeCtx.getChainActor().tell(new RuleNodeToRuleChainTellNextMsg(nodeCtx.getSelf().getRuleChainId(),
  259 + nodeCtx.getSelf().getId(), Collections.singleton(TbRelationTypes.FAILURE),
258 260 msg, failureMessage));
259 261 }
260 262
... ... @@ -277,7 +279,21 @@ class DefaultTbContext implements TbContext {
277 279 }
278 280
279 281 public TbMsg deviceCreatedMsg(Device device, RuleNodeId ruleNodeId) {
280   - 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);
281 297 }
282 298
283 299 public TbMsg assetCreatedMsg(Asset asset, RuleNodeId ruleNodeId) {
... ... @@ -285,12 +301,31 @@ class DefaultTbContext implements TbContext {
285 301 }
286 302
287 303 public TbMsg alarmActionMsg(Alarm alarm, RuleNodeId ruleNodeId, String action) {
288   - 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);
289 320 }
290 321
291 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) {
292 327 try {
293   - 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);
294 329 } catch (JsonProcessingException | IllegalArgumentException e) {
295 330 throw new RuntimeException("Failed to process " + id.getEntityType().name().toLowerCase() + " " + action + " msg: " + e);
296 331 }
... ... @@ -302,6 +337,16 @@ class DefaultTbContext implements TbContext {
302 337 }
303 338
304 339 @Override
  340 + public RuleNode getSelf() {
  341 + return nodeCtx.getSelf();
  342 + }
  343 +
  344 + @Override
  345 + public String getRuleChainName() {
  346 + return ruleChainName;
  347 + }
  348 +
  349 + @Override
305 350 public TenantId getTenantId() {
306 351 return nodeCtx.getTenantId();
307 352 }
... ... @@ -476,11 +521,6 @@ class DefaultTbContext implements TbContext {
476 521 }
477 522
478 523 @Override
479   - public RedisTemplate<String, Object> getRedisTemplate() {
480   - return mainCtx.getRedisTemplate();
481   - }
482   -
483   - @Override
484 524 public PageData<RuleNodeState> findRuleNodeStates(PageLink pageLink) {
485 525 if (log.isDebugEnabled()) {
486 526 log.debug("[{}][{}] Fetch Rule Node States.", getTenantId(), getSelfId());
... ...
... ... @@ -23,7 +23,6 @@ import org.thingsboard.server.actors.TbActorRef;
23 23 import org.thingsboard.server.actors.TbEntityActorId;
24 24 import org.thingsboard.server.actors.service.DefaultActorService;
25 25 import org.thingsboard.server.actors.shared.ComponentMsgProcessor;
26   -import org.thingsboard.server.common.data.ApiUsageRecordKey;
27 26 import org.thingsboard.server.common.data.EntityType;
28 27 import org.thingsboard.server.common.data.id.EntityId;
29 28 import org.thingsboard.server.common.data.id.RuleChainId;
... ... @@ -36,6 +35,7 @@ import org.thingsboard.server.common.data.rule.RuleChain;
36 35 import org.thingsboard.server.common.data.rule.RuleNode;
37 36 import org.thingsboard.server.common.msg.TbMsg;
38 37 import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
  38 +import org.thingsboard.server.common.msg.plugin.RuleNodeUpdatedMsg;
39 39 import org.thingsboard.server.common.msg.queue.PartitionChangeMsg;
40 40 import org.thingsboard.server.common.msg.queue.QueueToRuleEngineMsg;
41 41 import org.thingsboard.server.common.msg.queue.RuleEngineException;
... ... @@ -132,7 +132,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
132 132 } else {
133 133 log.trace("[{}][{}] Updating rule node [{}]: {}", entityId, ruleNode.getId(), ruleNode.getName(), ruleNode);
134 134 existing.setSelf(ruleNode);
135   - existing.getSelfActor().tellWithHighPriority(new ComponentLifecycleMsg(tenantId, existing.getSelf().getId(), ComponentLifecycleEvent.UPDATED));
  135 + existing.getSelfActor().tellWithHighPriority(new RuleNodeUpdatedMsg(tenantId, existing.getSelf().getId()));
136 136 }
137 137 }
138 138
... ... @@ -197,11 +197,11 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
197 197 }
198 198
199 199 void onQueueToRuleEngineMsg(QueueToRuleEngineMsg envelope) {
200   - TbMsg msg = envelope.getTbMsg();
  200 + TbMsg msg = envelope.getMsg();
201 201 log.trace("[{}][{}] Processing message [{}]: {}", entityId, firstId, msg.getId(), msg);
202 202 if (envelope.getRelationTypes() == null || envelope.getRelationTypes().isEmpty()) {
203 203 try {
204   - checkActive(envelope.getTbMsg());
  204 + checkActive(envelope.getMsg());
205 205 RuleNodeId targetId = msg.getRuleNodeId();
206 206 RuleNodeCtx targetCtx;
207 207 if (targetId == null) {
... ... @@ -218,12 +218,12 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
218 218 msg.getCallback().onSuccess();
219 219 }
220 220 } catch (RuleNodeException rne) {
221   - envelope.getTbMsg().getCallback().onFailure(rne);
  221 + envelope.getMsg().getCallback().onFailure(rne);
222 222 } catch (Exception e) {
223   - envelope.getTbMsg().getCallback().onFailure(new RuleEngineException(e.getMessage()));
  223 + envelope.getMsg().getCallback().onFailure(new RuleEngineException(e.getMessage()));
224 224 }
225 225 } else {
226   - onTellNext(envelope.getTbMsg(), envelope.getTbMsg().getRuleNodeId(), envelope.getRelationTypes(), envelope.getFailureMessage());
  226 + onTellNext(envelope.getMsg(), envelope.getMsg().getRuleNodeId(), envelope.getRelationTypes(), envelope.getFailureMessage());
227 227 }
228 228 }
229 229
... ... @@ -335,7 +335,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
335 335
336 336 private void pushMsgToNode(RuleNodeCtx nodeCtx, TbMsg msg, String fromRelationType) {
337 337 if (nodeCtx != null) {
338   - nodeCtx.getSelfActor().tell(new RuleChainToRuleNodeMsg(new DefaultTbContext(systemContext, nodeCtx), msg, fromRelationType));
  338 + nodeCtx.getSelfActor().tell(new RuleChainToRuleNodeMsg(new DefaultTbContext(systemContext, ruleChainName, nodeCtx), msg, fromRelationType));
339 339 } else {
340 340 log.error("[{}][{}] RuleNodeCtx is empty", entityId, ruleChainName);
341 341 msg.getCallback().onFailure(new RuleEngineException("Rule Node CTX is empty"));
... ...
... ... @@ -15,24 +15,44 @@
15 15 */
16 16 package org.thingsboard.server.actors.ruleChain;
17 17
18   -import lombok.Data;
  18 +import lombok.EqualsAndHashCode;
  19 +import lombok.Getter;
  20 +import lombok.ToString;
19 21 import org.thingsboard.server.common.data.id.RuleChainId;
20 22 import org.thingsboard.server.common.msg.MsgType;
21   -import org.thingsboard.server.common.msg.TbActorMsg;
  23 +import org.thingsboard.server.common.msg.TbActorStopReason;
22 24 import org.thingsboard.server.common.msg.TbMsg;
  25 +import org.thingsboard.server.common.msg.TbRuleEngineActorMsg;
23 26 import org.thingsboard.server.common.msg.aware.RuleChainAwareMsg;
  27 +import org.thingsboard.server.common.msg.queue.RuleEngineException;
24 28
25 29 /**
26 30 * Created by ashvayka on 19.03.18.
27 31 */
28   -@Data
29   -public final class RuleChainToRuleChainMsg implements TbActorMsg, RuleChainAwareMsg {
  32 +@EqualsAndHashCode(callSuper = true)
  33 +@ToString
  34 +public final class RuleChainToRuleChainMsg extends TbRuleEngineActorMsg implements RuleChainAwareMsg {
30 35
  36 + @Getter
31 37 private final RuleChainId target;
  38 + @Getter
32 39 private final RuleChainId source;
33   - private final TbMsg msg;
  40 + @Getter
34 41 private final String fromRelationType;
35 42
  43 + public RuleChainToRuleChainMsg(RuleChainId target, RuleChainId source, TbMsg tbMsg, String fromRelationType) {
  44 + super(tbMsg);
  45 + this.target = target;
  46 + this.source = source;
  47 + this.fromRelationType = fromRelationType;
  48 + }
  49 +
  50 + @Override
  51 + public void onTbActorStopped(TbActorStopReason reason) {
  52 + String message = reason == TbActorStopReason.STOPPED ? String.format("Rule chain [%s] stopped", target.getId()) : String.format("Failed to initialize rule chain [%s]!", target.getId());
  53 + msg.getCallback().onFailure(new RuleEngineException(message));
  54 + }
  55 +
36 56 @Override
37 57 public RuleChainId getRuleChainId() {
38 58 return target;
... ...
... ... @@ -15,22 +15,28 @@
15 15 */
16 16 package org.thingsboard.server.actors.ruleChain;
17 17
18   -import lombok.Data;
  18 +import lombok.EqualsAndHashCode;
  19 +import lombok.Getter;
  20 +import lombok.ToString;
19 21 import org.thingsboard.rule.engine.api.TbContext;
20 22 import org.thingsboard.server.common.msg.MsgType;
21   -import org.thingsboard.server.common.msg.TbActorMsg;
22 23 import org.thingsboard.server.common.msg.TbMsg;
23 24
24 25 /**
25 26 * Created by ashvayka on 19.03.18.
26 27 */
27   -@Data
28   -final class RuleChainToRuleNodeMsg implements TbActorMsg {
  28 +@EqualsAndHashCode(callSuper = true)
  29 +@ToString
  30 +final class RuleChainToRuleNodeMsg extends TbToRuleNodeActorMsg {
29 31
30   - private final TbContext ctx;
31   - private final TbMsg msg;
  32 + @Getter
32 33 private final String fromRelationType;
33 34
  35 + public RuleChainToRuleNodeMsg(TbContext ctx, TbMsg tbMsg, String fromRelationType) {
  36 + super(ctx, tbMsg);
  37 + this.fromRelationType = fromRelationType;
  38 + }
  39 +
34 40 @Override
35 41 public MsgType getMsgType() {
36 42 return MsgType.RULE_CHAIN_TO_RULE_MSG;
... ...
... ... @@ -26,7 +26,6 @@ import org.thingsboard.server.actors.service.ContextBasedCreator;
26 26 import org.thingsboard.server.common.data.id.RuleChainId;
27 27 import org.thingsboard.server.common.data.id.RuleNodeId;
28 28 import org.thingsboard.server.common.data.id.TenantId;
29   -import org.thingsboard.server.common.data.rule.RuleChain;
30 29 import org.thingsboard.server.common.msg.TbActorMsg;
31 30 import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
32 31 import org.thingsboard.server.common.msg.queue.PartitionChangeMsg;
... ... @@ -54,14 +53,12 @@ public class RuleNodeActor extends ComponentActor<RuleNodeId, RuleNodeActorMessa
54 53 protected boolean doProcess(TbActorMsg msg) {
55 54 switch (msg.getMsgType()) {
56 55 case COMPONENT_LIFE_CYCLE_MSG:
  56 + case RULE_NODE_UPDATED_MSG:
57 57 onComponentLifecycleMsg((ComponentLifecycleMsg) msg);
58 58 break;
59 59 case RULE_CHAIN_TO_RULE_MSG:
60 60 onRuleChainToRuleNodeMsg((RuleChainToRuleNodeMsg) msg);
61 61 break;
62   - case RULE_TO_SELF_ERROR_MSG:
63   - onRuleNodeToSelfErrorMsg((RuleNodeToSelfErrorMsg) msg);
64   - break;
65 62 case RULE_TO_SELF_MSG:
66 63 onRuleNodeToSelfMsg((RuleNodeToSelfMsg) msg);
67 64 break;
... ... @@ -101,10 +98,6 @@ public class RuleNodeActor extends ComponentActor<RuleNodeId, RuleNodeActorMessa
101 98 }
102 99 }
103 100
104   - private void onRuleNodeToSelfErrorMsg(RuleNodeToSelfErrorMsg msg) {
105   - logAndPersist("onRuleMsg", ActorSystemContext.toException(msg.getError()));
106   - }
107   -
108 101 public static class ActorCreator extends ContextBasedCreator {
109 102
110 103 private final TenantId tenantId;
... ...
... ... @@ -20,14 +20,13 @@ import org.thingsboard.rule.engine.api.TbNodeConfiguration;
20 20 import org.thingsboard.server.actors.ActorSystemContext;
21 21 import org.thingsboard.server.actors.TbActorCtx;
22 22 import org.thingsboard.server.actors.TbActorRef;
  23 +import org.thingsboard.server.actors.TbRuleNodeUpdateException;
23 24 import org.thingsboard.server.actors.shared.ComponentMsgProcessor;
24 25 import org.thingsboard.server.common.data.ApiUsageRecordKey;
25   -import org.thingsboard.server.common.data.TenantProfile;
26 26 import org.thingsboard.server.common.data.id.RuleNodeId;
27 27 import org.thingsboard.server.common.data.id.TenantId;
28 28 import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
29 29 import org.thingsboard.server.common.data.rule.RuleNode;
30   -import org.thingsboard.server.common.data.tenant.profile.TenantProfileConfiguration;
31 30 import org.thingsboard.server.common.msg.TbMsg;
32 31 import org.thingsboard.server.common.msg.queue.PartitionChangeMsg;
33 32 import org.thingsboard.server.common.msg.queue.RuleNodeException;
... ... @@ -54,7 +53,7 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
54 53 this.ruleChainName = ruleChainName;
55 54 this.self = self;
56 55 this.ruleNode = systemContext.getRuleChainService().findRuleNodeById(tenantId, entityId);
57   - this.defaultCtx = new DefaultTbContext(systemContext, new RuleNodeCtx(tenantId, parent, self, ruleNode));
  56 + this.defaultCtx = new DefaultTbContext(systemContext, ruleChainName, new RuleNodeCtx(tenantId, parent, self, ruleNode));
58 57 this.info = new RuleNodeInfo(ruleNodeId, ruleChainName, ruleNode != null ? ruleNode.getName() : "Unknown");
59 58 }
60 59
... ... @@ -78,7 +77,11 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
78 77 if (tbNode != null) {
79 78 tbNode.destroy();
80 79 }
81   - start(context);
  80 + try {
  81 + start(context);
  82 + } catch (Exception e) {
  83 + throw new TbRuleNodeUpdateException("Failed to update rule node", e);
  84 + }
82 85 }
83 86 }
84 87
... ... @@ -147,7 +150,7 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
147 150 TbNode tbNode = null;
148 151 if (ruleNode != null) {
149 152 Class<?> componentClazz = Class.forName(ruleNode.getType());
150   - tbNode = (TbNode) (componentClazz.newInstance());
  153 + tbNode = (TbNode) (componentClazz.getDeclaredConstructor().newInstance());
151 154 tbNode.init(defaultCtx, new TbNodeConfiguration(ruleNode.getConfiguration()));
152 155 }
153 156 return tbNode;
... ...
... ... @@ -15,11 +15,16 @@
15 15 */
16 16 package org.thingsboard.server.actors.ruleChain;
17 17
18   -import lombok.Data;
  18 +import lombok.EqualsAndHashCode;
  19 +import lombok.Getter;
  20 +import lombok.ToString;
  21 +import org.thingsboard.server.common.data.id.RuleChainId;
19 22 import org.thingsboard.server.common.data.id.RuleNodeId;
20 23 import org.thingsboard.server.common.msg.MsgType;
21   -import org.thingsboard.server.common.msg.TbActorMsg;
  24 +import org.thingsboard.server.common.msg.TbActorStopReason;
22 25 import org.thingsboard.server.common.msg.TbMsg;
  26 +import org.thingsboard.server.common.msg.TbRuleEngineActorMsg;
  27 +import org.thingsboard.server.common.msg.queue.RuleEngineException;
23 28
24 29 import java.io.Serializable;
25 30 import java.util.Set;
... ... @@ -27,15 +32,34 @@ import java.util.Set;
27 32 /**
28 33 * Created by ashvayka on 19.03.18.
29 34 */
30   -@Data
31   -class RuleNodeToRuleChainTellNextMsg implements TbActorMsg, Serializable {
  35 +@EqualsAndHashCode(callSuper = true)
  36 +@ToString
  37 +class RuleNodeToRuleChainTellNextMsg extends TbRuleEngineActorMsg implements Serializable {
32 38
33 39 private static final long serialVersionUID = 4577026446412871820L;
  40 + @Getter
  41 + private final RuleChainId ruleChainId;
  42 + @Getter
34 43 private final RuleNodeId originator;
  44 + @Getter
35 45 private final Set<String> relationTypes;
36   - private final TbMsg msg;
  46 + @Getter
37 47 private final String failureMessage;
38 48
  49 + public RuleNodeToRuleChainTellNextMsg(RuleChainId ruleChainId, RuleNodeId originator, Set<String> relationTypes, TbMsg tbMsg, String failureMessage) {
  50 + super(tbMsg);
  51 + this.ruleChainId = ruleChainId;
  52 + this.originator = originator;
  53 + this.relationTypes = relationTypes;
  54 + this.failureMessage = failureMessage;
  55 + }
  56 +
  57 + @Override
  58 + public void onTbActorStopped(TbActorStopReason reason) {
  59 + String message = reason == TbActorStopReason.STOPPED ? String.format("Rule chain [%s] stopped", ruleChainId.getId()) : String.format("Failed to initialize rule chain [%s]!", ruleChainId.getId());
  60 + msg.getCallback().onFailure(new RuleEngineException(message));
  61 + }
  62 +
39 63 @Override
40 64 public MsgType getMsgType() {
41 65 return MsgType.RULE_TO_RULE_CHAIN_TELL_NEXT_MSG;
... ...
... ... @@ -15,18 +15,25 @@
15 15 */
16 16 package org.thingsboard.server.actors.ruleChain;
17 17
18   -import lombok.Data;
  18 +import lombok.EqualsAndHashCode;
  19 +import lombok.ToString;
  20 +import org.thingsboard.rule.engine.api.TbContext;
19 21 import org.thingsboard.server.common.msg.MsgType;
20   -import org.thingsboard.server.common.msg.TbActorMsg;
  22 +import org.thingsboard.server.common.msg.TbActorStopReason;
21 23 import org.thingsboard.server.common.msg.TbMsg;
  24 +import org.thingsboard.server.common.msg.TbRuleEngineActorMsg;
  25 +import org.thingsboard.server.common.msg.queue.RuleNodeException;
22 26
23 27 /**
24 28 * Created by ashvayka on 19.03.18.
25 29 */
26   -@Data
27   -final class RuleNodeToSelfMsg implements TbActorMsg {
  30 +@EqualsAndHashCode(callSuper = true)
  31 +@ToString
  32 +final class RuleNodeToSelfMsg extends TbToRuleNodeActorMsg {
28 33
29   - private final TbMsg msg;
  34 + public RuleNodeToSelfMsg(TbContext ctx, TbMsg tbMsg) {
  35 + super(ctx, tbMsg);
  36 + }
30 37
31 38 @Override
32 39 public MsgType getMsgType() {
... ...
  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.actors.ruleChain;
  17 +
  18 +import lombok.EqualsAndHashCode;
  19 +import lombok.Getter;
  20 +import org.thingsboard.rule.engine.api.TbContext;
  21 +import org.thingsboard.server.common.msg.TbActorStopReason;
  22 +import org.thingsboard.server.common.msg.TbMsg;
  23 +import org.thingsboard.server.common.msg.TbRuleEngineActorMsg;
  24 +import org.thingsboard.server.common.msg.queue.RuleNodeException;
  25 +
  26 +@EqualsAndHashCode(callSuper = true)
  27 +public abstract class TbToRuleNodeActorMsg extends TbRuleEngineActorMsg {
  28 +
  29 + @Getter
  30 + private final TbContext ctx;
  31 +
  32 + public TbToRuleNodeActorMsg(TbContext ctx, TbMsg tbMsg) {
  33 + super(tbMsg);
  34 + this.ctx = ctx;
  35 + }
  36 +
  37 + @Override
  38 + public void onTbActorStopped(TbActorStopReason reason) {
  39 + String message = reason == TbActorStopReason.STOPPED ? "Rule node stopped" : "Failed to initialize rule node!";
  40 + msg.getCallback().onFailure(new RuleNodeException(message, ctx.getRuleChainName(), ctx.getSelf()));
  41 + }
  42 +}
... ...
... ... @@ -20,6 +20,7 @@ import org.thingsboard.server.actors.ActorSystemContext;
20 20 import org.thingsboard.server.actors.TbActor;
21 21 import org.thingsboard.server.actors.TbActorCtx;
22 22 import org.thingsboard.server.actors.TbActorException;
  23 +import org.thingsboard.server.actors.TbRuleNodeUpdateException;
23 24 import org.thingsboard.server.actors.shared.ComponentMsgProcessor;
24 25 import org.thingsboard.server.actors.stats.StatsPersistMsg;
25 26 import org.thingsboard.server.common.data.id.EntityId;
... ... @@ -123,6 +124,9 @@ public abstract class ComponentActor<T extends EntityId, P extends ComponentMsgP
123 124 } catch (Exception e) {
124 125 logAndPersist("onLifecycleMsg", e, true);
125 126 logLifecycleEvent(msg.getEvent(), e);
  127 + if (e instanceof TbRuleNodeUpdateException) {
  128 + throw (TbRuleNodeUpdateException) e;
  129 + }
126 130 }
127 131 }
128 132
... ...
... ... @@ -34,6 +34,7 @@ import org.thingsboard.server.actors.app.AppInitMsg;
34 34 import org.thingsboard.server.actors.stats.StatsActor;
35 35 import org.thingsboard.server.common.msg.queue.PartitionChangeMsg;
36 36 import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
  37 +import org.thingsboard.server.queue.discovery.TbApplicationEventListener;
37 38
38 39 import javax.annotation.PostConstruct;
39 40 import javax.annotation.PreDestroy;
... ... @@ -43,7 +44,7 @@ import java.util.concurrent.ScheduledExecutorService;
43 44
44 45 @Service
45 46 @Slf4j
46   -public class DefaultActorService implements ActorService {
  47 +public class DefaultActorService extends TbApplicationEventListener<PartitionChangeEvent> implements ActorService {
47 48
48 49 public static final String APP_DISPATCHER_NAME = "app-dispatcher";
49 50 public static final String TENANT_DISPATCHER_NAME = "tenant-dispatcher";
... ... @@ -120,10 +121,10 @@ public class DefaultActorService implements ActorService {
120 121 appActor.tellWithHighPriority(new AppInitMsg());
121 122 }
122 123
123   - @EventListener(PartitionChangeEvent.class)
124   - public void onApplicationEvent(PartitionChangeEvent partitionChangeEvent) {
  124 + @Override
  125 + protected void onTbApplicationEvent(PartitionChangeEvent event) {
125 126 log.info("Received partition change event.");
126   - this.appActor.tellWithHighPriority(new PartitionChangeMsg(partitionChangeEvent.getServiceQueueKey(), partitionChangeEvent.getPartitions()));
  127 + this.appActor.tellWithHighPriority(new PartitionChangeMsg(event.getServiceQueueKey(), event.getPartitions()));
127 128 }
128 129
129 130 @PreDestroy
... ...
... ... @@ -28,10 +28,10 @@ import org.thingsboard.server.common.msg.TbActorMsg;
28 28 @ToString
29 29 public final class StatsPersistMsg implements TbActorMsg {
30 30
31   - private long messagesProcessed;
32   - private long errorsOccurred;
33   - private TenantId tenantId;
34   - private EntityId entityId;
  31 + private final long messagesProcessed;
  32 + private final long errorsOccurred;
  33 + private final TenantId tenantId;
  34 + private final EntityId entityId;
35 35
36 36 @Override
37 37 public MsgType getMsgType() {
... ...
... ... @@ -18,7 +18,7 @@ package org.thingsboard.server.actors.stats;
18 18 import org.thingsboard.server.common.msg.MsgType;
19 19 import org.thingsboard.server.common.msg.TbActorMsg;
20 20
21   -public final class StatsPersistTick implements TbActorMsg{
  21 +public final class StatsPersistTick implements TbActorMsg {
22 22 @Override
23 23 public MsgType getMsgType() {
24 24 return MsgType.STATS_PERSIST_TICK_MSG;
... ...
... ... @@ -119,7 +119,7 @@ public class TenantActor extends RuleChainManagerActor {
119 119 log.info("[{}] Processing missing Tenant msg: {}", tenantId, msg);
120 120 if (msg.getMsgType().equals(MsgType.QUEUE_TO_RULE_ENGINE_MSG)) {
121 121 QueueToRuleEngineMsg queueMsg = (QueueToRuleEngineMsg) msg;
122   - queueMsg.getTbMsg().getCallback().onSuccess();
  122 + queueMsg.getMsg().getCallback().onSuccess();
123 123 } else if (msg.getMsgType().equals(MsgType.TRANSPORT_TO_DEVICE_ACTOR_MSG)) {
124 124 TransportToDeviceActorMsgWrapper transportMsg = (TransportToDeviceActorMsgWrapper) msg;
125 125 transportMsg.getCallback().onSuccess();
... ... @@ -177,7 +177,7 @@ public class TenantActor extends RuleChainManagerActor {
177 177 log.warn("RECEIVED INVALID MESSAGE: {}", msg);
178 178 return;
179 179 }
180   - TbMsg tbMsg = msg.getTbMsg();
  180 + TbMsg tbMsg = msg.getMsg();
181 181 if (apiUsageState.isReExecEnabled()) {
182 182 if (tbMsg.getRuleChainId() == null) {
183 183 if (getRootChainActor() != null) {
... ...
... ... @@ -91,6 +91,7 @@ public class CustomOAuth2AuthorizationRequestResolver implements OAuth2Authoriza
91 91 return action;
92 92 }
93 93
  94 + @SuppressWarnings("deprecation")
94 95 private OAuth2AuthorizationRequest resolve(HttpServletRequest request, String registrationId, String redirectUriAction) {
95 96 if (registrationId == null) {
96 97 return null;
... ...
... ... @@ -48,6 +48,7 @@ import org.thingsboard.server.service.security.auth.jwt.RefreshTokenAuthenticati
48 48 import org.thingsboard.server.service.security.auth.jwt.RefreshTokenProcessingFilter;
49 49 import org.thingsboard.server.service.security.auth.jwt.SkipPathRequestMatcher;
50 50 import org.thingsboard.server.service.security.auth.jwt.extractor.TokenExtractor;
  51 +import org.thingsboard.server.service.security.auth.oauth2.HttpCookieOAuth2AuthorizationRequestRepository;
51 52 import org.thingsboard.server.service.security.auth.rest.RestAuthenticationProvider;
52 53 import org.thingsboard.server.service.security.auth.rest.RestLoginProcessingFilter;
53 54 import org.thingsboard.server.service.security.auth.rest.RestPublicLoginProcessingFilter;
... ... @@ -84,6 +85,9 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt
84 85 @Qualifier("oauth2AuthenticationFailureHandler")
85 86 private AuthenticationFailureHandler oauth2AuthenticationFailureHandler;
86 87
  88 + @Autowired(required = false)
  89 + private HttpCookieOAuth2AuthorizationRequestRepository httpCookieOAuth2AuthorizationRequestRepository;
  90 +
87 91 @Autowired
88 92 @Qualifier("defaultAuthenticationSuccessHandler")
89 93 private AuthenticationSuccessHandler successHandler;
... ... @@ -127,7 +131,7 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt
127 131 }
128 132
129 133 protected JwtTokenAuthenticationProcessingFilter buildJwtTokenAuthenticationProcessingFilter() throws Exception {
130   - List<String> pathsToSkip = new ArrayList(Arrays.asList(NON_TOKEN_BASED_AUTH_ENTRY_POINTS));
  134 + List<String> pathsToSkip = new ArrayList<>(Arrays.asList(NON_TOKEN_BASED_AUTH_ENTRY_POINTS));
131 135 pathsToSkip.addAll(Arrays.asList(WS_TOKEN_BASED_AUTH_ENTRY_POINT, TOKEN_REFRESH_ENTRY_POINT, FORM_BASED_LOGIN_ENTRY_POINT,
132 136 PUBLIC_LOGIN_ENTRY_POINT, DEVICE_API_ENTRY_POINT, WEBJARS_ENTRY_POINT));
133 137 SkipPathRequestMatcher matcher = new SkipPathRequestMatcher(pathsToSkip, TOKEN_BASED_AUTH_ENTRY_POINT);
... ... @@ -213,7 +217,9 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt
213 217 .addFilterAfter(rateLimitProcessingFilter, UsernamePasswordAuthenticationFilter.class);
214 218 if (oauth2Configuration != null) {
215 219 http.oauth2Login()
216   - .authorizationEndpoint().authorizationRequestResolver(oAuth2AuthorizationRequestResolver)
  220 + .authorizationEndpoint()
  221 + .authorizationRequestRepository(httpCookieOAuth2AuthorizationRequestRepository)
  222 + .authorizationRequestResolver(oAuth2AuthorizationRequestResolver)
217 223 .and()
218 224 .loginPage("/oauth2Login")
219 225 .loginProcessingUrl(oauth2Configuration.getLoginProcessingUrl())
... ...
... ... @@ -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();
... ...
... ... @@ -27,7 +27,22 @@ import org.springframework.beans.factory.annotation.Value;
27 27 import org.springframework.security.core.Authentication;
28 28 import org.springframework.security.core.context.SecurityContextHolder;
29 29 import org.springframework.web.bind.annotation.ExceptionHandler;
30   -import org.thingsboard.server.common.data.*;
  30 +import org.thingsboard.server.common.data.Customer;
  31 +import org.thingsboard.server.common.data.Dashboard;
  32 +import org.thingsboard.server.common.data.DashboardInfo;
  33 +import org.thingsboard.server.common.data.DataConstants;
  34 +import org.thingsboard.server.common.data.Device;
  35 +import org.thingsboard.server.common.data.DeviceInfo;
  36 +import org.thingsboard.server.common.data.DeviceProfile;
  37 +import org.thingsboard.server.common.data.EntityType;
  38 +import org.thingsboard.server.common.data.EntityView;
  39 +import org.thingsboard.server.common.data.EntityViewInfo;
  40 +import org.thingsboard.server.common.data.HasName;
  41 +import org.thingsboard.server.common.data.HasTenantId;
  42 +import org.thingsboard.server.common.data.Tenant;
  43 +import org.thingsboard.server.common.data.TenantInfo;
  44 +import org.thingsboard.server.common.data.TenantProfile;
  45 +import org.thingsboard.server.common.data.User;
31 46 import org.thingsboard.server.common.data.alarm.Alarm;
32 47 import org.thingsboard.server.common.data.alarm.AlarmInfo;
33 48 import org.thingsboard.server.common.data.asset.Asset;
... ... @@ -84,6 +99,7 @@ import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService;
84 99 import org.thingsboard.server.dao.oauth2.OAuth2Service;
85 100 import org.thingsboard.server.dao.relation.RelationService;
86 101 import org.thingsboard.server.dao.rule.RuleChainService;
  102 +import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
87 103 import org.thingsboard.server.dao.tenant.TenantProfileService;
88 104 import org.thingsboard.server.dao.tenant.TenantService;
89 105 import org.thingsboard.server.dao.user.UserService;
... ... @@ -94,8 +110,8 @@ import org.thingsboard.server.queue.discovery.PartitionService;
94 110 import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
95 111 import org.thingsboard.server.queue.util.TbCoreComponent;
96 112 import org.thingsboard.server.service.component.ComponentDiscoveryService;
  113 +import org.thingsboard.server.service.lwm2m.LwM2MModelsRepository;
97 114 import org.thingsboard.server.service.profile.TbDeviceProfileCache;
98   -import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
99 115 import org.thingsboard.server.service.queue.TbClusterService;
100 116 import org.thingsboard.server.service.security.model.SecurityUser;
101 117 import org.thingsboard.server.service.security.permission.AccessControlService;
... ... @@ -123,6 +139,9 @@ public abstract class BaseController {
123 139 public static final String INCORRECT_TENANT_ID = "Incorrect tenantId ";
124 140 public static final String YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION = "You don't have permission to perform this operation!";
125 141
  142 + protected static final String DEFAULT_DASHBOARD = "defaultDashboardId";
  143 + protected static final String HOME_DASHBOARD = "homeDashboardId";
  144 +
126 145 private static final ObjectMapper json = new ObjectMapper();
127 146
128 147 @Autowired
... ... @@ -215,6 +234,9 @@ public abstract class BaseController {
215 234 @Autowired
216 235 protected TbDeviceProfileCache deviceProfileCache;
217 236
  237 + @Autowired
  238 + protected LwM2MModelsRepository lwM2MModelsRepository;
  239 +
218 240 @Value("${server.log_controller_error_stack_trace}")
219 241 @Getter
220 242 private boolean logControllerErrorStackTrace;
... ... @@ -645,6 +667,7 @@ public abstract class BaseController {
645 667 return ruleNode;
646 668 }
647 669
  670 + @SuppressWarnings("unchecked")
648 671 protected <I extends EntityId> I emptyId(EntityType entityType) {
649 672 return (I) EntityIdFactory.getByTypeAndUuid(entityType, ModelConstants.NULL_UUID);
650 673 }
... ... @@ -759,8 +782,9 @@ public abstract class BaseController {
759 782 entityNode = json.createObjectNode();
760 783 if (actionType == ActionType.ATTRIBUTES_UPDATED) {
761 784 String scope = extractParameter(String.class, 0, additionalInfo);
  785 + @SuppressWarnings("unchecked")
762 786 List<AttributeKvEntry> attributes = extractParameter(List.class, 1, additionalInfo);
763   - metaData.putValue("scope", scope);
  787 + metaData.putValue(DataConstants.SCOPE, scope);
764 788 if (attributes != null) {
765 789 for (AttributeKvEntry attr : attributes) {
766 790 addKvEntry(entityNode, attr);
... ... @@ -768,16 +792,19 @@ public abstract class BaseController {
768 792 }
769 793 } else if (actionType == ActionType.ATTRIBUTES_DELETED) {
770 794 String scope = extractParameter(String.class, 0, additionalInfo);
  795 + @SuppressWarnings("unchecked")
771 796 List<String> keys = extractParameter(List.class, 1, additionalInfo);
772   - metaData.putValue("scope", scope);
  797 + metaData.putValue(DataConstants.SCOPE, scope);
773 798 ArrayNode attrsArrayNode = entityNode.putArray("attributes");
774 799 if (keys != null) {
775 800 keys.forEach(attrsArrayNode::add);
776 801 }
777 802 } else if (actionType == ActionType.TIMESERIES_UPDATED) {
  803 + @SuppressWarnings("unchecked")
778 804 List<TsKvEntry> timeseries = extractParameter(List.class, 0, additionalInfo);
779 805 addTimeseries(entityNode, timeseries);
780 806 } else if (actionType == ActionType.TIMESERIES_DELETED) {
  807 + @SuppressWarnings("unchecked")
781 808 List<String> keys = extractParameter(List.class, 0, additionalInfo);
782 809 if (keys != null) {
783 810 ArrayNode timeseriesArrayNode = entityNode.putArray("timeseries");
... ... @@ -853,4 +880,14 @@ public abstract class BaseController {
853 880 }
854 881 }
855 882 }
  883 +
  884 + protected void processDashboardIdFromAdditionalInfo(ObjectNode additionalInfo, String requiredFields) throws ThingsboardException {
  885 + String dashboardId = additionalInfo.has(requiredFields) ? additionalInfo.get(requiredFields).asText() : null;
  886 + if(dashboardId != null && !dashboardId.equals("null")) {
  887 + if(dashboardService.findDashboardById(getTenantId(), new DashboardId(UUID.fromString(dashboardId))) == null) {
  888 + additionalInfo.remove(requiredFields);
  889 + }
  890 + }
  891 + }
  892 +
856 893 }
... ...
... ... @@ -55,7 +55,11 @@ public class CustomerController extends BaseController {
55 55 checkParameter(CUSTOMER_ID, strCustomerId);
56 56 try {
57 57 CustomerId customerId = new CustomerId(toUUID(strCustomerId));
58   - return checkCustomerId(customerId, Operation.READ);
  58 + Customer customer = checkCustomerId(customerId, Operation.READ);
  59 + if(!customer.getAdditionalInfo().isNull()) {
  60 + processDashboardIdFromAdditionalInfo((ObjectNode) customer.getAdditionalInfo(), HOME_DASHBOARD);
  61 + }
  62 + return customer;
59 63 } catch (Exception e) {
60 64 throw handleException(e);
61 65 }
... ...
... ... @@ -15,6 +15,8 @@
15 15 */
16 16 package org.thingsboard.server.controller;
17 17
  18 +import com.fasterxml.jackson.databind.JsonNode;
  19 +import com.fasterxml.jackson.databind.node.ObjectNode;
18 20 import org.springframework.beans.factory.annotation.Value;
19 21 import org.springframework.http.HttpStatus;
20 22 import org.springframework.security.access.prepost.PreAuthorize;
... ... @@ -30,7 +32,11 @@ import org.thingsboard.server.common.data.Customer;
30 32 import org.thingsboard.server.common.data.Dashboard;
31 33 import org.thingsboard.server.common.data.DashboardInfo;
32 34 import org.thingsboard.server.common.data.EntityType;
  35 +import org.thingsboard.server.common.data.HomeDashboard;
  36 +import org.thingsboard.server.common.data.HomeDashboardInfo;
33 37 import org.thingsboard.server.common.data.ShortCustomerInfo;
  38 +import org.thingsboard.server.common.data.Tenant;
  39 +import org.thingsboard.server.common.data.User;
34 40 import org.thingsboard.server.common.data.audit.ActionType;
35 41 import org.thingsboard.server.common.data.exception.ThingsboardException;
36 42 import org.thingsboard.server.common.data.id.CustomerId;
... ... @@ -38,8 +44,9 @@ import org.thingsboard.server.common.data.id.DashboardId;
38 44 import org.thingsboard.server.common.data.id.TenantId;
39 45 import org.thingsboard.server.common.data.page.PageData;
40 46 import org.thingsboard.server.common.data.page.PageLink;
41   -import org.thingsboard.server.common.data.page.TimePageLink;
  47 +import org.thingsboard.common.util.JacksonUtil;
42 48 import org.thingsboard.server.queue.util.TbCoreComponent;
  49 +import org.thingsboard.server.service.security.model.SecurityUser;
43 50 import org.thingsboard.server.service.security.permission.Operation;
44 51 import org.thingsboard.server.service.security.permission.Resource;
45 52
... ... @@ -53,6 +60,9 @@ public class DashboardController extends BaseController {
53 60
54 61 public static final String DASHBOARD_ID = "dashboardId";
55 62
  63 + private static final String HOME_DASHBOARD_ID = "homeDashboardId";
  64 + private static final String HOME_DASHBOARD_HIDE_TOOLBAR = "homeDashboardHideToolbar";
  65 +
56 66 @Value("${dashboard.max_datapoints_limit}")
57 67 private long maxDatapointsLimit;
58 68
... ... @@ -472,4 +482,100 @@ public class DashboardController extends BaseController {
472 482 throw handleException(e);
473 483 }
474 484 }
  485 +
  486 + @PreAuthorize("isAuthenticated()")
  487 + @RequestMapping(value = "/dashboard/home", method = RequestMethod.GET)
  488 + @ResponseBody
  489 + public HomeDashboard getHomeDashboard() throws ThingsboardException {
  490 + try {
  491 + SecurityUser securityUser = getCurrentUser();
  492 + if (securityUser.isSystemAdmin()) {
  493 + return null;
  494 + }
  495 + User user = userService.findUserById(securityUser.getTenantId(), securityUser.getId());
  496 + JsonNode additionalInfo = user.getAdditionalInfo();
  497 + HomeDashboard homeDashboard;
  498 + homeDashboard = extractHomeDashboardFromAdditionalInfo(additionalInfo);
  499 + if (homeDashboard == null) {
  500 + if (securityUser.isCustomerUser()) {
  501 + Customer customer = customerService.findCustomerById(securityUser.getTenantId(), securityUser.getCustomerId());
  502 + additionalInfo = customer.getAdditionalInfo();
  503 + homeDashboard = extractHomeDashboardFromAdditionalInfo(additionalInfo);
  504 + }
  505 + if (homeDashboard == null) {
  506 + Tenant tenant = tenantService.findTenantById(securityUser.getTenantId());
  507 + additionalInfo = tenant.getAdditionalInfo();
  508 + homeDashboard = extractHomeDashboardFromAdditionalInfo(additionalInfo);
  509 + }
  510 + }
  511 + return homeDashboard;
  512 + } catch (Exception e) {
  513 + throw handleException(e);
  514 + }
  515 + }
  516 +
  517 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  518 + @RequestMapping(value = "/tenant/dashboard/home/info", method = RequestMethod.GET)
  519 + @ResponseBody
  520 + public HomeDashboardInfo getTenantHomeDashboardInfo() throws ThingsboardException {
  521 + try {
  522 + Tenant tenant = tenantService.findTenantById(getTenantId());
  523 + JsonNode additionalInfo = tenant.getAdditionalInfo();
  524 + DashboardId dashboardId = null;
  525 + boolean hideDashboardToolbar = true;
  526 + if (additionalInfo != null && additionalInfo.has(HOME_DASHBOARD_ID) && !additionalInfo.get(HOME_DASHBOARD_ID).isNull()) {
  527 + String strDashboardId = additionalInfo.get(HOME_DASHBOARD_ID).asText();
  528 + dashboardId = new DashboardId(toUUID(strDashboardId));
  529 + if (additionalInfo.has(HOME_DASHBOARD_HIDE_TOOLBAR)) {
  530 + hideDashboardToolbar = additionalInfo.get(HOME_DASHBOARD_HIDE_TOOLBAR).asBoolean();
  531 + }
  532 + }
  533 + return new HomeDashboardInfo(dashboardId, hideDashboardToolbar);
  534 + } catch (Exception e) {
  535 + throw handleException(e);
  536 + }
  537 + }
  538 +
  539 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  540 + @RequestMapping(value = "/tenant/dashboard/home/info", method = RequestMethod.POST)
  541 + @ResponseStatus(value = HttpStatus.OK)
  542 + public void setTenantHomeDashboardInfo(@RequestBody HomeDashboardInfo homeDashboardInfo) throws ThingsboardException {
  543 + try {
  544 + if (homeDashboardInfo.getDashboardId() != null) {
  545 + checkDashboardId(homeDashboardInfo.getDashboardId(), Operation.READ);
  546 + }
  547 + Tenant tenant = tenantService.findTenantById(getTenantId());
  548 + JsonNode additionalInfo = tenant.getAdditionalInfo();
  549 + if (additionalInfo == null || !(additionalInfo instanceof ObjectNode)) {
  550 + additionalInfo = JacksonUtil.OBJECT_MAPPER.createObjectNode();
  551 + }
  552 + if (homeDashboardInfo.getDashboardId() != null) {
  553 + ((ObjectNode) additionalInfo).put(HOME_DASHBOARD_ID, homeDashboardInfo.getDashboardId().getId().toString());
  554 + ((ObjectNode) additionalInfo).put(HOME_DASHBOARD_HIDE_TOOLBAR, homeDashboardInfo.isHideDashboardToolbar());
  555 + } else {
  556 + ((ObjectNode) additionalInfo).remove(HOME_DASHBOARD_ID);
  557 + ((ObjectNode) additionalInfo).remove(HOME_DASHBOARD_HIDE_TOOLBAR);
  558 + }
  559 + tenant.setAdditionalInfo(additionalInfo);
  560 + tenantService.saveTenant(tenant);
  561 + } catch (Exception e) {
  562 + throw handleException(e);
  563 + }
  564 + }
  565 +
  566 + private HomeDashboard extractHomeDashboardFromAdditionalInfo(JsonNode additionalInfo) {
  567 + try {
  568 + if (additionalInfo != null && additionalInfo.has(HOME_DASHBOARD_ID) && !additionalInfo.get(HOME_DASHBOARD_ID).isNull()) {
  569 + String strDashboardId = additionalInfo.get(HOME_DASHBOARD_ID).asText();
  570 + DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
  571 + Dashboard dashboard = checkDashboardId(dashboardId, Operation.READ);
  572 + boolean hideDashboardToolbar = true;
  573 + if (additionalInfo.has(HOME_DASHBOARD_HIDE_TOOLBAR)) {
  574 + hideDashboardToolbar = additionalInfo.get(HOME_DASHBOARD_HIDE_TOOLBAR).asBoolean();
  575 + }
  576 + return new HomeDashboard(dashboard, hideDashboardToolbar);
  577 + }
  578 + } catch (Exception e) {}
  579 + return null;
  580 + }
475 581 }
... ...
... ... @@ -278,9 +278,7 @@ public class DeviceController extends BaseController {
278 278 try {
279 279 Device device = checkDeviceId(deviceCredentials.getDeviceId(), Operation.WRITE_CREDENTIALS);
280 280 DeviceCredentials result = checkNotNull(deviceCredentialsService.updateDeviceCredentials(getCurrentUser().getTenantId(), deviceCredentials));
281   -
282   - tbClusterService.pushMsgToCore(new DeviceCredentialsUpdateNotificationMsg(getCurrentUser().getTenantId(), deviceCredentials.getDeviceId()), null);
283   -
  281 + tbClusterService.pushMsgToCore(new DeviceCredentialsUpdateNotificationMsg(getCurrentUser().getTenantId(), deviceCredentials.getDeviceId(), result), null);
284 282 logEntityAction(device.getId(), device,
285 283 device.getCustomerId(),
286 284 ActionType.CREDENTIALS_UPDATED, null, deviceCredentials);
... ...
  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.controller;
  17 +
  18 +import com.fasterxml.jackson.databind.ObjectMapper;
  19 +import lombok.extern.slf4j.Slf4j;
  20 +import org.springframework.security.access.prepost.PreAuthorize;
  21 +import org.springframework.web.bind.annotation.PathVariable;
  22 +import org.springframework.web.bind.annotation.RequestBody;
  23 +import org.springframework.web.bind.annotation.RequestMapping;
  24 +import org.springframework.web.bind.annotation.RequestMethod;
  25 +import org.springframework.web.bind.annotation.RequestParam;
  26 +import org.springframework.web.bind.annotation.ResponseBody;
  27 +import org.springframework.web.bind.annotation.RestController;
  28 +import org.thingsboard.rule.engine.api.msg.DeviceNameOrTypeUpdateMsg;
  29 +import org.thingsboard.server.common.data.Device;
  30 +import org.thingsboard.server.common.data.EntityType;
  31 +import org.thingsboard.server.common.data.audit.ActionType;
  32 +import org.thingsboard.server.common.data.exception.ThingsboardException;
  33 +import org.thingsboard.server.common.data.lwm2m.LwM2mObject;
  34 +import org.thingsboard.server.common.data.lwm2m.ServerSecurityConfig;
  35 +import org.thingsboard.server.common.data.page.PageData;
  36 +import org.thingsboard.server.common.data.page.PageLink;
  37 +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
  38 +import org.thingsboard.server.common.data.security.DeviceCredentials;
  39 +import org.thingsboard.server.queue.util.TbCoreComponent;
  40 +import org.thingsboard.server.service.security.permission.Resource;
  41 +
  42 +import java.util.List;
  43 +import java.util.Map;
  44 +
  45 +@Slf4j
  46 +@RestController
  47 +@TbCoreComponent
  48 +@RequestMapping("/api")
  49 +public class DeviceLwm2mController extends BaseController {
  50 +
  51 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  52 + @RequestMapping(value = "/lwm2m/deviceProfile", params = {"sortOrder", "sortProperty"}, method = RequestMethod.GET)
  53 + @ResponseBody
  54 + public List<LwM2mObject> getLwm2mListObjects(@RequestParam String sortOrder,
  55 + @RequestParam String sortProperty,
  56 + @RequestParam(required = false) int[] objectIds,
  57 + @RequestParam(required = false) String searchText)
  58 + throws ThingsboardException {
  59 + try {
  60 + return lwM2MModelsRepository.getLwm2mObjects(objectIds, searchText, sortProperty, sortOrder);
  61 + } catch (Exception e) {
  62 + throw handleException(e);
  63 + }
  64 + }
  65 +
  66 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  67 + @RequestMapping(value = "/lwm2m/deviceProfile/objects", params = {"pageSize", "page"}, method = RequestMethod.GET)
  68 + @ResponseBody
  69 + public PageData<LwM2mObject> getLwm2mListObjects(@RequestParam int pageSize,
  70 + @RequestParam int page,
  71 + @RequestParam(required = false) String searchText,
  72 + @RequestParam(required = false) String sortProperty,
  73 + @RequestParam(required = false) String sortOrder) throws ThingsboardException {
  74 + try {
  75 + PageLink pageLink = createPageLink(pageSize, page, searchText, sortProperty, sortOrder);
  76 + return checkNotNull(lwM2MModelsRepository.findDeviceLwm2mObjects(getTenantId(), pageLink));
  77 + } catch (Exception e) {
  78 + throw handleException(e);
  79 + }
  80 + }
  81 +
  82 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  83 + @RequestMapping(value = "/lwm2m/deviceProfile/bootstrap/{securityMode}/{bootstrapServerIs}", method = RequestMethod.GET)
  84 + @ResponseBody
  85 + public ServerSecurityConfig getLwm2mBootstrapSecurityInfo(@PathVariable("securityMode") String securityMode,
  86 + @PathVariable("bootstrapServerIs") boolean bootstrapServerIs) throws ThingsboardException {
  87 + try {
  88 + return lwM2MModelsRepository.getBootstrapSecurityInfo(securityMode, bootstrapServerIs);
  89 + } catch (Exception e) {
  90 + throw handleException(e);
  91 + }
  92 + }
  93 +
  94 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  95 + @RequestMapping(value = "/lwm2m/device-credentials", method = RequestMethod.POST)
  96 + @ResponseBody
  97 + public Device saveDeviceWithCredentials(@RequestBody (required=false) Map<Class<?>, Object> deviceWithDeviceCredentials) throws ThingsboardException {
  98 + ObjectMapper mapper = new ObjectMapper();
  99 + Device device = checkNotNull(mapper.convertValue(deviceWithDeviceCredentials.get(Device.class), Device.class));
  100 + DeviceCredentials credentials = checkNotNull(mapper.convertValue( deviceWithDeviceCredentials.get(DeviceCredentials.class), DeviceCredentials.class));
  101 + try {
  102 + device.setTenantId(getCurrentUser().getTenantId());
  103 + checkEntity(device.getId(), device, Resource.DEVICE);
  104 + Device savedDevice = deviceService.saveDeviceWithCredentials(device, credentials);
  105 + checkNotNull(savedDevice);
  106 +
  107 + tbClusterService.onDeviceChange(savedDevice, null);
  108 + tbClusterService.pushMsgToCore(new DeviceNameOrTypeUpdateMsg(savedDevice.getTenantId(),
  109 + savedDevice.getId(), savedDevice.getName(), savedDevice.getType()), null);
  110 + tbClusterService.onEntityStateChange(savedDevice.getTenantId(), savedDevice.getId(),
  111 + device.getId() == null ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED);
  112 +
  113 + logEntityAction(savedDevice.getId(), savedDevice,
  114 + savedDevice.getCustomerId(),
  115 + device.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);
  116 +
  117 + if (device.getId() == null) {
  118 + deviceStateService.onDeviceAdded(savedDevice);
  119 + } else {
  120 + deviceStateService.onDeviceUpdated(savedDevice);
  121 + }
  122 + return savedDevice;
  123 + } catch (Exception e) {
  124 + logEntityAction(emptyId(EntityType.DEVICE), device,
  125 + null, device.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e);
  126 + throw handleException(e);
  127 + }
  128 + }
  129 +}
... ...
... ... @@ -63,7 +63,7 @@ import java.util.List;
63 63 import java.util.concurrent.ExecutionException;
64 64 import java.util.stream.Collectors;
65 65
66   -import static org.apache.commons.lang.StringUtils.isBlank;
  66 +import static org.apache.commons.lang3.StringUtils.isBlank;
67 67 import static org.thingsboard.server.controller.CustomerController.CUSTOMER_ID;
68 68
69 69 /**
... ...
  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.controller;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.springframework.security.access.prepost.PreAuthorize;
  20 +import org.springframework.web.bind.annotation.PathVariable;
  21 +import org.springframework.web.bind.annotation.RequestMapping;
  22 +import org.springframework.web.bind.annotation.RequestMethod;
  23 +import org.springframework.web.bind.annotation.RequestParam;
  24 +import org.springframework.web.bind.annotation.ResponseBody;
  25 +import org.springframework.web.bind.annotation.RestController;
  26 +import org.thingsboard.server.common.data.exception.ThingsboardException;
  27 +import org.thingsboard.server.common.data.id.TenantId;
  28 +import org.thingsboard.server.common.data.page.PageData;
  29 +import org.thingsboard.server.common.data.page.PageLink;
  30 +import org.thingsboard.server.common.data.transport.resource.Resource;
  31 +import org.thingsboard.server.common.data.transport.resource.ResourceType;
  32 +import org.thingsboard.server.dao.resource.ResourceService;
  33 +import org.thingsboard.server.queue.util.TbCoreComponent;
  34 +
  35 +@Slf4j
  36 +@RestController
  37 +@TbCoreComponent
  38 +@RequestMapping("/api")
  39 +public class ResourceController extends BaseController {
  40 +
  41 + private final ResourceService resourceService;
  42 +
  43 + public ResourceController(ResourceService resourceService) {
  44 + this.resourceService = resourceService;
  45 + }
  46 +
  47 + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
  48 + @RequestMapping(value = "/resource", method = RequestMethod.POST)
  49 + @ResponseBody
  50 + public Resource saveResource(Resource resource) throws ThingsboardException {
  51 + try {
  52 + resource.setTenantId(getTenantId());
  53 + Resource savedResource = checkNotNull(resourceService.saveResource(resource));
  54 + tbClusterService.onResourceChange(savedResource, null);
  55 + return savedResource;
  56 + } catch (Exception e) {
  57 + throw handleException(e);
  58 + }
  59 + }
  60 +
  61 + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
  62 + @RequestMapping(value = "/resource", method = RequestMethod.GET)
  63 + @ResponseBody
  64 + public PageData<Resource> getResources(@RequestParam(required = false) boolean system,
  65 + @RequestParam int pageSize,
  66 + @RequestParam int page,
  67 + @RequestParam(required = false) String sortProperty,
  68 + @RequestParam(required = false) String sortOrder) throws ThingsboardException {
  69 + try {
  70 + PageLink pageLink = createPageLink(pageSize, page, null, sortProperty, sortOrder);
  71 + return checkNotNull(resourceService.findResourcesByTenantId(system ? TenantId.SYS_TENANT_ID : getTenantId(), pageLink));
  72 + } catch (Exception e) {
  73 + throw handleException(e);
  74 + }
  75 + }
  76 +
  77 + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
  78 + @RequestMapping(value = "/resource/{resourceType}/{resourceId}", method = RequestMethod.DELETE)
  79 + @ResponseBody
  80 + public void deleteResource(@PathVariable("resourceType") ResourceType resourceType,
  81 + @PathVariable("resourceId") String resourceId) throws ThingsboardException {
  82 + try {
  83 + Resource resource = checkNotNull(resourceService.getResource(getTenantId(), resourceType, resourceId));
  84 + resourceService.deleteResource(getTenantId(), resourceType, resourceId);
  85 + tbClusterService.onResourceDeleted(resource, null);
  86 + } catch (Exception e) {
  87 + throw handleException(e);
  88 + }
  89 + }
  90 +}
... ...
... ... @@ -15,6 +15,7 @@
15 15 */
16 16 package org.thingsboard.server.controller;
17 17
  18 +import com.fasterxml.jackson.databind.node.ObjectNode;
18 19 import lombok.extern.slf4j.Slf4j;
19 20 import org.springframework.beans.factory.annotation.Autowired;
20 21 import org.springframework.http.HttpStatus;
... ... @@ -59,7 +60,11 @@ public class TenantController extends BaseController {
59 60 checkParameter("tenantId", strTenantId);
60 61 try {
61 62 TenantId tenantId = new TenantId(toUUID(strTenantId));
62   - return checkTenantId(tenantId, Operation.READ);
  63 + Tenant tenant = checkTenantId(tenantId, Operation.READ);
  64 + if(!tenant.getAdditionalInfo().isNull()) {
  65 + processDashboardIdFromAdditionalInfo((ObjectNode) tenant.getAdditionalInfo(), HOME_DASHBOARD);
  66 + }
  67 + return tenant;
63 68 } catch (Exception e) {
64 69 throw handleException(e);
65 70 }
... ...
... ... @@ -53,7 +53,6 @@ import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
53 53 import org.thingsboard.server.service.security.permission.Operation;
54 54 import org.thingsboard.server.service.security.permission.Resource;
55 55 import org.thingsboard.server.service.security.system.SystemSecurityService;
56   -import org.thingsboard.server.utils.MiscUtils;
57 56
58 57 import javax.servlet.http.HttpServletRequest;
59 58
... ... @@ -90,12 +89,29 @@ public class UserController extends BaseController {
90 89 checkParameter(USER_ID, strUserId);
91 90 try {
92 91 UserId userId = new UserId(toUUID(strUserId));
93   - return checkUserId(userId, Operation.READ);
  92 + User user = checkUserId(userId, Operation.READ);
  93 + if(!user.getAdditionalInfo().isNull()) {
  94 + processDashboardIdFromAdditionalInfo((ObjectNode) user.getAdditionalInfo(), DEFAULT_DASHBOARD);
  95 + processDashboardIdFromAdditionalInfo((ObjectNode) user.getAdditionalInfo(), HOME_DASHBOARD);
  96 + }
  97 + UserCredentials userCredentials = userService.findUserCredentialsByUserId(user.getTenantId(), user.getId());
  98 + if(userCredentials.isEnabled()) {
  99 + addUserCredentialsEnabled((ObjectNode) user.getAdditionalInfo());
  100 + }
  101 + return user;
94 102 } catch (Exception e) {
95 103 throw handleException(e);
96 104 }
97 105 }
98 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 +
99 115 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
100 116 @RequestMapping(value = "/user/tokenAccessEnabled", method = RequestMethod.GET)
101 117 @ResponseBody
... ... @@ -189,13 +205,13 @@ public class UserController extends BaseController {
189 205 user.getId(), user);
190 206
191 207 UserCredentials userCredentials = userService.findUserCredentialsByUserId(getCurrentUser().getTenantId(), user.getId());
192   - if (!userCredentials.isEnabled()) {
  208 + if (!userCredentials.isEnabled() && userCredentials.getActivateToken() != null) {
193 209 String baseUrl = systemSecurityService.getBaseUrl(getTenantId(), getCurrentUser().getCustomerId(), request);
194 210 String activateUrl = String.format(ACTIVATE_URL_PATTERN, baseUrl,
195 211 userCredentials.getActivateToken());
196 212 mailService.sendActivationEmail(activateUrl, email);
197 213 } else {
198   - throw new ThingsboardException("User is already active!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
  214 + throw new ThingsboardException("User is already activated!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
199 215 }
200 216 } catch (Exception e) {
201 217 throw handleException(e);
... ... @@ -214,13 +230,13 @@ public class UserController extends BaseController {
214 230 User user = checkUserId(userId, Operation.READ);
215 231 SecurityUser authUser = getCurrentUser();
216 232 UserCredentials userCredentials = userService.findUserCredentialsByUserId(authUser.getTenantId(), user.getId());
217   - if (!userCredentials.isEnabled()) {
  233 + if (!userCredentials.isEnabled() && userCredentials.getActivateToken() != null) {
218 234 String baseUrl = systemSecurityService.getBaseUrl(getTenantId(), getCurrentUser().getCustomerId(), request);
219 235 String activateUrl = String.format(ACTIVATE_URL_PATTERN, baseUrl,
220 236 userCredentials.getActivateToken());
221 237 return activateUrl;
222 238 } else {
223   - throw new ThingsboardException("User is already active!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
  239 + throw new ThingsboardException("User is already activated!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
224 240 }
225 241 } catch (Exception e) {
226 242 throw handleException(e);
... ... @@ -329,4 +345,5 @@ public class UserController extends BaseController {
329 345 throw handleException(e);
330 346 }
331 347 }
  348 +
332 349 }
... ...
... ... @@ -41,9 +41,13 @@ import org.thingsboard.server.service.telemetry.TelemetryWebSocketMsgEndpoint;
41 41 import org.thingsboard.server.service.telemetry.TelemetryWebSocketService;
42 42 import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef;
43 43
44   -import javax.websocket.*;
  44 +import javax.websocket.RemoteEndpoint;
  45 +import javax.websocket.SendHandler;
  46 +import javax.websocket.SendResult;
  47 +import javax.websocket.Session;
45 48 import java.io.IOException;
46 49 import java.net.URI;
  50 +import java.nio.ByteBuffer;
47 51 import java.security.InvalidParameterException;
48 52 import java.util.Queue;
49 53 import java.util.Set;
... ... @@ -79,6 +83,9 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
79 83 @Value("${server.ws.limits.max_updates_per_session:}")
80 84 private String perSessionUpdatesConfiguration;
81 85
  86 + @Value("${server.ws.ping_timeout:30000}")
  87 + private long pingTimeout;
  88 +
82 89 private ConcurrentMap<String, TelemetryWebSocketSessionRef> blacklistedSessions = new ConcurrentHashMap<>();
83 90 private ConcurrentMap<String, TbRateLimits> perSessionUpdateLimits = new ConcurrentHashMap<>();
84 91
... ... @@ -120,6 +127,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
120 127 return;
121 128 }
122 129 internalSessionMap.put(internalSessionId, new SessionMetaData(session, sessionRef, maxMsgQueuePerSession));
  130 +
123 131 externalSessionMap.put(externalSessionId, internalSessionId);
124 132 processInWebSocketService(sessionRef, SessionEvent.onEstablished());
125 133 log.info("[{}][{}][{}] Session is opened", sessionRef.getSecurityCtx().getTenantId(), externalSessionId, session.getId());
... ... @@ -189,6 +197,8 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
189 197 private volatile boolean isSending = false;
190 198 private final Queue<String> msgQueue;
191 199
  200 + private volatile long lastActivityTime;
  201 +
192 202 SessionMetaData(WebSocketSession session, TelemetryWebSocketSessionRef sessionRef, int maxMsgQueuePerSession) {
193 203 super();
194 204 this.session = session;
... ... @@ -196,6 +206,23 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
196 206 this.asyncRemote = nativeSession.getAsyncRemote();
197 207 this.sessionRef = sessionRef;
198 208 this.msgQueue = new LinkedBlockingQueue<>(maxMsgQueuePerSession);
  209 + this.lastActivityTime = System.currentTimeMillis();
  210 + }
  211 +
  212 + synchronized void sendPing(long currentTime) {
  213 + try {
  214 + if (currentTime - lastActivityTime >= pingTimeout) {
  215 + this.asyncRemote.sendPing(ByteBuffer.wrap(new byte[]{}));
  216 + lastActivityTime = currentTime;
  217 + }
  218 + } catch (Exception e) {
  219 + log.trace("[{}] Failed to send ping msg", session.getId(), e);
  220 + try {
  221 + close(this.sessionRef, CloseStatus.SESSION_NOT_RELIABLE);
  222 + } catch (IOException ioe) {
  223 + log.trace("[{}] Session transport error", session.getId(), ioe);
  224 + }
  225 + }
199 226 }
200 227
201 228 synchronized void sendMsg(String msg) {
... ... @@ -243,6 +270,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
243 270 log.trace("[{}] Session transport error", session.getId(), ioe);
244 271 }
245 272 } else {
  273 + lastActivityTime = System.currentTimeMillis();
246 274 String msg = msgQueue.poll();
247 275 if (msg != null) {
248 276 sendMsgInternal(msg);
... ... @@ -285,6 +313,22 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
285 313 }
286 314
287 315 @Override
  316 + public void sendPing(TelemetryWebSocketSessionRef sessionRef, long currentTime) throws IOException {
  317 + String externalId = sessionRef.getSessionId();
  318 + String internalId = externalSessionMap.get(externalId);
  319 + if (internalId != null) {
  320 + SessionMetaData sessionMd = internalSessionMap.get(internalId);
  321 + if (sessionMd != null) {
  322 + sessionMd.sendPing(currentTime);
  323 + } else {
  324 + log.warn("[{}][{}] Failed to find session by internal id", externalId, internalId);
  325 + }
  326 + } else {
  327 + log.warn("[{}] Failed to find session by external id", externalId);
  328 + }
  329 + }
  330 +
  331 + @Override
288 332 public void close(TelemetryWebSocketSessionRef sessionRef, CloseStatus reason) throws IOException {
289 333 String externalId = sessionRef.getSessionId();
290 334 log.debug("[{}] Processing close request", externalId);
... ...
... ... @@ -185,9 +185,21 @@ public class ThingsboardInstallService {
185 185 case "3.2.0":
186 186 log.info("Upgrading ThingsBoard from version 3.2.0 to 3.2.1 ...");
187 187 databaseEntitiesUpgradeService.upgradeDatabase("3.2.0");
  188 + case "3.2.1":
  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 +
188 194 log.info("Updating system data...");
189 195 systemDataLoaderService.updateSystemWidgets();
190 196 break;
  197 + case "3.2.2":
  198 + log.info("Upgrading ThingsBoard from version 3.2.2 to 3.3.0 ...");
  199 + databaseEntitiesUpgradeService.upgradeDatabase("3.2.2");
  200 +
  201 + log.info("Updating system data...");
  202 + break;
191 203 default:
192 204 throw new RuntimeException("Unable to upgrade ThingsBoard, unsupported fromVersion: " + upgradeFromVersion);
193 205
... ... @@ -220,6 +232,7 @@ public class ThingsboardInstallService {
220 232 systemDataLoaderService.createAdminSettings();
221 233 systemDataLoaderService.loadSystemWidgets();
222 234 systemDataLoaderService.createOAuth2Templates();
  235 + systemDataLoaderService.loadSystemLwm2mResources();
223 236 // systemDataLoaderService.loadSystemPlugins();
224 237 // systemDataLoaderService.loadSystemRules();
225 238
... ...
... ... @@ -54,6 +54,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.UsageStatsKVProto;
54 54 import org.thingsboard.server.queue.common.TbProtoQueueMsg;
55 55 import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
56 56 import org.thingsboard.server.queue.discovery.PartitionService;
  57 +import org.thingsboard.server.queue.discovery.TbApplicationEventListener;
57 58 import org.thingsboard.server.queue.scheduler.SchedulerComponent;
58 59 import org.thingsboard.server.service.queue.TbClusterService;
59 60 import org.thingsboard.server.service.telemetry.InternalTelemetryService;
... ... @@ -78,7 +79,7 @@ import java.util.stream.Collectors;
78 79
79 80 @Slf4j
80 81 @Service
81   -public class DefaultTbApiUsageStateService implements TbApiUsageStateService {
  82 +public class DefaultTbApiUsageStateService extends TbApplicationEventListener<PartitionChangeEvent> implements TbApiUsageStateService {
82 83
83 84 public static final String HOURLY = "Hourly";
84 85 public static final FutureCallback<Integer> VOID_CALLBACK = new FutureCallback<Integer>() {
... ... @@ -188,7 +189,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService {
188 189 }
189 190
190 191 @Override
191   - public void onApplicationEvent(PartitionChangeEvent partitionChangeEvent) {
  192 + protected void onTbApplicationEvent(PartitionChangeEvent partitionChangeEvent) {
192 193 if (partitionChangeEvent.getServiceType().equals(ServiceType.TB_CORE)) {
193 194 myTenantStates.entrySet().removeIf(entry -> !partitionService.resolve(ServiceType.TB_CORE, entry.getKey(), entry.getKey()).isMyPartition());
194 195 otherTenantStates.entrySet().removeIf(entry -> partitionService.resolve(ServiceType.TB_CORE, entry.getKey(), entry.getKey()).isMyPartition());
... ...
... ... @@ -24,6 +24,7 @@ import org.springframework.beans.factory.annotation.Value;
24 24 import org.springframework.beans.factory.config.BeanDefinition;
25 25 import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
26 26 import org.springframework.core.env.Environment;
  27 +import org.springframework.core.env.Profiles;
27 28 import org.springframework.core.type.filter.AnnotationTypeFilter;
28 29 import org.springframework.stereotype.Service;
29 30 import org.thingsboard.rule.engine.api.NodeConfiguration;
... ... @@ -69,7 +70,7 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
69 70 private ObjectMapper mapper = new ObjectMapper();
70 71
71 72 private boolean isInstall() {
72   - return environment.acceptsProfiles("install");
  73 + return environment.acceptsProfiles(Profiles.of("install"));
73 74 }
74 75
75 76 @PostConstruct
... ... @@ -185,7 +186,7 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
185 186 nodeDefinition.setRelationTypes(getRelationTypesWithFailureRelation(nodeAnnotation));
186 187 nodeDefinition.setCustomRelations(nodeAnnotation.customRelations());
187 188 Class<? extends NodeConfiguration> configClazz = nodeAnnotation.configClazz();
188   - NodeConfiguration config = configClazz.newInstance();
  189 + NodeConfiguration config = configClazz.getDeclaredConstructor().newInstance();
189 190 NodeConfiguration defaultConfiguration = config.defaultConfiguration();
190 191 nodeDefinition.setDefaultConfiguration(mapper.valueToTree(defaultConfiguration));
191 192 nodeDefinition.setUiResources(nodeAnnotation.uiResources());
... ...
... ... @@ -20,7 +20,7 @@ import com.fasterxml.jackson.databind.JsonNode;
20 20 import com.fasterxml.jackson.databind.node.ObjectNode;
21 21 import com.google.common.util.concurrent.ListenableFuture;
22 22 import lombok.extern.slf4j.Slf4j;
23   -import org.apache.commons.lang.RandomStringUtils;
  23 +import org.apache.commons.lang3.RandomStringUtils;
24 24 import org.springframework.beans.factory.annotation.Autowired;
25 25 import org.springframework.stereotype.Service;
26 26 import org.springframework.util.StringUtils;
... ... @@ -50,7 +50,7 @@ import org.thingsboard.server.dao.device.provision.ProvisionFailedException;
50 50 import org.thingsboard.server.dao.device.provision.ProvisionRequest;
51 51 import org.thingsboard.server.dao.device.provision.ProvisionResponse;
52 52 import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus;
53   -import org.thingsboard.server.dao.util.mapping.JacksonUtil;
  53 +import org.thingsboard.common.util.JacksonUtil;
54 54 import org.thingsboard.server.gen.transport.TransportProtos;
55 55 import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
56 56 import org.thingsboard.server.queue.TbQueueCallback;
... ...
... ... @@ -50,6 +50,7 @@ public class CassandraTsDatabaseUpgradeService extends AbstractCassandraDatabase
50 50 break;
51 51 case "2.5.0":
52 52 case "3.1.1":
  53 + case "3.2.1":
53 54 break;
54 55 default:
55 56 throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion);
... ...
... ... @@ -36,6 +36,9 @@ import org.thingsboard.server.common.data.TenantProfile;
36 36 import org.thingsboard.server.common.data.User;
37 37 import org.thingsboard.server.common.data.alarm.AlarmSeverity;
38 38 import org.thingsboard.server.common.data.device.profile.AlarmCondition;
  39 +import org.thingsboard.server.common.data.device.profile.AlarmConditionFilter;
  40 +import org.thingsboard.server.common.data.device.profile.AlarmConditionFilterKey;
  41 +import org.thingsboard.server.common.data.device.profile.AlarmConditionKeyType;
39 42 import org.thingsboard.server.common.data.device.profile.AlarmRule;
40 43 import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration;
41 44 import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration;
... ... @@ -197,7 +200,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
197 200 generalSettings.setKey("general");
198 201 ObjectNode node = objectMapper.createObjectNode();
199 202 node.put("baseUrl", "http://localhost:8080");
200   - node.put("prohibitDifferentUrl", true);
  203 + node.put("prohibitDifferentUrl", false);
201 204 generalSettings.setJsonValue(node);
202 205 adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, generalSettings);
203 206
... ... @@ -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);
... ... @@ -438,9 +441,15 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
438 441 this.deleteSystemWidgetBundle("input_widgets");
439 442 this.deleteSystemWidgetBundle("date");
440 443 this.deleteSystemWidgetBundle("entity_admin_widgets");
  444 + this.deleteSystemWidgetBundle("navigation_widgets");
441 445 installScripts.loadSystemWidgets();
442 446 }
443 447
  448 + @Override
  449 + public void loadSystemLwm2mResources() throws Exception {
  450 + installScripts.loadSystemLwm2mResources();
  451 + }
  452 +
444 453 private User createUser(Authority authority,
445 454 TenantId tenantId,
446 455 CustomerId customerId,
... ...
... ... @@ -15,11 +15,20 @@
15 15 */
16 16 package org.thingsboard.server.service.install;
17 17
  18 +import lombok.extern.slf4j.Slf4j;
18 19 import org.springframework.context.annotation.Profile;
19 20 import org.springframework.stereotype.Service;
20 21 import org.thingsboard.server.dao.util.HsqlDao;
21 22
  23 +import java.nio.charset.Charset;
  24 +import java.nio.file.Files;
  25 +import java.nio.file.Path;
  26 +import java.nio.file.Paths;
  27 +import java.sql.Connection;
  28 +import java.sql.DriverManager;
  29 +
22 30 @Service
  31 +@Slf4j
23 32 @HsqlDao
24 33 @Profile("install")
25 34 public class HsqlEntityDatabaseSchemaService extends SqlAbstractDatabaseSchemaService
... ... @@ -27,5 +36,21 @@ public class HsqlEntityDatabaseSchemaService extends SqlAbstractDatabaseSchemaSe
27 36 protected HsqlEntityDatabaseSchemaService() {
28 37 super("schema-entities-hsql.sql", "schema-entities-idx.sql");
29 38 }
  39 +
  40 + private final String schemaTypesSql = "schema-types-hsql.sql";
  41 +
  42 + @Override
  43 + public void createDatabaseSchema(boolean createIndexes) throws Exception {
  44 +
  45 + log.info("Installing SQL DataBase types part: " + schemaTypesSql);
  46 +
  47 + Path schemaFile = Paths.get(installScripts.getDataDir(), SQL_DIR, schemaTypesSql);
  48 + try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
  49 + String sql = new String(Files.readAllBytes(schemaFile), Charset.forName("UTF-8"));
  50 + conn.createStatement().execute(sql); //NOSONAR, ignoring because method used to load initial thingsboard database schema
  51 + }
  52 +
  53 + super.createDatabaseSchema(createIndexes);
  54 + }
30 55 }
31 56
... ...
... ... @@ -24,15 +24,17 @@ import org.springframework.util.StringUtils;
24 24 import org.thingsboard.server.common.data.Dashboard;
25 25 import org.thingsboard.server.common.data.id.CustomerId;
26 26 import org.thingsboard.server.common.data.id.EntityId;
27   -import org.thingsboard.server.common.data.id.RuleChainId;
28 27 import org.thingsboard.server.common.data.id.TenantId;
29 28 import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate;
30 29 import org.thingsboard.server.common.data.rule.RuleChain;
31 30 import org.thingsboard.server.common.data.rule.RuleChainMetaData;
  31 +import org.thingsboard.server.common.data.transport.resource.Resource;
  32 +import org.thingsboard.server.common.data.transport.resource.ResourceType;
32 33 import org.thingsboard.server.common.data.widget.WidgetType;
33 34 import org.thingsboard.server.common.data.widget.WidgetsBundle;
34 35 import org.thingsboard.server.dao.dashboard.DashboardService;
35 36 import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService;
  37 +import org.thingsboard.server.dao.resource.ResourceService;
36 38 import org.thingsboard.server.dao.rule.RuleChainService;
37 39 import org.thingsboard.server.dao.widget.WidgetTypeService;
38 40 import org.thingsboard.server.dao.widget.WidgetsBundleService;
... ... @@ -42,6 +44,7 @@ import java.nio.file.DirectoryStream;
42 44 import java.nio.file.Files;
43 45 import java.nio.file.Path;
44 46 import java.nio.file.Paths;
  47 +import java.util.Base64;
45 48 import java.util.Optional;
46 49
47 50 import static org.thingsboard.server.service.install.DatabaseHelper.objectMapper;
... ... @@ -66,8 +69,11 @@ public class InstallScripts {
66 69 public static final String WIDGET_BUNDLES_DIR = "widget_bundles";
67 70 public static final String OAUTH2_CONFIG_TEMPLATES_DIR = "oauth2_config_templates";
68 71 public static final String DASHBOARDS_DIR = "dashboards";
  72 + public static final String MODELS_DIR = "models";
  73 + public static final String CREDENTIALS_DIR = "credentials";
69 74
70 75 public static final String JSON_EXT = ".json";
  76 + public static final String XML_EXT = ".xml";
71 77
72 78 @Value("${install.data_dir:}")
73 79 private String dataDir;
... ... @@ -87,6 +93,9 @@ public class InstallScripts {
87 93 @Autowired
88 94 private OAuth2ConfigTemplateService oAuth2TemplateService;
89 95
  96 + @Autowired
  97 + private ResourceService resourceService;
  98 +
90 99 public Path getTenantRuleChainsDir() {
91 100 return Paths.get(getDataDir(), JSON_DIR, TENANT_DIR, RULE_CHAINS_DIR);
92 101 }
... ... @@ -186,6 +195,42 @@ public class InstallScripts {
186 195 }
187 196 }
188 197
  198 + public void loadSystemLwm2mResources() throws Exception {
  199 + Path modelsDir = Paths.get(getDataDir(), MODELS_DIR);
  200 + if (Files.isDirectory(modelsDir)) {
  201 + try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(modelsDir, path -> path.toString().endsWith(XML_EXT))) {
  202 + dirStream.forEach(
  203 + path -> {
  204 + try {
  205 + Resource resource = new Resource();
  206 + resource.setTenantId(TenantId.SYS_TENANT_ID);
  207 + resource.setResourceType(ResourceType.LWM2M_MODEL);
  208 + resource.setResourceId(path.getFileName().toString());
  209 + resource.setValue(Base64.getEncoder().encodeToString(Files.readAllBytes(path)));
  210 + resourceService.saveResource(resource);
  211 + } catch (Exception e) {
  212 + log.error("Unable to load lwm2m model [{}]", path.toString());
  213 + throw new RuntimeException("Unable to load lwm2m model", e);
  214 + }
  215 + }
  216 + );
  217 + }
  218 + }
  219 +
  220 + Path jksPath = Paths.get(getDataDir(), CREDENTIALS_DIR, "serverKeyStore.jks");
  221 + try {
  222 + Resource resource = new Resource();
  223 + resource.setTenantId(TenantId.SYS_TENANT_ID);
  224 + resource.setResourceType(ResourceType.JKS);
  225 + resource.setResourceId(jksPath.getFileName().toString());
  226 + resource.setValue(Base64.getEncoder().encodeToString(Files.readAllBytes(jksPath)));
  227 + resourceService.saveResource(resource);
  228 + } catch (Exception e) {
  229 + log.error("Unable to load lwm2m serverKeyStore [{}]", jksPath.toString());
  230 + throw new RuntimeException("Unable to load l2m2m serverKeyStore", e);
  231 + }
  232 + }
  233 +
189 234 public void loadDashboards(TenantId tenantId, CustomerId customerId) throws Exception {
190 235 Path dashboardsDir = Paths.get(getDataDir(), JSON_DIR, DEMO_DIR, DASHBOARDS_DIR);
191 236 try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(dashboardsDir, path -> path.toString().endsWith(JSON_EXT))) {
... ... @@ -208,7 +253,6 @@ public class InstallScripts {
208 253 }
209 254 }
210 255
211   -
212 256 public void loadDemoRuleChains(TenantId tenantId) throws Exception {
213 257 try {
214 258 createDefaultRuleChains(tenantId);
... ...
... ... @@ -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:
... ...
... ... @@ -30,7 +30,7 @@ import java.sql.SQLException;
30 30 @Slf4j
31 31 public abstract class SqlAbstractDatabaseSchemaService implements DatabaseSchemaService {
32 32
33   - private static final String SQL_DIR = "sql";
  33 + protected static final String SQL_DIR = "sql";
34 34
35 35 @Value("${spring.datasource.url}")
36 36 protected String dbUrl;
... ... @@ -42,7 +42,7 @@ public abstract class SqlAbstractDatabaseSchemaService implements DatabaseSchema
42 42 protected String dbPassword;
43 43
44 44 @Autowired
45   - private InstallScripts installScripts;
  45 + protected InstallScripts installScripts;
46 46
47 47 private final String schemaSql;
48 48 private final String schemaIdxSql;
... ...
... ... @@ -434,6 +434,25 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
434 434 log.info("Schema updated.");
435 435 }
436 436 break;
  437 + case "3.2.2":
  438 + try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
  439 + log.info("Updating schema ...");
  440 + try {
  441 + conn.createStatement().execute("CREATE TABLE IF NOT EXISTS resource (" +
  442 + " tenant_id uuid NOT NULL," +
  443 + " resource_type varchar(32) NOT NULL," +
  444 + " resource_id varchar(255) NOT NULL," +
  445 + " resource_value varchar," +
  446 + " CONSTRAINT resource_unq_key UNIQUE (tenant_id, resource_type, resource_id)" +
  447 + " );");
  448 +
  449 + conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3003000;");
  450 + } catch (Exception e) {
  451 + log.error("Failed updating schema!!!", e);
  452 + }
  453 + log.info("Schema updated.");
  454 + }
  455 + break;
437 456 default:
438 457 throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
439 458 }
... ...
... ... @@ -33,4 +33,6 @@ public interface SystemDataLoaderService {
33 33
34 34 void deleteSystemWidgetBundle(String bundleAlias) throws Exception;
35 35
  36 + void loadSystemLwm2mResources() throws Exception;
  37 +
36 38 }
... ...
... ... @@ -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);
... ...
... ... @@ -146,17 +146,17 @@ public class CassandraDbHelper {
146 146 if (row.isNull(index)) {
147 147 return null;
148 148 } else if (type.getProtocolCode() == ProtocolConstants.DataType.DOUBLE) {
149   - str = new Double(row.getDouble(index)).toString();
  149 + str = Double.valueOf(row.getDouble(index)).toString();
150 150 } else if (type.getProtocolCode() == ProtocolConstants.DataType.INT) {
151   - str = new Integer(row.getInt(index)).toString();
  151 + str = Integer.valueOf(row.getInt(index)).toString();
152 152 } else if (type.getProtocolCode() == ProtocolConstants.DataType.BIGINT) {
153   - str = new Long(row.getLong(index)).toString();
  153 + str = Long.valueOf(row.getLong(index)).toString();
154 154 } else if (type.getProtocolCode() == ProtocolConstants.DataType.UUID) {
155 155 str = row.getUuid(index).toString();
156 156 } else if (type.getProtocolCode() == ProtocolConstants.DataType.TIMEUUID) {
157 157 str = row.getUuid(index).toString();
158 158 } else if (type.getProtocolCode() == ProtocolConstants.DataType.FLOAT) {
159   - str = new Float(row.getFloat(index)).toString();
  159 + str = Float.valueOf(row.getFloat(index)).toString();
160 160 } else if (type.getProtocolCode() == ProtocolConstants.DataType.TIMESTAMP) {
161 161 str = ""+row.getInstant(index).toEpochMilli();
162 162 } else {
... ...
... ... @@ -153,7 +153,8 @@ public class CassandraToSqlColumn {
153 153 sqlInsertStatement.setBoolean(this.sqlIndex, Boolean.parseBoolean(value));
154 154 break;
155 155 case ENUM_TO_INT:
156   - Enum enumVal = Enum.valueOf(this.enumClass, value);
  156 + @SuppressWarnings("unchecked")
  157 + Enum<?> enumVal = Enum.valueOf(this.enumClass, value);
157 158 int intValue = enumVal.ordinal();
158 159 sqlInsertStatement.setInt(this.sqlIndex, intValue);
159 160 break;
... ...
... ... @@ -15,9 +15,7 @@
15 15 */
16 16 package org.thingsboard.server.service.install.update;
17 17
18   -import com.fasterxml.jackson.databind.JsonNode;
19 18 import com.fasterxml.jackson.databind.node.ObjectNode;
20   -import com.google.common.util.concurrent.FutureCallback;
21 19 import com.google.common.util.concurrent.Futures;
22 20 import com.google.common.util.concurrent.ListenableFuture;
23 21 import com.google.common.util.concurrent.MoreExecutors;
... ... @@ -25,16 +23,12 @@ import lombok.extern.slf4j.Slf4j;
25 23 import org.springframework.beans.factory.annotation.Autowired;
26 24 import org.springframework.context.annotation.Profile;
27 25 import org.springframework.stereotype.Service;
28   -import org.springframework.util.StringUtils;
29 26 import org.thingsboard.rule.engine.profile.TbDeviceProfileNode;
30 27 import org.thingsboard.rule.engine.profile.TbDeviceProfileNodeConfiguration;
31 28 import org.thingsboard.server.common.data.EntityView;
32   -import org.thingsboard.server.common.data.SearchTextBased;
33 29 import org.thingsboard.server.common.data.Tenant;
34   -import org.thingsboard.server.common.data.id.EntityId;
35 30 import org.thingsboard.server.common.data.id.EntityViewId;
36 31 import org.thingsboard.server.common.data.id.TenantId;
37   -import org.thingsboard.server.common.data.id.UUIDBased;
38 32 import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
39 33 import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
40 34 import org.thingsboard.server.common.data.kv.TsKvEntry;
... ... @@ -47,18 +41,16 @@ import org.thingsboard.server.dao.entityview.EntityViewService;
47 41 import org.thingsboard.server.dao.rule.RuleChainService;
48 42 import org.thingsboard.server.dao.tenant.TenantService;
49 43 import org.thingsboard.server.dao.timeseries.TimeseriesService;
50   -import org.thingsboard.server.dao.util.mapping.JacksonUtil;
  44 +import org.thingsboard.common.util.JacksonUtil;
51 45 import org.thingsboard.server.service.install.InstallScripts;
52 46
53   -import javax.annotation.Nullable;
54 47 import java.util.ArrayList;
55 48 import java.util.Collections;
56 49 import java.util.List;
57 50 import java.util.concurrent.ExecutionException;
58 51 import java.util.stream.Collectors;
59 52
60   -import static org.apache.commons.lang.StringUtils.isBlank;
61   -import static org.thingsboard.server.service.install.DatabaseHelper.objectMapper;
  53 +import static org.apache.commons.lang3.StringUtils.isBlank;
62 54
63 55 @Service
64 56 @Profile("install")
... ...
  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.lwm2m;
  17 +
  18 +
  19 +import lombok.extern.slf4j.Slf4j;
  20 +import org.eclipse.leshan.core.model.ObjectModel;
  21 +import org.eclipse.leshan.core.util.Hex;
  22 +import org.springframework.beans.factory.annotation.Autowired;
  23 +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
  24 +import org.springframework.data.domain.PageImpl;
  25 +import org.springframework.stereotype.Service;
  26 +import org.thingsboard.server.common.data.id.TenantId;
  27 +import org.thingsboard.server.common.data.lwm2m.LwM2mInstance;
  28 +import org.thingsboard.server.common.data.lwm2m.LwM2mObject;
  29 +import org.thingsboard.server.common.data.lwm2m.LwM2mResource;
  30 +import org.thingsboard.server.common.data.lwm2m.ServerSecurityConfig;
  31 +import org.thingsboard.server.common.data.page.PageData;
  32 +import org.thingsboard.server.common.data.page.PageLink;
  33 +import org.thingsboard.server.common.transport.lwm2m.LwM2MTransportConfigBootstrap;
  34 +import org.thingsboard.server.common.transport.lwm2m.LwM2MTransportConfigServer;
  35 +import org.thingsboard.server.dao.service.Validator;
  36 +import org.thingsboard.server.queue.util.TbLwM2mTransportComponent;
  37 +import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode;
  38 +
  39 +import java.math.BigInteger;
  40 +import java.security.AlgorithmParameters;
  41 +import java.security.GeneralSecurityException;
  42 +import java.security.KeyFactory;
  43 +import java.security.KeyStoreException;
  44 +import java.security.PublicKey;
  45 +import java.security.cert.CertificateEncodingException;
  46 +import java.security.cert.X509Certificate;
  47 +import java.security.spec.ECGenParameterSpec;
  48 +import java.security.spec.ECParameterSpec;
  49 +import java.security.spec.ECPoint;
  50 +import java.security.spec.ECPublicKeySpec;
  51 +import java.security.spec.KeySpec;
  52 +import java.util.ArrayList;
  53 +import java.util.Comparator;
  54 +import java.util.List;
  55 +import java.util.concurrent.atomic.AtomicInteger;
  56 +import java.util.function.Predicate;
  57 +import java.util.stream.Collector;
  58 +import java.util.stream.Collectors;
  59 +import java.util.stream.IntStream;
  60 +
  61 +import static org.thingsboard.server.dao.service.Validator.validateId;
  62 +
  63 +@Slf4j
  64 +@Service
  65 +@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true') || '${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core'")
  66 +public class LwM2MModelsRepository {
  67 +
  68 + private static final String INCORRECT_TENANT_ID = "Incorrect tenantId ";
  69 +
  70 + @Autowired
  71 + LwM2MTransportConfigServer contextServer;
  72 +
  73 +
  74 + @Autowired
  75 + LwM2MTransportConfigBootstrap contextBootStrap;
  76 +
  77 + /**
  78 + * @param objectIds
  79 + * @param textSearch
  80 + * @return list of LwM2mObject
  81 + * Filter by Predicate (uses objectIds, if objectIds is null then it uses textSearch,
  82 + * if textSearch is null then it uses AllList from List<ObjectModel>)
  83 + */
  84 + public List<LwM2mObject> getLwm2mObjects(int[] objectIds, String textSearch, String sortProperty, String sortOrder) {
  85 + if (objectIds == null && textSearch != null && !textSearch.isEmpty()) {
  86 + objectIds = getObjectIdFromTextSearch(textSearch);
  87 + }
  88 + int[] finalObjectIds = objectIds;
  89 + return getLwm2mObjects((objectIds != null && objectIds.length > 0 && textSearch != null && !textSearch.isEmpty()) ?
  90 + (ObjectModel element) -> IntStream.of(finalObjectIds).anyMatch(x -> x == element.id) || element.name.toLowerCase().contains(textSearch.toLowerCase()) :
  91 + (objectIds != null && objectIds.length > 0) ?
  92 + (ObjectModel element) -> IntStream.of(finalObjectIds).anyMatch(x -> x == element.id) :
  93 + (textSearch != null && !textSearch.isEmpty()) ?
  94 + (ObjectModel element) -> element.name.contains(textSearch) :
  95 + null,
  96 + sortProperty, sortOrder);
  97 + }
  98 +
  99 + /**
  100 + * @param predicate
  101 + * @return list of LwM2mObject
  102 + */
  103 + private List<LwM2mObject> getLwm2mObjects(Predicate<? super ObjectModel> predicate, String sortProperty, String sortOrder) {
  104 + List<LwM2mObject> lwM2mObjects = new ArrayList<>();
  105 + List<ObjectModel> listObjects = (predicate == null) ? this.contextServer.getModelsValue() :
  106 + contextServer.getModelsValue().stream()
  107 + .filter(predicate)
  108 + .collect(Collectors.toList());
  109 +
  110 + listObjects.forEach(obj -> {
  111 + LwM2mObject lwM2mObject = new LwM2mObject();
  112 + lwM2mObject.setId(obj.id);
  113 + lwM2mObject.setName(obj.name);
  114 + lwM2mObject.setMultiple(obj.multiple);
  115 + lwM2mObject.setMandatory(obj.mandatory);
  116 + LwM2mInstance instance = new LwM2mInstance();
  117 + instance.setId(0);
  118 + List<LwM2mResource> resources = new ArrayList<>();
  119 + obj.resources.forEach((k, v) -> {
  120 + if (!v.operations.isExecutable()) {
  121 + LwM2mResource resource = new LwM2mResource(k, v.name, false, false, false);
  122 + resources.add(resource);
  123 + }
  124 + });
  125 + instance.setResources(resources.stream().toArray(LwM2mResource[]::new));
  126 + lwM2mObject.setInstances(new LwM2mInstance[]{instance});
  127 + lwM2mObjects.add(lwM2mObject);
  128 + });
  129 + return lwM2mObjects.size() > 1 ? this.sortList (lwM2mObjects, sortProperty, sortOrder) : lwM2mObjects;
  130 + }
  131 +
  132 + private List<LwM2mObject> sortList (List<LwM2mObject> lwM2mObjects, String sortProperty, String sortOrder) {
  133 + switch (sortProperty) {
  134 + case "name":
  135 + switch (sortOrder) {
  136 + case "ASC":
  137 + lwM2mObjects.sort((o1, o2) -> o1.getName().compareTo(o2.getName()));
  138 + break;
  139 + case "DESC":
  140 + lwM2mObjects.stream().sorted(Comparator.comparing(LwM2mObject::getName).reversed());
  141 + break;
  142 + }
  143 + case "id":
  144 + switch (sortOrder) {
  145 + case "ASC":
  146 + lwM2mObjects.sort((o1, o2) -> Long.compare(o1.getId(), o2.getId()));
  147 + break;
  148 + case "DESC":
  149 + lwM2mObjects.sort((o1, o2) -> Long.compare(o2.getId(), o1.getId()));
  150 + }
  151 + }
  152 + return lwM2mObjects;
  153 + }
  154 +
  155 + /**
  156 + * @param tenantId
  157 + * @param pageLink
  158 + * @return List of LwM2mObject in PageData format
  159 + */
  160 + public PageData<LwM2mObject> findDeviceLwm2mObjects(TenantId tenantId, PageLink pageLink) {
  161 + log.trace("Executing findDeviceProfileInfos tenantId [{}], pageLink [{}]", tenantId, pageLink);
  162 + validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
  163 + Validator.validatePageLink(pageLink);
  164 + return this.findLwm2mListObjects(pageLink);
  165 + }
  166 +
  167 + /**
  168 + * @param pageLink
  169 + * @return List of LwM2mObject in PageData format, filter == TextSearch
  170 + * PageNumber = 1, PageSize = List<LwM2mObject>.size()
  171 + */
  172 + public PageData<LwM2mObject> findLwm2mListObjects(PageLink pageLink) {
  173 + PageImpl<LwM2mObject> page = new PageImpl<>(getLwm2mObjects(getObjectIdFromTextSearch(pageLink.getTextSearch()),
  174 + pageLink.getTextSearch(),
  175 + pageLink.getSortOrder().getProperty(),
  176 + pageLink.getSortOrder().getDirection().name()));
  177 + PageData<LwM2mObject> pageData = new PageData<>(page.getContent(), page.getTotalPages(), page.getTotalElements(), page.hasNext());
  178 + return pageData;
  179 + }
  180 +
  181 + /**
  182 + * Filter for id Object
  183 + * @param textSearch -
  184 + * @return - return Object id only first chartAt in textSearch
  185 + */
  186 + private int[] getObjectIdFromTextSearch(String textSearch) {
  187 + String filtered = null;
  188 + if (textSearch !=null && !textSearch.isEmpty()) {
  189 + AtomicInteger a = new AtomicInteger();
  190 + filtered = textSearch.chars ()
  191 + .mapToObj(chr -> (char) chr)
  192 + .filter(i -> Character.isDigit(i) && textSearch.charAt(a.getAndIncrement()) == i)
  193 + .collect(Collector.of(StringBuilder::new, StringBuilder::append, StringBuilder::append, StringBuilder::toString));
  194 + }
  195 + return (filtered != null && !filtered.isEmpty()) ? new int[]{Integer.parseInt(filtered)} : new int[0];
  196 + }
  197 +
  198 + /**
  199 + * @param securityMode
  200 + * @param bootstrapServerIs
  201 + * @return ServerSecurityConfig more value is default: Important - port, host, publicKey
  202 + */
  203 + public ServerSecurityConfig getBootstrapSecurityInfo(String securityMode, boolean bootstrapServerIs) {
  204 + LwM2MSecurityMode lwM2MSecurityMode = LwM2MSecurityMode.fromSecurityMode(securityMode.toLowerCase());
  205 + return getBootstrapServer(bootstrapServerIs, lwM2MSecurityMode);
  206 + }
  207 +
  208 + /**
  209 + * @param bootstrapServerIs
  210 + * @param mode
  211 + * @return ServerSecurityConfig more value is default: Important - port, host, publicKey
  212 + */
  213 + private ServerSecurityConfig getBootstrapServer(boolean bootstrapServerIs, LwM2MSecurityMode mode) {
  214 + ServerSecurityConfig bsServ = new ServerSecurityConfig();
  215 + bsServ.setBootstrapServerIs(bootstrapServerIs);
  216 + if (bootstrapServerIs) {
  217 + bsServ.setServerId(contextBootStrap.getBootstrapServerId());
  218 + switch (mode) {
  219 + case NO_SEC:
  220 + bsServ.setHost(contextBootStrap.getBootstrapHost());
  221 + bsServ.setPort(contextBootStrap.getBootstrapPortNoSec());
  222 + bsServ.setServerPublicKey("");
  223 + break;
  224 + case PSK:
  225 + bsServ.setHost(contextBootStrap.getBootstrapHostSecurity());
  226 + bsServ.setPort(contextBootStrap.getBootstrapPortSecurity());
  227 + bsServ.setServerPublicKey("");
  228 + break;
  229 + case RPK:
  230 + case X509:
  231 + bsServ.setHost(contextBootStrap.getBootstrapHostSecurity());
  232 + bsServ.setPort(contextBootStrap.getBootstrapPortSecurity());
  233 + bsServ.setServerPublicKey(getPublicKey (contextBootStrap.getBootstrapAlias(), this.contextBootStrap.getBootstrapPublicX(), this.contextBootStrap.getBootstrapPublicY()));
  234 + break;
  235 + default:
  236 + break;
  237 + }
  238 + } else {
  239 + bsServ.setServerId(contextServer.getServerId());
  240 + switch (mode) {
  241 + case NO_SEC:
  242 + bsServ.setHost(contextServer.getServerHost());
  243 + bsServ.setPort(contextServer.getServerPortNoSec());
  244 + bsServ.setServerPublicKey("");
  245 + break;
  246 + case PSK:
  247 + bsServ.setHost(contextServer.getServerHostSecurity());
  248 + bsServ.setPort(contextServer.getServerPortSecurity());
  249 + bsServ.setServerPublicKey("");
  250 + break;
  251 + case RPK:
  252 + case X509:
  253 + bsServ.setHost(contextServer.getServerHostSecurity());
  254 + bsServ.setPort(contextServer.getServerPortSecurity());
  255 + bsServ.setServerPublicKey(getPublicKey (contextServer.getServerAlias(), this.contextServer.getServerPublicX(), this.contextServer.getServerPublicY()));
  256 + break;
  257 + default:
  258 + break;
  259 + }
  260 + }
  261 + return bsServ;
  262 + }
  263 +
  264 + private String getPublicKey (String alias, String publicServerX, String publicServerY) {
  265 + String publicKey = getServerPublicKeyX509(alias);
  266 + return publicKey != null ? publicKey : getRPKPublicKey(publicServerX, publicServerY);
  267 + }
  268 +
  269 + /**
  270 + * @param alias
  271 + * @return PublicKey format HexString or null
  272 + */
  273 + private String getServerPublicKeyX509(String alias) {
  274 + try {
  275 + X509Certificate serverCertificate = (X509Certificate) contextServer.getKeyStoreValue().getCertificate(alias);
  276 + return Hex.encodeHexString(serverCertificate.getEncoded());
  277 + } catch (CertificateEncodingException | KeyStoreException e) {
  278 + e.printStackTrace();
  279 + }
  280 + return null;
  281 + }
  282 +
  283 + /**
  284 + * @param publicServerX
  285 + * @param publicServerY
  286 + * @return PublicKey format HexString or null
  287 + */
  288 + private String getRPKPublicKey(String publicServerX, String publicServerY) {
  289 + try {
  290 + /** Get Elliptic Curve Parameter spec for secp256r1 */
  291 + AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC");
  292 + algoParameters.init(new ECGenParameterSpec("secp256r1"));
  293 + ECParameterSpec parameterSpec = algoParameters.getParameterSpec(ECParameterSpec.class);
  294 + if (publicServerX != null && !publicServerX.isEmpty() && publicServerY != null && !publicServerY.isEmpty()) {
  295 + /** Get point values */
  296 + byte[] publicX = Hex.decodeHex(publicServerX.toCharArray());
  297 + byte[] publicY = Hex.decodeHex(publicServerY.toCharArray());
  298 + /** Create key specs */
  299 + KeySpec publicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(publicX), new BigInteger(publicY)),
  300 + parameterSpec);
  301 + /** Get keys */
  302 + PublicKey publicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec);
  303 + if (publicKey != null && publicKey.getEncoded().length > 0) {
  304 + return Hex.encodeHexString(publicKey.getEncoded());
  305 + }
  306 + }
  307 + } catch (GeneralSecurityException | IllegalArgumentException e) {
  308 + log.error("[{}] Failed generate Server RPK for profile", e.getMessage());
  309 + throw new RuntimeException(e);
  310 + }
  311 + return null;
  312 + }
  313 +}
  314 +
... ...
... ... @@ -47,7 +47,7 @@ import org.thingsboard.server.dao.attributes.AttributesService;
47 47 import org.thingsboard.server.dao.entity.EntityService;
48 48 import org.thingsboard.server.dao.model.ModelConstants;
49 49 import org.thingsboard.server.dao.timeseries.TimeseriesService;
50   -import org.thingsboard.server.dao.util.mapping.JacksonUtil;
  50 +import org.thingsboard.common.util.JacksonUtil;
51 51 import org.thingsboard.server.queue.util.TbCoreComponent;
52 52 import org.thingsboard.server.service.executors.DbCallbackExecutorService;
53 53 import org.thingsboard.server.service.security.AccessValidator;
... ... @@ -206,7 +206,7 @@ public class DefaultEntityQueryService implements EntityQueryService {
206 206 addItemsToArrayNode(json.putArray("entityTypes"), types);
207 207 addItemsToArrayNode(json.putArray("timeseries"), timeseriesKeys);
208 208 addItemsToArrayNode(json.putArray("attribute"), attributesKeys);
209   - response.setResult(new ResponseEntity(json, HttpStatus.OK));
  209 + response.setResult(new ResponseEntity<>(json, HttpStatus.OK));
210 210 }
211 211
212 212 private void replyWithEmptyResponse(DeferredResult<ResponseEntity> response) {
... ...
... ... @@ -34,6 +34,7 @@ import org.thingsboard.server.common.data.id.EntityId;
34 34 import org.thingsboard.server.common.data.id.RuleChainId;
35 35 import org.thingsboard.server.common.data.id.TenantId;
36 36 import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
  37 +import org.thingsboard.server.common.data.transport.resource.Resource;
37 38 import org.thingsboard.server.common.msg.TbMsg;
38 39 import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
39 40 import org.thingsboard.server.common.msg.queue.ServiceType;
... ... @@ -145,7 +146,7 @@ public class DefaultTbClusterService implements TbClusterService {
145 146 tbMsg = transformMsg(tbMsg, deviceProfileCache.get(tenantId, new DeviceProfileId(entityId.getId())));
146 147 }
147 148 }
148   - TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, tenantId, entityId);
  149 + TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, tbMsg.getQueueName(), tenantId, entityId);
149 150 log.trace("PUSHING msg: {} to:{}", tbMsg, tpi);
150 151 ToRuleEngineMsg msg = ToRuleEngineMsg.newBuilder()
151 152 .setTenantIdMSB(tenantId.getId().getMostSignificantBits())
... ... @@ -247,6 +248,34 @@ public class DefaultTbClusterService implements TbClusterService {
247 248 onEntityDelete(entity.getTenantId(), entity.getId(), entity.getName(), callback);
248 249 }
249 250
  251 + @Override
  252 + public void onResourceChange(Resource resource, TbQueueCallback callback) {
  253 + TenantId tenantId = resource.getTenantId();
  254 + log.trace("[{}][{}][{}] Processing change resource", tenantId, resource.getResourceType(), resource.getResourceId());
  255 + TransportProtos.ResourceUpdateMsg resourceUpdateMsg = TransportProtos.ResourceUpdateMsg.newBuilder()
  256 + .setTenantIdMSB(tenantId.getId().getMostSignificantBits())
  257 + .setTenantIdLSB(tenantId.getId().getLeastSignificantBits())
  258 + .setResourceType(resource.getResourceType().name())
  259 + .setResourceId(resource.getResourceId())
  260 + .build();
  261 + ToTransportMsg transportMsg = ToTransportMsg.newBuilder().setResourceUpdateMsg(resourceUpdateMsg).build();
  262 + broadcast(transportMsg, callback);
  263 + }
  264 +
  265 + @Override
  266 + public void onResourceDeleted(Resource resource, TbQueueCallback callback) {
  267 + TenantId tenantId = resource.getTenantId();
  268 + log.trace("[{}][{}][{}] Processing delete resource", tenantId, resource.getResourceType(), resource.getResourceId());
  269 + TransportProtos.ResourceDeleteMsg resourceUpdateMsg = TransportProtos.ResourceDeleteMsg.newBuilder()
  270 + .setTenantIdMSB(tenantId.getId().getMostSignificantBits())
  271 + .setTenantIdLSB(tenantId.getId().getLeastSignificantBits())
  272 + .setResourceType(resource.getResourceType().name())
  273 + .setResourceId(resource.getResourceId())
  274 + .build();
  275 + ToTransportMsg transportMsg = ToTransportMsg.newBuilder().setResourceDeleteMsg(resourceUpdateMsg).build();
  276 + broadcast(transportMsg, callback);
  277 + }
  278 +
250 279 public <T> void onEntityChange(TenantId tenantId, EntityId entityid, T entity, TbQueueCallback callback) {
251 280 String entityName = (entity instanceof HasName) ? ((HasName) entity).getName() : entity.getClass().getName();
252 281 log.trace("[{}][{}][{}] Processing [{}] change event", tenantId, entityid.getEntityType(), entityid.getId(), entityName);
... ...
... ... @@ -35,7 +35,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType;
35 35 import org.thingsboard.server.common.msg.queue.TbCallback;
36 36 import org.thingsboard.server.common.stats.StatsFactory;
37 37 import org.thingsboard.server.common.transport.util.DataDecodingEncodingService;
38   -import org.thingsboard.server.dao.util.mapping.JacksonUtil;
  38 +import org.thingsboard.common.util.JacksonUtil;
39 39 import org.thingsboard.server.gen.transport.TransportProtos.DeviceStateServiceMsgProto;
40 40 import org.thingsboard.server.gen.transport.TransportProtos.FromDeviceRPCResponseProto;
41 41 import org.thingsboard.server.gen.transport.TransportProtos.LocalSubscriptionServiceMsgProto;
... ... @@ -151,12 +151,12 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
151 151 }
152 152
153 153 @Override
154   - public void onApplicationEvent(PartitionChangeEvent partitionChangeEvent) {
155   - if (partitionChangeEvent.getServiceType().equals(getServiceType())) {
156   - log.info("Subscribing to partitions: {}", partitionChangeEvent.getPartitions());
157   - this.mainConsumer.subscribe(partitionChangeEvent.getPartitions());
  154 + protected void onTbApplicationEvent(PartitionChangeEvent event) {
  155 + if (event.getServiceType().equals(getServiceType())) {
  156 + log.info("Subscribing to partitions: {}", event.getPartitions());
  157 + this.mainConsumer.subscribe(event.getPartitions());
158 158 this.usageStatsConsumer.subscribe(
159   - partitionChangeEvent
  159 + event
160 160 .getPartitions()
161 161 .stream()
162 162 .map(tpi -> tpi.newByTopic(usageStatsConsumer.getTopic()))
... ...
... ... @@ -140,11 +140,11 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
140 140 }
141 141
142 142 @Override
143   - public void onApplicationEvent(PartitionChangeEvent partitionChangeEvent) {
144   - if (partitionChangeEvent.getServiceType().equals(getServiceType())) {
145   - ServiceQueue serviceQueue = partitionChangeEvent.getServiceQueueKey().getServiceQueue();
146   - log.info("[{}] Subscribing to partitions: {}", serviceQueue.getQueue(), partitionChangeEvent.getPartitions());
147   - consumers.get(serviceQueue.getQueue()).subscribe(partitionChangeEvent.getPartitions());
  143 + protected void onTbApplicationEvent(PartitionChangeEvent event) {
  144 + if (event.getServiceType().equals(getServiceType())) {
  145 + ServiceQueue serviceQueue = event.getServiceQueueKey().getServiceQueue();
  146 + log.info("[{}] Subscribing to partitions: {}", serviceQueue.getQueue(), event.getPartitions());
  147 + consumers.get(serviceQueue.getQueue()).subscribe(event.getPartitions());
148 148 }
149 149 }
150 150
... ... @@ -181,7 +181,7 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
181 181 new TbMsgPackCallback(id, tenantId, ctx, stats.getTimer(tenantId, SUCCESSFUL_STATUS), stats.getTimer(tenantId, FAILED_STATUS)) :
182 182 new TbMsgPackCallback(id, tenantId, ctx);
183 183 try {
184   - if (toRuleEngineMsg.getTbMsg() != null && !toRuleEngineMsg.getTbMsg().isEmpty()) {
  184 + if (!toRuleEngineMsg.getTbMsg().isEmpty()) {
185 185 forwardToRuleEngineActor(configuration.getName(), tenantId, toRuleEngineMsg, callback);
186 186 } else {
187 187 callback.onSuccess();
... ... @@ -209,6 +209,9 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
209 209 if (statsEnabled) {
210 210 stats.log(result, decision.isCommit());
211 211 }
  212 +
  213 + ctx.cleanup();
  214 +
212 215 if (decision.isCommit()) {
213 216 submitStrategy.stop();
214 217 break;
... ...
... ... @@ -24,6 +24,7 @@ import org.thingsboard.server.common.data.TenantProfile;
24 24 import org.thingsboard.server.common.data.id.EntityId;
25 25 import org.thingsboard.server.common.data.id.TenantId;
26 26 import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
  27 +import org.thingsboard.server.common.data.transport.resource.Resource;
27 28 import org.thingsboard.server.common.msg.TbMsg;
28 29 import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
29 30 import org.thingsboard.server.gen.transport.TransportProtos;
... ... @@ -71,4 +72,8 @@ public interface TbClusterService {
71 72 void onDeviceChange(Device device, TbQueueCallback callback);
72 73
73 74 void onDeviceDeleted(Device device, TbQueueCallback callback);
  75 +
  76 + void onResourceChange(Resource resource, TbQueueCallback callback);
  77 +
  78 + void onResourceDeleted(Resource resource, TbQueueCallback callback);
74 79 }
... ...
... ... @@ -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) {
... ...
... ... @@ -147,4 +147,10 @@ public class TbMsgPackProcessingContext {
147 147 .forEach(info -> log.info("[{}][{}] execution count: {}. {}", queueName, info.getRuleNodeId(), info.getExecutionCount(), info.getLabel()));
148 148 }
149 149 }
  150 +
  151 + public void cleanup() {
  152 + pendingMap.clear();
  153 + successMap.clear();
  154 + failedMap.clear();
  155 + }
150 156 }
... ...
... ... @@ -36,6 +36,7 @@ import org.thingsboard.server.queue.TbQueueConsumer;
36 36 import org.thingsboard.server.queue.common.TbProtoQueueMsg;
37 37 import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
38 38 import org.thingsboard.server.common.transport.util.DataDecodingEncodingService;
  39 +import org.thingsboard.server.queue.discovery.TbApplicationEventListener;
39 40 import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
40 41 import org.thingsboard.server.service.profile.TbDeviceProfileCache;
41 42 import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
... ... @@ -56,7 +57,7 @@ import java.util.function.Function;
56 57 import java.util.stream.Collectors;
57 58
58 59 @Slf4j
59   -public abstract class AbstractConsumerService<N extends com.google.protobuf.GeneratedMessageV3> implements ApplicationListener<PartitionChangeEvent> {
  60 +public abstract class AbstractConsumerService<N extends com.google.protobuf.GeneratedMessageV3> extends TbApplicationEventListener<PartitionChangeEvent> {
60 61
61 62 protected volatile ExecutorService consumersExecutor;
62 63 protected volatile ExecutorService notificationsConsumerExecutor;
... ...
... ... @@ -31,6 +31,8 @@ import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
31 31 @RequiredArgsConstructor
32 32 public class ToDeviceRpcRequestActorMsg implements ToDeviceActorNotificationMsg {
33 33
  34 + private static final long serialVersionUID = -8592877558138716589L;
  35 +
34 36 @Getter
35 37 private final String serviceId;
36 38 @Getter
... ...
... ... @@ -21,7 +21,6 @@ import com.google.common.util.concurrent.ListenableFuture;
21 21 import com.google.common.util.concurrent.MoreExecutors;
22 22 import delight.nashornsandbox.NashornSandbox;
23 23 import delight.nashornsandbox.NashornSandboxes;
24   -import jdk.nashorn.api.scripting.NashornScriptEngineFactory;
25 24 import lombok.Getter;
26 25 import lombok.extern.slf4j.Slf4j;
27 26 import org.springframework.beans.factory.annotation.Value;
... ... @@ -33,6 +32,7 @@ import javax.annotation.PostConstruct;
33 32 import javax.annotation.PreDestroy;
34 33 import javax.script.Invocable;
35 34 import javax.script.ScriptEngine;
  35 +import javax.script.ScriptEngineManager;
36 36 import javax.script.ScriptException;
37 37 import java.util.UUID;
38 38 import java.util.concurrent.ExecutionException;
... ... @@ -97,8 +97,8 @@ public abstract class AbstractNashornJsInvokeService extends AbstractJsInvokeSer
97 97 sandbox.allowLoadFunctions(true);
98 98 sandbox.setMaxPreparedStatements(30);
99 99 } else {
100   - NashornScriptEngineFactory factory = new NashornScriptEngineFactory();
101   - engine = factory.getScriptEngine(new String[]{"--no-java"});
  100 + ScriptEngineManager factory = new ScriptEngineManager();
  101 + engine = factory.getEngineByName("nashorn");
102 102 }
103 103 }
104 104
... ...
... ... @@ -29,7 +29,7 @@ public class SkipPathRequestMatcher implements RequestMatcher {
29 29 private RequestMatcher processingMatcher;
30 30
31 31 public SkipPathRequestMatcher(List<String> pathsToSkip, String processingPath) {
32   - Assert.notNull(pathsToSkip);
  32 + Assert.notNull(pathsToSkip, "List of paths to skip is required.");
33 33 List<RequestMatcher> m = pathsToSkip.stream().map(path -> new AntPathRequestMatcher(path)).collect(Collectors.toList());
34 34 matchers = new OrRequestMatcher(m);
35 35 processingMatcher = new AntPathRequestMatcher(processingPath);
... ...
  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.security.auth.oauth2;
  17 +
  18 +import org.springframework.util.SerializationUtils;
  19 +import javax.servlet.http.Cookie;
  20 +import javax.servlet.http.HttpServletRequest;
  21 +import javax.servlet.http.HttpServletResponse;
  22 +import java.util.Base64;
  23 +import java.util.Optional;
  24 +
  25 +public class CookieUtils {
  26 +
  27 + public static Optional<Cookie> getCookie(HttpServletRequest request, String name) {
  28 + Cookie[] cookies = request.getCookies();
  29 +
  30 + if (cookies != null && cookies.length > 0) {
  31 + for (Cookie cookie : cookies) {
  32 + if (cookie.getName().equals(name)) {
  33 + return Optional.of(cookie);
  34 + }
  35 + }
  36 + }
  37 +
  38 + return Optional.empty();
  39 + }
  40 +
  41 + public static void addCookie(HttpServletResponse response, String name, String value, int maxAge) {
  42 + Cookie cookie = new Cookie(name, value);
  43 + cookie.setPath("/");
  44 + cookie.setHttpOnly(true);
  45 + cookie.setMaxAge(maxAge);
  46 + response.addCookie(cookie);
  47 + }
  48 +
  49 + public static void deleteCookie(HttpServletRequest request, HttpServletResponse response, String name) {
  50 + Cookie[] cookies = request.getCookies();
  51 + if (cookies != null && cookies.length > 0) {
  52 + for (Cookie cookie: cookies) {
  53 + if (cookie.getName().equals(name)) {
  54 + cookie.setValue("");
  55 + cookie.setPath("/");
  56 + cookie.setMaxAge(0);
  57 + response.addCookie(cookie);
  58 + }
  59 + }
  60 + }
  61 + }
  62 +
  63 + public static String serialize(Object object) {
  64 + return Base64.getUrlEncoder()
  65 + .encodeToString(SerializationUtils.serialize(object));
  66 + }
  67 +
  68 + public static <T> T deserialize(Cookie cookie, Class<T> cls) {
  69 + return cls.cast(SerializationUtils.deserialize(
  70 + Base64.getUrlDecoder().decode(cookie.getValue())));
  71 + }
  72 +}
... ...
  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.security.auth.oauth2;
  17 +
  18 +import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository;
  19 +import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
  20 +import org.springframework.stereotype.Component;
  21 +
  22 +import javax.servlet.http.HttpServletRequest;
  23 +import javax.servlet.http.HttpServletResponse;
  24 +
  25 +@Component
  26 +public class HttpCookieOAuth2AuthorizationRequestRepository implements AuthorizationRequestRepository<OAuth2AuthorizationRequest> {
  27 + public static final String OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME = "oauth2_auth_request";
  28 + private static final int cookieExpireSeconds = 180;
  29 +
  30 + @Override
  31 + public OAuth2AuthorizationRequest loadAuthorizationRequest(HttpServletRequest request) {
  32 + return CookieUtils.getCookie(request, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME)
  33 + .map(cookie -> CookieUtils.deserialize(cookie, OAuth2AuthorizationRequest.class))
  34 + .orElse(null);
  35 + }
  36 +
  37 + @Override
  38 + public void saveAuthorizationRequest(OAuth2AuthorizationRequest authorizationRequest, HttpServletRequest request, HttpServletResponse response) {
  39 + if (authorizationRequest == null) {
  40 + CookieUtils.deleteCookie(request, response, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME);
  41 + return;
  42 + }
  43 + CookieUtils.addCookie(response, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME, CookieUtils.serialize(authorizationRequest), cookieExpireSeconds);
  44 + }
  45 +
  46 + @SuppressWarnings("deprecation")
  47 + @Override
  48 + public OAuth2AuthorizationRequest removeAuthorizationRequest(HttpServletRequest request) {
  49 + return this.loadAuthorizationRequest(request);
  50 + }
  51 +
  52 + public void removeAuthorizationRequestCookies(HttpServletRequest request, HttpServletResponse response) {
  53 + CookieUtils.deleteCookie(request, response, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME);
  54 + }
  55 +}
... ...
... ... @@ -37,10 +37,13 @@ import java.nio.charset.StandardCharsets;
37 37 @ConditionalOnProperty(prefix = "security.oauth2", value = "enabled", havingValue = "true")
38 38 public class Oauth2AuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
39 39
  40 + private final HttpCookieOAuth2AuthorizationRequestRepository httpCookieOAuth2AuthorizationRequestRepository;
40 41 private final SystemSecurityService systemSecurityService;
41 42
42 43 @Autowired
43   - public Oauth2AuthenticationFailureHandler(final SystemSecurityService systemSecurityService) {
  44 + public Oauth2AuthenticationFailureHandler(final HttpCookieOAuth2AuthorizationRequestRepository httpCookieOAuth2AuthorizationRequestRepository,
  45 + final SystemSecurityService systemSecurityService) {
  46 + this.httpCookieOAuth2AuthorizationRequestRepository = httpCookieOAuth2AuthorizationRequestRepository;
44 47 this.systemSecurityService = systemSecurityService;
45 48 }
46 49
... ... @@ -49,6 +52,7 @@ public class Oauth2AuthenticationFailureHandler extends SimpleUrlAuthenticationF
49 52 HttpServletResponse response, AuthenticationException exception)
50 53 throws IOException, ServletException {
51 54 String baseUrl = this.systemSecurityService.getBaseUrl(TenantId.SYS_TENANT_ID, new CustomerId(EntityId.NULL_UUID), request);
  55 + httpCookieOAuth2AuthorizationRequestRepository.removeAuthorizationRequestCookies(request, response);
52 56 getRedirectStrategy().sendRedirect(request, response, baseUrl + "/login?loginError=" +
53 57 URLEncoder.encode(exception.getMessage(), StandardCharsets.UTF_8.toString()));
54 58 }
... ...
... ... @@ -49,6 +49,7 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS
49 49 private final OAuth2ClientMapperProvider oauth2ClientMapperProvider;
50 50 private final OAuth2Service oAuth2Service;
51 51 private final OAuth2AuthorizedClientService oAuth2AuthorizedClientService;
  52 + private final HttpCookieOAuth2AuthorizationRequestRepository httpCookieOAuth2AuthorizationRequestRepository;
52 53 private final SystemSecurityService systemSecurityService;
53 54
54 55 @Autowired
... ... @@ -56,12 +57,15 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS
56 57 final RefreshTokenRepository refreshTokenRepository,
57 58 final OAuth2ClientMapperProvider oauth2ClientMapperProvider,
58 59 final OAuth2Service oAuth2Service,
59   - final OAuth2AuthorizedClientService oAuth2AuthorizedClientService, final SystemSecurityService systemSecurityService) {
  60 + final OAuth2AuthorizedClientService oAuth2AuthorizedClientService,
  61 + final HttpCookieOAuth2AuthorizationRequestRepository httpCookieOAuth2AuthorizationRequestRepository,
  62 + final SystemSecurityService systemSecurityService) {
60 63 this.tokenFactory = tokenFactory;
61 64 this.refreshTokenRepository = refreshTokenRepository;
62 65 this.oauth2ClientMapperProvider = oauth2ClientMapperProvider;
63 66 this.oAuth2Service = oAuth2Service;
64 67 this.oAuth2AuthorizedClientService = oAuth2AuthorizedClientService;
  68 + this.httpCookieOAuth2AuthorizationRequestRepository = httpCookieOAuth2AuthorizationRequestRepository;
65 69 this.systemSecurityService = systemSecurityService;
66 70 }
67 71
... ... @@ -84,10 +88,17 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS
84 88 JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser);
85 89 JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser);
86 90
  91 + clearAuthenticationAttributes(request, response);
87 92 getRedirectStrategy().sendRedirect(request, response, baseUrl + "/?accessToken=" + accessToken.getToken() + "&refreshToken=" + refreshToken.getToken());
88 93 } catch (Exception e) {
  94 + clearAuthenticationAttributes(request, response);
89 95 getRedirectStrategy().sendRedirect(request, response, baseUrl + "/login?loginError=" +
90 96 URLEncoder.encode(e.getMessage(), StandardCharsets.UTF_8.toString()));
91 97 }
92 98 }
  99 +
  100 + protected void clearAuthenticationAttributes(HttpServletRequest request, HttpServletResponse response) {
  101 + super.clearAuthenticationAttributes(request);
  102 + httpCookieOAuth2AuthorizationRequestRepository.removeAuthorizationRequestCookies(request, response);
  103 + }
93 104 }
... ...
... ... @@ -53,6 +53,8 @@ public class DefaultDeviceAuthService implements DeviceAuthService {
53 53 return DeviceAuthResult.of(credentials.getDeviceId());
54 54 case X509_CERTIFICATE:
55 55 return DeviceAuthResult.of(credentials.getDeviceId());
  56 + case LWM2M_CREDENTIALS:
  57 + return DeviceAuthResult.of(credentials.getDeviceId());
56 58 default:
57 59 return DeviceAuthResult.of("Credentials Type is not supported yet!");
58 60 }
... ... @@ -65,4 +67,4 @@ public class DefaultDeviceAuthService implements DeviceAuthService {
65 67 }
66 68 }
67 69
68   -}
\ No newline at end of file
  70 +}
... ...
... ... @@ -100,6 +100,7 @@ public class JwtTokenFactory {
100 100 Jws<Claims> jwsClaims = rawAccessToken.parseClaims(settings.getTokenSigningKey());
101 101 Claims claims = jwsClaims.getBody();
102 102 String subject = claims.getSubject();
  103 + @SuppressWarnings("unchecked")
103 104 List<String> scopes = claims.get(SCOPES, List.class);
104 105 if (scopes == null || scopes.isEmpty()) {
105 106 throw new IllegalArgumentException("JWT Token doesn't have any scopes");
... ... @@ -155,6 +156,7 @@ public class JwtTokenFactory {
155 156 Jws<Claims> jwsClaims = rawAccessToken.parseClaims(settings.getTokenSigningKey());
156 157 Claims claims = jwsClaims.getBody();
157 158 String subject = claims.getSubject();
  159 + @SuppressWarnings("unchecked")
158 160 List<String> scopes = claims.get(SCOPES, List.class);
159 161 if (scopes == null || scopes.isEmpty()) {
160 162 throw new IllegalArgumentException("Refresh Token doesn't have any scopes");
... ...
... ... @@ -47,6 +47,7 @@ public class CustomerUserPermissions extends AbstractPermissions {
47 47 Operation.READ_ATTRIBUTES, Operation.READ_TELEMETRY, Operation.RPC_CALL, Operation.CLAIM_DEVICES) {
48 48
49 49 @Override
  50 + @SuppressWarnings("unchecked")
50 51 public boolean hasPermission(SecurityUser user, Operation operation, EntityId entityId, HasTenantId entity) {
51 52
52 53 if (!super.hasPermission(user, operation, entityId, entity)) {
... ... @@ -69,6 +70,7 @@ public class CustomerUserPermissions extends AbstractPermissions {
69 70 new PermissionChecker.GenericPermissionChecker(Operation.READ, Operation.READ_ATTRIBUTES, Operation.READ_TELEMETRY) {
70 71
71 72 @Override
  73 + @SuppressWarnings("unchecked")
72 74 public boolean hasPermission(SecurityUser user, Operation operation, EntityId entityId, HasTenantId entity) {
73 75 if (!super.hasPermission(user, operation, entityId, entity)) {
74 76 return false;
... ... @@ -119,6 +121,7 @@ public class CustomerUserPermissions extends AbstractPermissions {
119 121 private static final PermissionChecker widgetsPermissionChecker = new PermissionChecker.GenericPermissionChecker(Operation.READ) {
120 122
121 123 @Override
  124 + @SuppressWarnings("unchecked")
122 125 public boolean hasPermission(SecurityUser user, Operation operation, EntityId entityId, HasTenantId entity) {
123 126 if (!super.hasPermission(user, operation, entityId, entity)) {
124 127 return false;
... ...
... ... @@ -56,6 +56,7 @@ public class DefaultAccessControlService implements AccessControlService {
56 56 }
57 57
58 58 @Override
  59 + @SuppressWarnings("unchecked")
59 60 public <I extends EntityId, T extends HasTenantId> void checkPermission(SecurityUser user, Resource resource,
60 61 Operation operation, I entityId, T entity) throws ThingsboardException {
61 62 PermissionChecker permissionChecker = getPermissionChecker(user.getAuthority(), resource);
... ...
... ... @@ -59,6 +59,7 @@ public class TenantAdminPermissions extends AbstractPermissions {
59 59 new PermissionChecker.GenericPermissionChecker(Operation.READ, Operation.READ_ATTRIBUTES, Operation.READ_TELEMETRY) {
60 60
61 61 @Override
  62 + @SuppressWarnings("unchecked")
62 63 public boolean hasPermission(SecurityUser user, Operation operation, EntityId entityId, HasTenantId entity) {
63 64 if (!super.hasPermission(user, operation, entityId, entity)) {
64 65 return false;
... ...
... ... @@ -15,8 +15,8 @@
15 15 */
16 16 package org.thingsboard.server.service.security.system;
17 17
  18 +import com.fasterxml.jackson.core.type.TypeReference;
18 19 import com.fasterxml.jackson.databind.JsonNode;
19   -import com.fasterxml.jackson.databind.ObjectMapper;
20 20 import com.fasterxml.jackson.databind.node.ObjectNode;
21 21 import lombok.extern.slf4j.Slf4j;
22 22 import org.apache.commons.lang3.StringUtils;
... ... @@ -49,6 +49,7 @@ import org.thingsboard.server.dao.exception.DataValidationException;
49 49 import org.thingsboard.server.dao.settings.AdminSettingsService;
50 50 import org.thingsboard.server.dao.user.UserService;
51 51 import org.thingsboard.server.dao.user.UserServiceImpl;
  52 +import org.thingsboard.common.util.JacksonUtil;
52 53 import org.thingsboard.server.service.security.exception.UserPasswordExpiredException;
53 54 import org.thingsboard.server.utils.MiscUtils;
54 55
... ... @@ -65,8 +66,6 @@ import static org.thingsboard.server.common.data.CacheConstants.SECURITY_SETTING
65 66 @Slf4j
66 67 public class DefaultSystemSecurityService implements SystemSecurityService {
67 68
68   - private static final ObjectMapper objectMapper = new ObjectMapper();
69   -
70 69 @Autowired
71 70 private AdminSettingsService adminSettingsService;
72 71
... ... @@ -89,7 +88,7 @@ public class DefaultSystemSecurityService implements SystemSecurityService {
89 88 AdminSettings adminSettings = adminSettingsService.findAdminSettingsByKey(tenantId, "securitySettings");
90 89 if (adminSettings != null) {
91 90 try {
92   - securitySettings = objectMapper.treeToValue(adminSettings.getJsonValue(), SecuritySettings.class);
  91 + securitySettings = JacksonUtil.convertValue(adminSettings.getJsonValue(), SecuritySettings.class);
93 92 } catch (Exception e) {
94 93 throw new RuntimeException("Failed to load security settings!", e);
95 94 }
... ... @@ -109,10 +108,10 @@ public class DefaultSystemSecurityService implements SystemSecurityService {
109 108 adminSettings = new AdminSettings();
110 109 adminSettings.setKey("securitySettings");
111 110 }
112   - adminSettings.setJsonValue(objectMapper.valueToTree(securitySettings));
  111 + adminSettings.setJsonValue(JacksonUtil.valueToTree(securitySettings));
113 112 AdminSettings savedAdminSettings = adminSettingsService.saveAdminSettings(tenantId, adminSettings);
114 113 try {
115   - return objectMapper.treeToValue(savedAdminSettings.getJsonValue(), SecuritySettings.class);
  114 + return JacksonUtil.convertValue(savedAdminSettings.getJsonValue(), SecuritySettings.class);
116 115 } catch (Exception e) {
117 116 throw new RuntimeException("Failed to load security settings!", e);
118 117 }
... ... @@ -189,7 +188,7 @@ public class DefaultSystemSecurityService implements SystemSecurityService {
189 188 JsonNode additionalInfo = user.getAdditionalInfo();
190 189 if (additionalInfo instanceof ObjectNode && additionalInfo.has(UserServiceImpl.USER_PASSWORD_HISTORY)) {
191 190 JsonNode userPasswordHistoryJson = additionalInfo.get(UserServiceImpl.USER_PASSWORD_HISTORY);
192   - Map<String, String> userPasswordHistoryMap = objectMapper.convertValue(userPasswordHistoryJson, Map.class);
  191 + Map<String, String> userPasswordHistoryMap = JacksonUtil.convertValue(userPasswordHistoryJson, new TypeReference<>() {});
193 192 for (Map.Entry<String, String> entry : userPasswordHistoryMap.entrySet()) {
194 193 if (encoder.matches(password, entry.getValue()) && Long.parseLong(entry.getKey()) > passwordReuseFrequencyTs) {
195 194 throw new DataValidationException("Password was already used for the last " + passwordPolicy.getPasswordReuseFrequencyDays() + " days");
... ...
... ... @@ -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;
... ...
... ... @@ -31,7 +31,7 @@ import org.thingsboard.server.common.data.exception.ThingsboardException;
31 31 import org.thingsboard.server.common.data.id.EntityId;
32 32 import org.thingsboard.server.common.data.id.TenantId;
33 33 import org.thingsboard.server.dao.settings.AdminSettingsService;
34   -import org.thingsboard.server.dao.util.mapping.JacksonUtil;
  34 +import org.thingsboard.common.util.JacksonUtil;
35 35 import org.thingsboard.server.queue.usagestats.TbApiUsageClient;
36 36 import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
37 37
... ...
... ... @@ -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
... ...
... ... @@ -52,13 +52,15 @@ import org.thingsboard.server.dao.attributes.AttributesService;
52 52 import org.thingsboard.server.dao.device.DeviceService;
53 53 import org.thingsboard.server.dao.tenant.TenantService;
54 54 import org.thingsboard.server.dao.timeseries.TimeseriesService;
55   -import org.thingsboard.server.dao.util.mapping.JacksonUtil;
  55 +import org.thingsboard.common.util.JacksonUtil;
56 56 import org.thingsboard.server.gen.transport.TransportProtos;
57 57 import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
58 58 import org.thingsboard.server.queue.discovery.PartitionService;
  59 +import org.thingsboard.server.queue.discovery.TbApplicationEventListener;
59 60 import org.thingsboard.server.queue.util.TbCoreComponent;
60 61 import org.thingsboard.server.service.queue.TbClusterService;
61 62 import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
  63 +import org.thingsboard.server.utils.EventDeduplicationExecutor;
62 64
63 65 import javax.annotation.Nullable;
64 66 import javax.annotation.PostConstruct;
... ... @@ -89,7 +91,7 @@ import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE;
89 91 @Service
90 92 @TbCoreComponent
91 93 @Slf4j
92   -public class DefaultDeviceStateService implements DeviceStateService {
  94 +public class DefaultDeviceStateService extends TbApplicationEventListener<PartitionChangeEvent> implements DeviceStateService {
93 95
94 96 public static final String ACTIVITY_STATE = "active";
95 97 public static final String LAST_CONNECT_TIME = "lastConnectTime";
... ... @@ -126,13 +128,13 @@ public class DefaultDeviceStateService implements DeviceStateService {
126 128 @Getter
127 129 private int initFetchPackSize;
128 130
129   - private volatile boolean clusterUpdatePending = false;
130   -
131 131 private ListeningScheduledExecutorService queueExecutor;
132 132 private final ConcurrentMap<TopicPartitionInfo, Set<DeviceId>> partitionedDevices = new ConcurrentHashMap<>();
133 133 private final ConcurrentMap<DeviceId, DeviceStateData> deviceStates = new ConcurrentHashMap<>();
134 134 private final ConcurrentMap<DeviceId, Long> deviceLastReportedActivity = new ConcurrentHashMap<>();
135 135 private final ConcurrentMap<DeviceId, Long> deviceLastSavedActivity = new ConcurrentHashMap<>();
  136 + private volatile EventDeduplicationExecutor<Set<TopicPartitionInfo>> deduplicationExecutor;
  137 +
136 138
137 139 public DefaultDeviceStateService(TenantService tenantService, DeviceService deviceService,
138 140 AttributesService attributesService, TimeseriesService tsService,
... ... @@ -155,6 +157,7 @@ public class DefaultDeviceStateService implements DeviceStateService {
155 157 // Should be always single threaded due to absence of locks.
156 158 queueExecutor = MoreExecutors.listeningDecorator(Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("device-state")));
157 159 queueExecutor.scheduleAtFixedRate(this::updateState, new Random().nextInt(defaultStateCheckIntervalInSec), defaultStateCheckIntervalInSec, TimeUnit.SECONDS);
  160 + deduplicationExecutor = new EventDeduplicationExecutor<>(DefaultDeviceStateService.class.getSimpleName(), queueExecutor, this::initStateFromDB);
158 161 }
159 162
160 163 @PreDestroy
... ... @@ -204,7 +207,6 @@ public class DefaultDeviceStateService implements DeviceStateService {
204 207 if (!state.isActive()) {
205 208 state.setActive(true);
206 209 save(deviceId, ACTIVITY_STATE, state.isActive());
207   - stateData.getMetaData().putValue("scope", SERVER_SCOPE);
208 210 pushRuleEngineMessage(stateData, ACTIVITY_EVENT);
209 211 }
210 212 }
... ... @@ -292,25 +294,14 @@ public class DefaultDeviceStateService implements DeviceStateService {
292 294 }
293 295 }
294 296
295   - volatile Set<TopicPartitionInfo> pendingPartitions;
296   -
297 297 @Override
298   - public void onApplicationEvent(PartitionChangeEvent partitionChangeEvent) {
  298 + protected void onTbApplicationEvent(PartitionChangeEvent partitionChangeEvent) {
299 299 if (ServiceType.TB_CORE.equals(partitionChangeEvent.getServiceType())) {
300   - synchronized (this) {
301   - pendingPartitions = partitionChangeEvent.getPartitions();
302   - if (!clusterUpdatePending) {
303   - clusterUpdatePending = true;
304   - queueExecutor.submit(() -> {
305   - clusterUpdatePending = false;
306   - initStateFromDB();
307   - });
308   - }
309   - }
  300 + deduplicationExecutor.submit(partitionChangeEvent.getPartitions());
310 301 }
311 302 }
312 303
313   - private void initStateFromDB() {
  304 + private void initStateFromDB(Set<TopicPartitionInfo> pendingPartitions) {
314 305 try {
315 306 log.info("CURRENT PARTITIONS: {}", partitionedDevices.keySet());
316 307 log.info("NEW PARTITIONS: {}", pendingPartitions);
... ... @@ -456,7 +447,7 @@ public class DefaultDeviceStateService implements DeviceStateService {
456 447 }
457 448
458 449 private <T extends KvEntry> Function<List<T>, DeviceStateData> extractDeviceStateData(Device device) {
459   - return new Function<List<T>, DeviceStateData>() {
  450 + return new Function<>() {
460 451 @Nullable
461 452 @Override
462 453 public DeviceStateData apply(@Nullable List<T> data) {
... ... @@ -512,7 +503,11 @@ public class DefaultDeviceStateService implements DeviceStateService {
512 503 } else {
513 504 data = JacksonUtil.toString(state);
514 505 }
515   - TbMsg tbMsg = TbMsg.newMsg(msgType, stateData.getDeviceId(), stateData.getMetaData().copy(), TbMsgDataType.JSON, data);
  506 + TbMsgMetaData md = stateData.getMetaData().copy();
  507 + if(!persistToTelemetry){
  508 + md.putValue(DataConstants.SCOPE, SERVER_SCOPE);
  509 + }
  510 + TbMsg tbMsg = TbMsg.newMsg(msgType, stateData.getDeviceId(), md, TbMsgDataType.JSON, data);
516 511 clusterService.pushMsgToRuleEngine(stateData.getTenantId(), stateData.getDeviceId(), tbMsg, null);
517 512 } catch (Exception e) {
518 513 log.warn("[{}] Failed to push inactivity alarm: {}", stateData.getDeviceId(), state, e);
... ...
... ... @@ -39,7 +39,7 @@ import org.thingsboard.server.common.msg.queue.TbCallback;
39 39 import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
40 40 import org.thingsboard.server.dao.attributes.AttributesService;
41 41 import org.thingsboard.server.dao.timeseries.TimeseriesService;
42   -import org.thingsboard.server.dao.util.mapping.JacksonUtil;
  42 +import org.thingsboard.common.util.JacksonUtil;
43 43 import org.thingsboard.server.gen.transport.TransportProtos.*;
44 44 import org.thingsboard.server.gen.transport.TransportProtos.LocalSubscriptionServiceMsgProto;
45 45 import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionUpdateProto;
... ... @@ -48,6 +48,7 @@ import org.thingsboard.server.queue.TbQueueProducer;
48 48 import org.thingsboard.server.queue.common.TbProtoQueueMsg;
49 49 import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
50 50 import org.thingsboard.server.queue.discovery.PartitionService;
  51 +import org.thingsboard.server.queue.discovery.TbApplicationEventListener;
51 52 import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
52 53 import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
53 54 import org.thingsboard.server.queue.util.TbCoreComponent;
... ... @@ -76,7 +77,7 @@ import java.util.function.Predicate;
76 77 @Slf4j
77 78 @TbCoreComponent
78 79 @Service
79   -public class DefaultSubscriptionManagerService implements SubscriptionManagerService {
  80 +public class DefaultSubscriptionManagerService extends TbApplicationEventListener<PartitionChangeEvent> implements SubscriptionManagerService {
80 81
81 82 @Autowired
82 83 private AttributesService attrService;
... ... @@ -178,7 +179,7 @@ public class DefaultSubscriptionManagerService implements SubscriptionManagerSer
178 179 }
179 180
180 181 @Override
181   - public void onApplicationEvent(PartitionChangeEvent partitionChangeEvent) {
  182 + protected void onTbApplicationEvent(PartitionChangeEvent partitionChangeEvent) {
182 183 if (ServiceType.TB_CORE.equals(partitionChangeEvent.getServiceType())) {
183 184 Set<TopicPartitionInfo> removedPartitions = new HashSet<>(currentPartitions);
184 185 removedPartitions.removeAll(partitionChangeEvent.getPartitions());
... ...
... ... @@ -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,16 +320,30 @@ 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   - ctx.setAndResolveQuery(cmd.getQuery());
  326 + if (cmd.getQuery() != null) {
  327 + ctx.setAndResolveQuery(cmd.getQuery());
  328 + }
  329 + sessionSubs.put(cmd.getCmdId(), ctx);
  330 + return ctx;
  331 + }
  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 + }
306 340 sessionSubs.put(cmd.getCmdId(), ctx);
307 341 return ctx;
308 342 }
309 343
  344 +
310 345 private TbAlarmDataSubCtx createSubCtx(TelemetryWebSocketSessionRef sessionRef, AlarmDataCmd cmd) {
311   - Map<Integer, TbAbstractDataSubCtx> sessionSubs = subscriptionsBySessionId.computeIfAbsent(sessionRef.getSessionId(), k -> new HashMap<>());
  346 + Map<Integer, TbAbstractSubCtx> sessionSubs = subscriptionsBySessionId.computeIfAbsent(sessionRef.getSessionId(), k -> new HashMap<>());
312 347 TbAlarmDataSubCtx ctx = new TbAlarmDataSubCtx(serviceId, wsService, entityService, localSubscriptionService,
313 348 attributesService, stats, alarmService, sessionRef, cmd.getCmdId(), maxEntitiesPerAlarmSubscription);
314 349 ctx.setAndResolveQuery(cmd.getQuery());
... ... @@ -316,8 +351,9 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
316 351 return ctx;
317 352 }
318 353
319   - private <T extends TbAbstractDataSubCtx> T getSubCtx(String sessionId, int cmdId) {
320   - Map<Integer, TbAbstractDataSubCtx> sessionSubs = subscriptionsBySessionId.get(sessionId);
  354 + @SuppressWarnings("unchecked")
  355 + private <T extends TbAbstractSubCtx> T getSubCtx(String sessionId, int cmdId) {
  356 + Map<Integer, TbAbstractSubCtx> sessionSubs = subscriptionsBySessionId.get(sessionId);
321 357 if (sessionSubs != null) {
322 358 return (T) sessionSubs.get(cmdId);
323 359 } else {
... ... @@ -461,19 +497,18 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
461 497 cleanupAndCancel(getSubCtx(sessionId, cmd.getCmdId()));
462 498 }
463 499
464   - private void cleanupAndCancel(TbAbstractDataSubCtx ctx) {
  500 + private void cleanupAndCancel(TbAbstractSubCtx ctx) {
465 501 if (ctx != null) {
466 502 ctx.cancelTasks();
467   - ctx.clearEntitySubscriptions();
468   - ctx.clearDynamicValueSubscriptions();
  503 + ctx.clearSubscriptions();
469 504 }
470 505 }
471 506
472 507 @Override
473 508 public void cancelAllSessionSubscriptions(String sessionId) {
474   - Map<Integer, TbAbstractDataSubCtx> sessionSubs = subscriptionsBySessionId.remove(sessionId);
  509 + Map<Integer, TbAbstractSubCtx> sessionSubs = subscriptionsBySessionId.remove(sessionId);
475 510 if (sessionSubs != null) {
476   - sessionSubs.values().stream().filter(sub -> sub instanceof TbEntityDataSubCtx).map(sub -> (TbEntityDataSubCtx) sub).forEach(this::cleanupAndCancel);
  511 + sessionSubs.values().forEach(this::cleanupAndCancel);
477 512 }
478 513 }
479 514
... ...
... ... @@ -28,6 +28,7 @@ import org.thingsboard.server.queue.discovery.PartitionService;
28 28 import org.thingsboard.server.common.msg.queue.ServiceType;
29 29 import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
30 30 import org.thingsboard.server.common.msg.queue.TbCallback;
  31 +import org.thingsboard.server.queue.discovery.TbApplicationEventListener;
31 32 import org.thingsboard.server.queue.util.TbCoreComponent;
32 33 import org.thingsboard.server.service.queue.TbClusterService;
33 34 import org.thingsboard.server.service.telemetry.sub.AlarmSubscriptionUpdate;
... ... @@ -62,6 +63,34 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer
62 63 private SubscriptionManagerService subscriptionManagerService;
63 64
64 65 private ExecutorService subscriptionUpdateExecutor;
  66 +
  67 + private TbApplicationEventListener<PartitionChangeEvent> partitionChangeListener = new TbApplicationEventListener<>() {
  68 + @Override
  69 + protected void onTbApplicationEvent(PartitionChangeEvent event) {
  70 + if (ServiceType.TB_CORE.equals(event.getServiceType())) {
  71 + currentPartitions.clear();
  72 + currentPartitions.addAll(event.getPartitions());
  73 + }
  74 + }
  75 + };
  76 +
  77 + private TbApplicationEventListener<ClusterTopologyChangeEvent> clusterTopologyChangeListener = new TbApplicationEventListener<>() {
  78 + @Override
  79 + protected void onTbApplicationEvent(ClusterTopologyChangeEvent event) {
  80 + if (event.getServiceQueueKeys().stream().anyMatch(key -> ServiceType.TB_CORE.equals(key.getServiceType()))) {
  81 + /*
  82 + * If the cluster topology has changed, we need to push all current subscriptions to SubscriptionManagerService again.
  83 + * Otherwise, the SubscriptionManagerService may "forget" those subscriptions in case of restart.
  84 + * Although this is resource consuming operation, it is cheaper than sending ping/pong commands periodically
  85 + * It is also cheaper then caching the subscriptions by entity id and then lookup of those caches every time we have new telemetry in SubscriptionManagerService.
  86 + * Even if we cache locally the list of active subscriptions by entity id, it is still time consuming operation to get them from cache
  87 + * Since number of subscriptions is usually much less then number of devices that are pushing data.
  88 + */
  89 + subscriptionsBySessionId.values().forEach(map -> map.values()
  90 + .forEach(sub -> pushSubscriptionToManagerService(sub, true)));
  91 + }
  92 + }
  93 + };
65 94
66 95 @PostConstruct
67 96 public void initExecutor() {
... ... @@ -77,28 +106,14 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer
77 106
78 107 @Override
79 108 @EventListener(PartitionChangeEvent.class)
80   - public void onApplicationEvent(PartitionChangeEvent partitionChangeEvent) {
81   - if (ServiceType.TB_CORE.equals(partitionChangeEvent.getServiceType())) {
82   - currentPartitions.clear();
83   - currentPartitions.addAll(partitionChangeEvent.getPartitions());
84   - }
  109 + public void onApplicationEvent(PartitionChangeEvent event) {
  110 + partitionChangeListener.onApplicationEvent(event);
85 111 }
86 112
87 113 @Override
88 114 @EventListener(ClusterTopologyChangeEvent.class)
89 115 public void onApplicationEvent(ClusterTopologyChangeEvent event) {
90   - if (event.getServiceQueueKeys().stream().anyMatch(key -> ServiceType.TB_CORE.equals(key.getServiceType()))) {
91   - /*
92   - * If the cluster topology has changed, we need to push all current subscriptions to SubscriptionManagerService again.
93   - * Otherwise, the SubscriptionManagerService may "forget" those subscriptions in case of restart.
94   - * Although this is resource consuming operation, it is cheaper than sending ping/pong commands periodically
95   - * It is also cheaper then caching the subscriptions by entity id and then lookup of those caches every time we have new telemetry in SubscriptionManagerService.
96   - * Even if we cache locally the list of active subscriptions by entity id, it is still time consuming operation to get them from cache
97   - * Since number of subscriptions is usually much less then number of devices that are pushing data.
98   - */
99   - subscriptionsBySessionId.values().forEach(map -> map.values()
100   - .forEach(sub -> pushSubscriptionToManagerService(sub, true)));
101   - }
  116 + clusterTopologyChangeListener.onApplicationEvent(event);
102 117 }
103 118
104 119 //TODO 3.1: replace null callbacks with callbacks from websocket service.
... ... @@ -123,6 +138,7 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer
123 138 }
124 139
125 140 @Override
  141 + @SuppressWarnings("unchecked")
126 142 public void onSubscriptionUpdate(String sessionId, TelemetrySubscriptionUpdate update, TbCallback callback) {
127 143 TbSubscription subscription = subscriptionsBySessionId
128 144 .getOrDefault(sessionId, Collections.emptyMap()).get(update.getSubscriptionId());
... ... @@ -143,6 +159,7 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer
143 159 }
144 160
145 161 @Override
  162 + @SuppressWarnings("unchecked")
146 163 public void onSubscriptionUpdate(String sessionId, AlarmSubscriptionUpdate update, TbCallback callback) {
147 164 TbSubscription subscription = subscriptionsBySessionId
148 165 .getOrDefault(sessionId, Collections.emptyMap()).get(update.getSubscriptionId());
... ...
... ... @@ -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.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,102 +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   - private void updateDynamicValuesByKey(DynamicValueKeySub sub, TsValue tsValue) {
268   - DynamicValueKey dvk = sub.getKey();
269   - switch (dvk.getPredicateType()) {
270   - case STRING:
271   - dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(tsValue.getValue()));
272   - break;
273   - case NUMERIC:
274   - try {
275   - Double dValue = Double.parseDouble(tsValue.getValue());
276   - dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(dValue));
277   - } catch (NumberFormatException e) {
278   - dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(null));
279   - }
280   - break;
281   - case BOOLEAN:
282   - Boolean bValue = Boolean.parseBoolean(tsValue.getValue());
283   - dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(bValue));
284   - break;
285   - }
286   - }
287   -
288   - private void registerDynamicValues(KeyFilterPredicate predicate) {
289   - switch (predicate.getType()) {
290   - case STRING:
291   - case NUMERIC:
292   - case BOOLEAN:
293   - Optional<DynamicValue> value = getDynamicValueFromSimplePredicate((SimpleKeyFilterPredicate) predicate);
294   - if (value.isPresent()) {
295   - DynamicValue dynamicValue = value.get();
296   - DynamicValueKey key = new DynamicValueKey(
297   - predicate.getType(),
298   - dynamicValue.getSourceType(),
299   - dynamicValue.getSourceAttribute());
300   - dynamicValues.computeIfAbsent(key, tmp -> new ArrayList<>()).add(dynamicValue);
301   - }
302   - break;
303   - case COMPLEX:
304   - ((ComplexFilterPredicate) predicate).getPredicates().forEach(this::registerDynamicValues);
305   - }
306   - }
307   -
308   - private Optional<DynamicValue<T>> getDynamicValueFromSimplePredicate(SimpleKeyFilterPredicate<T> predicate) {
309   - if (predicate.getValue().getUserValue() == null) {
310   - return Optional.ofNullable(predicate.getValue().getDynamicValue());
311   - } else {
312   - return Optional.empty();
313   - }
314   - }
315   -
316   - public String getSessionId() {
317   - return sessionRef.getSessionId();
318   - }
319   -
320   - public TenantId getTenantId() {
321   - return sessionRef.getSecurityCtx().getTenantId();
322   - }
323   -
324   - public CustomerId getCustomerId() {
325   - return sessionRef.getSecurityCtx().getCustomerId();
326   - }
327   -
328   - public UserId getUserId() {
329   - return sessionRef.getSecurityCtx().getId();
  103 + @Override
  104 + public void clearSubscriptions() {
  105 + clearEntitySubscriptions();
  106 + super.clearSubscriptions();
330 107 }
331 108
332 109 public void clearEntitySubscriptions() {
... ... @@ -338,26 +115,6 @@ public abstract class TbAbstractDataSubCtx<T extends AbstractDataQuery<? extends
338 115 }
339 116 }
340 117
341   - public void clearDynamicValueSubscriptions() {
342   - if (subToDynamicValueKeySet != null) {
343   - for (Integer subId : subToDynamicValueKeySet) {
344   - localSubscriptionService.cancelSubscription(sessionRef.getSessionId(), subId);
345   - }
346   - subToDynamicValueKeySet.clear();
347   - }
348   - }
349   -
350   - public void setRefreshTask(ScheduledFuture<?> task) {
351   - this.refreshTask = task;
352   - }
353   -
354   - public void cancelTasks() {
355   - if (this.refreshTask != null) {
356   - log.trace("[{}][{}] Canceling old refresh task", sessionRef.getSessionId(), cmdId);
357   - this.refreshTask.cancel(true);
358   - }
359   - }
360   -
361 118 public void createSubscriptions(List<EntityKey> keys, boolean resultToLatestValues) {
362 119 Map<EntityKeyType, List<EntityKey>> keysByType = getEntityKeyByTypeMap(keys);
363 120 for (EntityData entityData : data.getData()) {
... ... @@ -457,14 +214,4 @@ public abstract class TbAbstractDataSubCtx<T extends AbstractDataQuery<? extends
457 214
458 215 abstract void sendWsMsg(String sessionId, TelemetrySubscriptionUpdate subscriptionUpdate, EntityKeyType keyType, boolean resultToLatestValues);
459 216
460   - @Data
461   - private static class DynamicValueKey {
462   - @Getter
463   - private final FilterPredicateType predicateType;
464   - @Getter
465   - private final DynamicValueSourceType sourceType;
466   - @Getter
467   - private final String sourceAttribute;
468   - }
469   -
470 217 }
... ...
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.service.subscription;
  17 +
  18 +import com.google.common.util.concurrent.Futures;
  19 +import com.google.common.util.concurrent.ListenableFuture;
  20 +import com.google.common.util.concurrent.MoreExecutors;
  21 +import lombok.Data;
  22 +import lombok.Getter;
  23 +import lombok.Setter;
  24 +import lombok.extern.slf4j.Slf4j;
  25 +import org.thingsboard.server.common.data.id.CustomerId;
  26 +import org.thingsboard.server.common.data.id.EntityId;
  27 +import org.thingsboard.server.common.data.id.TenantId;
  28 +import org.thingsboard.server.common.data.id.UserId;
  29 +import org.thingsboard.server.common.data.kv.AttributeKvEntry;
  30 +import org.thingsboard.server.common.data.query.ComplexFilterPredicate;
  31 +import org.thingsboard.server.common.data.query.DynamicValue;
  32 +import org.thingsboard.server.common.data.query.DynamicValueSourceType;
  33 +import org.thingsboard.server.common.data.query.EntityCountQuery;
  34 +import org.thingsboard.server.common.data.query.EntityKeyType;
  35 +import org.thingsboard.server.common.data.query.FilterPredicateType;
  36 +import org.thingsboard.server.common.data.query.KeyFilter;
  37 +import org.thingsboard.server.common.data.query.KeyFilterPredicate;
  38 +import org.thingsboard.server.common.data.query.SimpleKeyFilterPredicate;
  39 +import org.thingsboard.server.common.data.query.TsValue;
  40 +import org.thingsboard.server.dao.attributes.AttributesService;
  41 +import org.thingsboard.server.dao.entity.EntityService;
  42 +import org.thingsboard.server.service.telemetry.TelemetryWebSocketService;
  43 +import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef;
  44 +import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate;
  45 +
  46 +import java.util.ArrayList;
  47 +import java.util.HashMap;
  48 +import java.util.List;
  49 +import java.util.Map;
  50 +import java.util.Optional;
  51 +import java.util.Set;
  52 +import java.util.concurrent.ConcurrentHashMap;
  53 +import java.util.concurrent.ExecutionException;
  54 +import java.util.concurrent.ScheduledFuture;
  55 +
  56 +@Slf4j
  57 +@Data
  58 +public abstract class TbAbstractSubCtx<T extends EntityCountQuery> {
  59 +
  60 + protected final String serviceId;
  61 + protected final SubscriptionServiceStatistics stats;
  62 + protected final TelemetryWebSocketService wsService;
  63 + protected final EntityService entityService;
  64 + protected final TbLocalSubscriptionService localSubscriptionService;
  65 + protected final AttributesService attributesService;
  66 + protected final TelemetryWebSocketSessionRef sessionRef;
  67 + protected final int cmdId;
  68 + protected final Set<Integer> subToDynamicValueKeySet;
  69 + @Getter
  70 + protected final Map<DynamicValueKey, List<DynamicValue>> dynamicValues;
  71 + @Getter
  72 + @Setter
  73 + protected T query;
  74 + @Setter
  75 + protected volatile ScheduledFuture<?> refreshTask;
  76 +
  77 + public TbAbstractSubCtx(String serviceId, TelemetryWebSocketService wsService,
  78 + EntityService entityService, TbLocalSubscriptionService localSubscriptionService,
  79 + AttributesService attributesService, SubscriptionServiceStatistics stats,
  80 + TelemetryWebSocketSessionRef sessionRef, int cmdId) {
  81 + this.serviceId = serviceId;
  82 + this.wsService = wsService;
  83 + this.entityService = entityService;
  84 + this.localSubscriptionService = localSubscriptionService;
  85 + this.attributesService = attributesService;
  86 + this.stats = stats;
  87 + this.sessionRef = sessionRef;
  88 + this.cmdId = cmdId;
  89 + this.subToDynamicValueKeySet = ConcurrentHashMap.newKeySet();
  90 + this.dynamicValues = new ConcurrentHashMap<>();
  91 + }
  92 +
  93 + public void setAndResolveQuery(T query) {
  94 + dynamicValues.clear();
  95 + this.query = query;
  96 + if (query != null && query.getKeyFilters() != null) {
  97 + for (KeyFilter filter : query.getKeyFilters()) {
  98 + registerDynamicValues(filter.getPredicate());
  99 + }
  100 + }
  101 + resolve(getTenantId(), getCustomerId(), getUserId());
  102 + }
  103 +
  104 + public void resolve(TenantId tenantId, CustomerId customerId, UserId userId) {
  105 + List<ListenableFuture<DynamicValueKeySub>> futures = new ArrayList<>();
  106 + for (DynamicValueKey key : dynamicValues.keySet()) {
  107 + switch (key.getSourceType()) {
  108 + case CURRENT_TENANT:
  109 + futures.add(resolveEntityValue(tenantId, tenantId, key));
  110 + break;
  111 + case CURRENT_CUSTOMER:
  112 + if (customerId != null && !customerId.isNullUid()) {
  113 + futures.add(resolveEntityValue(tenantId, customerId, key));
  114 + }
  115 + break;
  116 + case CURRENT_USER:
  117 + if (userId != null && !userId.isNullUid()) {
  118 + futures.add(resolveEntityValue(tenantId, userId, key));
  119 + }
  120 + break;
  121 + }
  122 + }
  123 + try {
  124 + Map<EntityId, Map<String, DynamicValueKeySub>> tmpSubMap = new HashMap<>();
  125 + for (DynamicValueKeySub sub : Futures.successfulAsList(futures).get()) {
  126 + tmpSubMap.computeIfAbsent(sub.getEntityId(), tmp -> new HashMap<>()).put(sub.getKey().getSourceAttribute(), sub);
  127 + }
  128 + for (EntityId entityId : tmpSubMap.keySet()) {
  129 + Map<String, Long> keyStates = new HashMap<>();
  130 + Map<String, DynamicValueKeySub> dynamicValueKeySubMap = tmpSubMap.get(entityId);
  131 + dynamicValueKeySubMap.forEach((k, v) -> keyStates.put(k, v.getLastUpdateTs()));
  132 + int subIdx = sessionRef.getSessionSubIdSeq().incrementAndGet();
  133 + TbAttributeSubscription sub = TbAttributeSubscription.builder()
  134 + .serviceId(serviceId)
  135 + .sessionId(sessionRef.getSessionId())
  136 + .subscriptionId(subIdx)
  137 + .tenantId(sessionRef.getSecurityCtx().getTenantId())
  138 + .entityId(entityId)
  139 + .updateConsumer((s, subscriptionUpdate) -> dynamicValueSubUpdate(s, subscriptionUpdate, dynamicValueKeySubMap))
  140 + .allKeys(false)
  141 + .keyStates(keyStates)
  142 + .scope(TbAttributeSubscriptionScope.SERVER_SCOPE)
  143 + .build();
  144 + subToDynamicValueKeySet.add(subIdx);
  145 + localSubscriptionService.addSubscription(sub);
  146 + }
  147 + } catch (InterruptedException | ExecutionException e) {
  148 + log.info("[{}][{}][{}] Failed to resolve dynamic values: {}", tenantId, customerId, userId, dynamicValues.keySet());
  149 + }
  150 +
  151 + }
  152 +
  153 + private void dynamicValueSubUpdate(String sessionId, TelemetrySubscriptionUpdate subscriptionUpdate,
  154 + Map<String, DynamicValueKeySub> dynamicValueKeySubMap) {
  155 + Map<String, TsValue> latestUpdate = new HashMap<>();
  156 + subscriptionUpdate.getData().forEach((k, v) -> {
  157 + Object[] data = (Object[]) v.get(0);
  158 + latestUpdate.put(k, new TsValue((Long) data[0], (String) data[1]));
  159 + });
  160 +
  161 + boolean invalidateFilter = false;
  162 + for (Map.Entry<String, TsValue> entry : latestUpdate.entrySet()) {
  163 + String k = entry.getKey();
  164 + TsValue tsValue = entry.getValue();
  165 + DynamicValueKeySub sub = dynamicValueKeySubMap.get(k);
  166 + if (sub.updateValue(tsValue)) {
  167 + invalidateFilter = true;
  168 + updateDynamicValuesByKey(sub, tsValue);
  169 + }
  170 + }
  171 +
  172 + if (invalidateFilter) {
  173 + update();
  174 + }
  175 + }
  176 +
  177 + public abstract void fetchData();
  178 +
  179 + protected abstract void update();
  180 +
  181 + public void clearSubscriptions() {
  182 + clearDynamicValueSubscriptions();
  183 + }
  184 +
  185 + @Data
  186 + private static class DynamicValueKeySub {
  187 + private final DynamicValueKey key;
  188 + private final EntityId entityId;
  189 + private long lastUpdateTs;
  190 + private String lastUpdateValue;
  191 +
  192 + boolean updateValue(TsValue value) {
  193 + if (value.getTs() > lastUpdateTs && (lastUpdateValue == null || !lastUpdateValue.equals(value.getValue()))) {
  194 + this.lastUpdateTs = value.getTs();
  195 + this.lastUpdateValue = value.getValue();
  196 + return true;
  197 + } else {
  198 + return false;
  199 + }
  200 + }
  201 + }
  202 +
  203 + private ListenableFuture<DynamicValueKeySub> resolveEntityValue(TenantId tenantId, EntityId entityId, DynamicValueKey key) {
  204 + ListenableFuture<Optional<AttributeKvEntry>> entry = attributesService.find(tenantId, entityId,
  205 + TbAttributeSubscriptionScope.SERVER_SCOPE.name(), key.getSourceAttribute());
  206 + return Futures.transform(entry, attributeOpt -> {
  207 + DynamicValueKeySub sub = new DynamicValueKeySub(key, entityId);
  208 + if (attributeOpt.isPresent()) {
  209 + AttributeKvEntry attribute = attributeOpt.get();
  210 + sub.setLastUpdateTs(attribute.getLastUpdateTs());
  211 + sub.setLastUpdateValue(attribute.getValueAsString());
  212 + updateDynamicValuesByKey(sub, new TsValue(attribute.getLastUpdateTs(), attribute.getValueAsString()));
  213 + }
  214 + return sub;
  215 + }, MoreExecutors.directExecutor());
  216 + }
  217 +
  218 + @SuppressWarnings("unchecked")
  219 + protected void updateDynamicValuesByKey(DynamicValueKeySub sub, TsValue tsValue) {
  220 + DynamicValueKey dvk = sub.getKey();
  221 + switch (dvk.getPredicateType()) {
  222 + case STRING:
  223 + dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(tsValue.getValue()));
  224 + break;
  225 + case NUMERIC:
  226 + try {
  227 + Double dValue = Double.parseDouble(tsValue.getValue());
  228 + dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(dValue));
  229 + } catch (NumberFormatException e) {
  230 + dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(null));
  231 + }
  232 + break;
  233 + case BOOLEAN:
  234 + Boolean bValue = Boolean.parseBoolean(tsValue.getValue());
  235 + dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(bValue));
  236 + break;
  237 + }
  238 + }
  239 +
  240 + @SuppressWarnings("unchecked")
  241 + private void registerDynamicValues(KeyFilterPredicate predicate) {
  242 + switch (predicate.getType()) {
  243 + case STRING:
  244 + case NUMERIC:
  245 + case BOOLEAN:
  246 + Optional<DynamicValue> value = getDynamicValueFromSimplePredicate((SimpleKeyFilterPredicate) predicate);
  247 + if (value.isPresent()) {
  248 + DynamicValue dynamicValue = value.get();
  249 + DynamicValueKey key = new DynamicValueKey(
  250 + predicate.getType(),
  251 + dynamicValue.getSourceType(),
  252 + dynamicValue.getSourceAttribute());
  253 + dynamicValues.computeIfAbsent(key, tmp -> new ArrayList<>()).add(dynamicValue);
  254 + }
  255 + break;
  256 + case COMPLEX:
  257 + ((ComplexFilterPredicate) predicate).getPredicates().forEach(this::registerDynamicValues);
  258 + }
  259 + }
  260 +
  261 + private Optional<DynamicValue<T>> getDynamicValueFromSimplePredicate(SimpleKeyFilterPredicate<T> predicate) {
  262 + if (predicate.getValue().getUserValue() == null) {
  263 + return Optional.ofNullable(predicate.getValue().getDynamicValue());
  264 + } else {
  265 + return Optional.empty();
  266 + }
  267 + }
  268 +
  269 + public String getSessionId() {
  270 + return sessionRef.getSessionId();
  271 + }
  272 +
  273 + public TenantId getTenantId() {
  274 + return sessionRef.getSecurityCtx().getTenantId();
  275 + }
  276 +
  277 + public CustomerId getCustomerId() {
  278 + return sessionRef.getSecurityCtx().getCustomerId();
  279 + }
  280 +
  281 + public UserId getUserId() {
  282 + return sessionRef.getSecurityCtx().getId();
  283 + }
  284 +
  285 + protected void clearDynamicValueSubscriptions() {
  286 + if (subToDynamicValueKeySet != null) {
  287 + for (Integer subId : subToDynamicValueKeySet) {
  288 + localSubscriptionService.cancelSubscription(sessionRef.getSessionId(), subId);
  289 + }
  290 + subToDynamicValueKeySet.clear();
  291 + }
  292 + }
  293 +
  294 + public void setRefreshTask(ScheduledFuture<?> task) {
  295 + this.refreshTask = task;
  296 + }
  297 +
  298 + public void cancelTasks() {
  299 + if (this.refreshTask != null) {
  300 + log.trace("[{}][{}] Canceling old refresh task", sessionRef.getSessionId(), cmdId);
  301 + this.refreshTask.cancel(true);
  302 + }
  303 + }
  304 +
  305 + @Data
  306 + public static class DynamicValueKey {
  307 + @Getter
  308 + private final FilterPredicateType predicateType;
  309 + @Getter
  310 + private final DynamicValueSourceType sourceType;
  311 + @Getter
  312 + private final String sourceAttribute;
  313 + }
  314 +
  315 +}
... ...
... ... @@ -90,8 +90,7 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx<AlarmDataQuery> {
90 90 AlarmDataUpdate update;
91 91 if (!entitiesMap.isEmpty()) {
92 92 long start = System.currentTimeMillis();
93   - PageData<AlarmData> alarms = alarmService.findAlarmDataByQueryForEntities(getTenantId(), getCustomerId(),
94   - query, getOrderedEntityIds());
  93 + PageData<AlarmData> alarms = alarmService.findAlarmDataByQueryForEntities(getTenantId(), getCustomerId(), query, getOrderedEntityIds());
95 94 long end = System.currentTimeMillis();
96 95 stats.getAlarmQueryInvocationCnt().incrementAndGet();
97 96 stats.getAlarmQueryTimeSpent().addAndGet(end - start);
... ...
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.service.subscription;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.thingsboard.server.common.data.query.EntityCountQuery;
  20 +import org.thingsboard.server.common.data.query.EntityKeyType;
  21 +import org.thingsboard.server.dao.attributes.AttributesService;
  22 +import org.thingsboard.server.dao.entity.EntityService;
  23 +import org.thingsboard.server.service.telemetry.TelemetryWebSocketService;
  24 +import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef;
  25 +import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountUpdate;
  26 +import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate;
  27 +import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate;
  28 +
  29 +@Slf4j
  30 +public class TbEntityCountSubCtx extends TbAbstractSubCtx<EntityCountQuery> {
  31 +
  32 + private volatile int result;
  33 +
  34 + public TbEntityCountSubCtx(String serviceId, TelemetryWebSocketService wsService, EntityService entityService,
  35 + TbLocalSubscriptionService localSubscriptionService, AttributesService attributesService,
  36 + SubscriptionServiceStatistics stats, TelemetryWebSocketSessionRef sessionRef, int cmdId) {
  37 + super(serviceId, wsService, entityService, localSubscriptionService, attributesService, stats, sessionRef, cmdId);
  38 + }
  39 +
  40 + @Override
  41 + public void fetchData() {
  42 + result = (int) entityService.countEntitiesByQuery(getTenantId(), getCustomerId(), query);
  43 + wsService.sendWsMsg(sessionRef.getSessionId(), new EntityCountUpdate(cmdId, result));
  44 + }
  45 +
  46 + @Override
  47 + protected void update() {
  48 + int newCount = (int) entityService.countEntitiesByQuery(getTenantId(), getCustomerId(), query);
  49 + if (newCount != result) {
  50 + result = newCount;
  51 + wsService.sendWsMsg(sessionRef.getSessionId(), new EntityCountUpdate(cmdId, result));
  52 + }
  53 + }
  54 +
  55 +}
... ...
... ... @@ -17,6 +17,7 @@ package org.thingsboard.server.service.subscription;
17 17
18 18 import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef;
19 19 import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataCmd;
  20 +import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountCmd;
20 21 import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd;
21 22 import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUnsubscribeCmd;
22 23 import org.thingsboard.server.service.telemetry.cmd.v2.UnsubscribeCmd;
... ... @@ -25,6 +26,8 @@ public interface TbEntityDataSubscriptionService {
25 26
26 27 void handleCmd(TelemetryWebSocketSessionRef sessionId, EntityDataCmd cmd);
27 28
  29 + void handleCmd(TelemetryWebSocketSessionRef sessionId, EntityCountCmd cmd);
  30 +
28 31 void handleCmd(TelemetryWebSocketSessionRef sessionId, AlarmDataCmd cmd);
29 32
30 33 void cancelSubscription(String sessionId, UnsubscribeCmd subscriptionId);
... ...
... ... @@ -30,7 +30,7 @@ import org.thingsboard.server.common.data.kv.KvEntry;
30 30 import org.thingsboard.server.common.data.kv.LongDataEntry;
31 31 import org.thingsboard.server.common.data.kv.StringDataEntry;
32 32 import org.thingsboard.server.common.data.kv.TsKvEntry;
33   -import org.thingsboard.server.dao.util.mapping.JacksonUtil;
  33 +import org.thingsboard.common.util.JacksonUtil;
34 34 import org.thingsboard.server.gen.transport.TransportProtos;
35 35 import org.thingsboard.server.gen.transport.TransportProtos.KeyValueProto;
36 36 import org.thingsboard.server.gen.transport.TransportProtos.KeyValueType;
... ...
... ... @@ -41,6 +41,7 @@ import org.thingsboard.server.dao.timeseries.TimeseriesService;
41 41 import org.thingsboard.server.gen.transport.TransportProtos;
42 42 import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
43 43 import org.thingsboard.server.queue.discovery.PartitionService;
  44 +import org.thingsboard.server.queue.discovery.TbApplicationEventListener;
44 45 import org.thingsboard.server.service.queue.TbClusterService;
45 46 import org.thingsboard.server.service.subscription.SubscriptionManagerService;
46 47 import org.thingsboard.server.service.subscription.TbSubscriptionUtils;
... ... @@ -61,7 +62,7 @@ import java.util.function.Consumer;
61 62 * Created by ashvayka on 27.03.18.
62 63 */
63 64 @Slf4j
64   -public abstract class AbstractSubscriptionService implements ApplicationListener<PartitionChangeEvent> {
  65 +public abstract class AbstractSubscriptionService extends TbApplicationEventListener<PartitionChangeEvent>{
65 66
66 67 protected final Set<TopicPartitionInfo> currentPartitions = ConcurrentHashMap.newKeySet();
67 68
... ... @@ -97,8 +98,7 @@ public abstract class AbstractSubscriptionService implements ApplicationListener
97 98 }
98 99
99 100 @Override
100   - @EventListener(PartitionChangeEvent.class)
101   - public void onApplicationEvent(PartitionChangeEvent partitionChangeEvent) {
  101 + protected void onTbApplicationEvent(PartitionChangeEvent partitionChangeEvent) {
102 102 if (ServiceType.TB_CORE.equals(partitionChangeEvent.getServiceType())) {
103 103 currentPartitions.clear();
104 104 currentPartitions.addAll(partitionChangeEvent.getPartitions());
... ...
... ... @@ -201,6 +201,7 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
201 201 }
202 202
203 203 @Override
  204 +
204 205 public void saveAndNotify(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, FutureCallback<Void> callback) {
205 206 saveAndNotify(tenantId, entityId, scope, attributes, true, callback);
206 207 }
... ...
... ... @@ -51,22 +51,21 @@ 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;
67   -import org.thingsboard.server.service.telemetry.cmd.v2.DataUpdate;
  66 +import org.thingsboard.server.service.telemetry.cmd.v2.CmdUpdate;
  67 +import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountCmd;
68 68 import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd;
69   -import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUnsubscribeCmd;
70 69 import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate;
71 70 import org.thingsboard.server.service.telemetry.cmd.v2.UnsubscribeCmd;
72 71 import org.thingsboard.server.service.telemetry.exception.UnauthorizedException;
... ... @@ -89,6 +88,8 @@ import java.util.concurrent.ConcurrentHashMap;
89 88 import java.util.concurrent.ConcurrentMap;
90 89 import java.util.concurrent.ExecutorService;
91 90 import java.util.concurrent.Executors;
  91 +import java.util.concurrent.ScheduledExecutorService;
  92 +import java.util.concurrent.TimeUnit;
92 93 import java.util.function.Consumer;
93 94 import java.util.stream.Collectors;
94 95
... ... @@ -151,14 +152,23 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
151 152 private ExecutorService executor;
152 153 private String serviceId;
153 154
  155 + private ScheduledExecutorService pingExecutor;
  156 +
154 157 @PostConstruct
155 158 public void initExecutor() {
156 159 serviceId = serviceInfoProvider.getServiceId();
157 160 executor = Executors.newWorkStealingPool(50);
  161 +
  162 + pingExecutor = Executors.newSingleThreadScheduledExecutor();
  163 + pingExecutor.scheduleWithFixedDelay(this::sendPing, 10000, 10000, TimeUnit.MILLISECONDS);
158 164 }
159 165
160 166 @PreDestroy
161 167 public void shutdownExecutor() {
  168 + if (pingExecutor != null) {
  169 + pingExecutor.shutdownNow();
  170 + }
  171 +
162 172 if (executor != null) {
163 173 executor.shutdownNow();
164 174 }
... ... @@ -216,12 +226,18 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
216 226 if (cmdsWrapper.getAlarmDataCmds() != null) {
217 227 cmdsWrapper.getAlarmDataCmds().forEach(cmd -> handleWsAlarmDataCmd(sessionRef, cmd));
218 228 }
  229 + if (cmdsWrapper.getEntityCountCmds() != null) {
  230 + cmdsWrapper.getEntityCountCmds().forEach(cmd -> handleWsEntityCountCmd(sessionRef, cmd));
  231 + }
219 232 if (cmdsWrapper.getEntityDataUnsubscribeCmds() != null) {
220 233 cmdsWrapper.getEntityDataUnsubscribeCmds().forEach(cmd -> handleWsDataUnsubscribeCmd(sessionRef, cmd));
221 234 }
222 235 if (cmdsWrapper.getAlarmDataUnsubscribeCmds() != null) {
223 236 cmdsWrapper.getAlarmDataUnsubscribeCmds().forEach(cmd -> handleWsDataUnsubscribeCmd(sessionRef, cmd));
224 237 }
  238 + if (cmdsWrapper.getEntityCountUnsubscribeCmds() != null) {
  239 + cmdsWrapper.getEntityCountUnsubscribeCmds().forEach(cmd -> handleWsDataUnsubscribeCmd(sessionRef, cmd));
  240 + }
225 241 }
226 242 } catch (IOException e) {
227 243 log.warn("Failed to decode subscription cmd: {}", e.getMessage(), e);
... ... @@ -239,6 +255,16 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
239 255 }
240 256 }
241 257
  258 + private void handleWsEntityCountCmd(TelemetryWebSocketSessionRef sessionRef, EntityCountCmd cmd) {
  259 + String sessionId = sessionRef.getSessionId();
  260 + log.debug("[{}] Processing: {}", sessionId, cmd);
  261 +
  262 + if (validateSessionMetadata(sessionRef, cmd.getCmdId(), sessionId)
  263 + && validateSubscriptionCmd(sessionRef, cmd)) {
  264 + entityDataSubService.handleCmd(sessionRef, cmd);
  265 + }
  266 + }
  267 +
242 268 private void handleWsAlarmDataCmd(TelemetryWebSocketSessionRef sessionRef, AlarmDataCmd cmd) {
243 269 String sessionId = sessionRef.getSessionId();
244 270 log.debug("[{}] Processing: {}", sessionId, cmd);
... ... @@ -264,7 +290,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
264 290 }
265 291
266 292 @Override
267   - public void sendWsMsg(String sessionId, DataUpdate update) {
  293 + public void sendWsMsg(String sessionId, CmdUpdate update) {
268 294 sendWsMsg(sessionId, update.getCmdId(), update);
269 295 }
270 296
... ... @@ -679,6 +705,20 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
679 705 return true;
680 706 }
681 707
  708 + private boolean validateSubscriptionCmd(TelemetryWebSocketSessionRef sessionRef, EntityCountCmd cmd) {
  709 + if (cmd.getCmdId() < 0) {
  710 + TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST,
  711 + "Cmd id is negative value!");
  712 + sendWsMsg(sessionRef, update);
  713 + return false;
  714 + } else if (cmd.getQuery() == null) {
  715 + TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST, "Query is empty!");
  716 + sendWsMsg(sessionRef, update);
  717 + return false;
  718 + }
  719 + return true;
  720 + }
  721 +
682 722 private boolean validateSubscriptionCmd(TelemetryWebSocketSessionRef sessionRef, AlarmDataCmd cmd) {
683 723 if (cmd.getCmdId() < 0) {
684 724 TelemetrySubscriptionUpdate update = new TelemetrySubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST,
... ... @@ -744,6 +784,17 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
744 784 }
745 785 }
746 786
  787 + private void sendPing() {
  788 + long currentTime = System.currentTimeMillis();
  789 + wsSessionsMap.values().forEach(md ->
  790 + executor.submit(() -> {
  791 + try {
  792 + msgEndpoint.sendPing(md.getSessionRef(), currentTime);
  793 + } catch (IOException e) {
  794 + log.warn("[{}] Failed to send ping: {}", md.getSessionRef().getSessionId(), e);
  795 + }
  796 + }));
  797 + }
747 798
748 799 private static Optional<Set<String>> getKeys(TelemetryPluginCmd cmd) {
749 800 if (!StringUtils.isEmpty(cmd.getKeys())) {
... ...
... ... @@ -26,5 +26,7 @@ public interface TelemetryWebSocketMsgEndpoint {
26 26
27 27 void send(TelemetryWebSocketSessionRef sessionRef, int subscriptionId, String msg) throws IOException;
28 28
  29 + void sendPing(TelemetryWebSocketSessionRef sessionRef, long currentTime) throws IOException;
  30 +
29 31 void close(TelemetryWebSocketSessionRef sessionRef, CloseStatus withReason) throws IOException;
30 32 }
... ...
... ... @@ -15,6 +15,7 @@
15 15 */
16 16 package org.thingsboard.server.service.telemetry;
17 17
  18 +import org.thingsboard.server.service.telemetry.cmd.v2.CmdUpdate;
18 19 import org.thingsboard.server.service.telemetry.cmd.v2.DataUpdate;
19 20 import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate;
20 21
... ... @@ -29,6 +30,6 @@ public interface TelemetryWebSocketService {
29 30
30 31 void sendWsMsg(String sessionId, TelemetrySubscriptionUpdate update);
31 32
32   - void sendWsMsg(String sessionId, DataUpdate update);
  33 + void sendWsMsg(String sessionId, CmdUpdate update);
33 34
34 35 }
... ...
... ... @@ -21,6 +21,8 @@ import org.thingsboard.server.service.telemetry.cmd.v1.GetHistoryCmd;
21 21 import org.thingsboard.server.service.telemetry.cmd.v1.TimeseriesSubscriptionCmd;
22 22 import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataCmd;
23 23 import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataUnsubscribeCmd;
  24 +import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountCmd;
  25 +import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountUnsubscribeCmd;
24 26 import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd;
25 27 import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUnsubscribeCmd;
26 28
... ... @@ -46,4 +48,8 @@ public class TelemetryPluginCmdsWrapper {
46 48
47 49 private List<AlarmDataUnsubscribeCmd> alarmDataUnsubscribeCmds;
48 50
  51 + private List<EntityCountCmd> entityCountCmds;
  52 +
  53 + private List<EntityCountUnsubscribeCmd> entityCountUnsubscribeCmds;
  54 +
49 55 }
... ...
... ... @@ -18,14 +18,14 @@ package org.thingsboard.server.service.telemetry.cmd.v2;
18 18 import com.fasterxml.jackson.annotation.JsonCreator;
19 19 import com.fasterxml.jackson.annotation.JsonProperty;
20 20 import lombok.Getter;
21   -import lombok.NoArgsConstructor;
  21 +import lombok.ToString;
22 22 import org.thingsboard.server.common.data.page.PageData;
23 23 import org.thingsboard.server.common.data.query.AlarmData;
24   -import org.thingsboard.server.common.data.query.EntityData;
25 24 import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode;
26 25
27 26 import java.util.List;
28 27
  28 +@ToString
29 29 public class AlarmDataUpdate extends DataUpdate<AlarmData> {
30 30
31 31 @Getter
... ... @@ -44,8 +44,8 @@ public class AlarmDataUpdate extends DataUpdate<AlarmData> {
44 44 }
45 45
46 46 @Override
47   - public DataUpdateType getDataUpdateType() {
48   - return DataUpdateType.ALARM_DATA;
  47 + public CmdUpdateType getCmdUpdateType() {
  48 + return CmdUpdateType.ALARM_DATA;
49 49 }
50 50
51 51 @JsonCreator
... ...
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.service.telemetry.cmd.v2;
  17 +
  18 +import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
  19 +import lombok.AllArgsConstructor;
  20 +import lombok.Data;
  21 +
  22 +@Data
  23 +@AllArgsConstructor
  24 +@JsonIgnoreProperties(ignoreUnknown = true)
  25 +public abstract class CmdUpdate {
  26 +
  27 + private final int cmdId;
  28 + private final int errorCode;
  29 + private final String errorMsg;
  30 +
  31 + public abstract CmdUpdateType getCmdUpdateType();
  32 +
  33 +}
... ...
application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/CmdUpdateType.java renamed from application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/DataUpdateType.java
... ... @@ -15,7 +15,8 @@
15 15 */
16 16 package org.thingsboard.server.service.telemetry.cmd.v2;
17 17
18   -public enum DataUpdateType {
  18 +public enum CmdUpdateType {
19 19 ENTITY_DATA,
20   - ALARM_DATA
  20 + ALARM_DATA,
  21 + COUNT_DATA
21 22 }
... ...
... ... @@ -15,24 +15,24 @@
15 15 */
16 16 package org.thingsboard.server.service.telemetry.cmd.v2;
17 17
18   -import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
19   -import lombok.AllArgsConstructor;
20   -import lombok.Data;
  18 +import lombok.Getter;
21 19 import org.thingsboard.server.common.data.page.PageData;
22 20 import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode;
23 21
24 22 import java.util.List;
25 23
26   -@Data
27   -@AllArgsConstructor
28   -@JsonIgnoreProperties(ignoreUnknown = true)
29   -public abstract class DataUpdate<T> {
  24 +public abstract class DataUpdate<T> extends CmdUpdate {
30 25
31   - private final int cmdId;
  26 + @Getter
32 27 private final PageData<T> data;
  28 + @Getter
33 29 private final List<T> update;
34   - private final int errorCode;
35   - private final String errorMsg;
  30 +
  31 + public DataUpdate(int cmdId, PageData<T> data, List<T> update, int errorCode, String errorMsg) {
  32 + super(cmdId, errorCode, errorMsg);
  33 + this.data = data;
  34 + this.update = update;
  35 + }
36 36
37 37 public DataUpdate(int cmdId, PageData<T> data, List<T> update) {
38 38 this(cmdId, data, update, SubscriptionErrorCode.NO_ERROR.getCode(), null);
... ... @@ -42,5 +42,4 @@ public abstract class DataUpdate<T> {
42 42 this(cmdId, null, null, errorCode, errorMsg);
43 43 }
44 44
45   - public abstract DataUpdateType getDataUpdateType();
46 45 }
... ...
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.service.telemetry.cmd.v2;
  17 +
  18 +import com.fasterxml.jackson.annotation.JsonCreator;
  19 +import com.fasterxml.jackson.annotation.JsonProperty;
  20 +import lombok.Getter;
  21 +import org.thingsboard.server.common.data.query.EntityCountQuery;
  22 +import org.thingsboard.server.common.data.query.EntityDataQuery;
  23 +
  24 +public class EntityCountCmd extends DataCmd {
  25 +
  26 + @Getter
  27 + private final EntityCountQuery query;
  28 +
  29 + @JsonCreator
  30 + public EntityCountCmd(@JsonProperty("cmdId") int cmdId,
  31 + @JsonProperty("query") EntityCountQuery query) {
  32 + super(cmdId);
  33 + this.query = query;
  34 + }
  35 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.service.telemetry.cmd.v2;
  17 +
  18 +import lombok.Data;
  19 +
  20 +@Data
  21 +public class EntityCountUnsubscribeCmd implements UnsubscribeCmd {
  22 +
  23 + private final int cmdId;
  24 +
  25 +}
... ...