Commit f673b0944bd08c144b0ffb8d09ce6c29ac25a082
Committed by
GitHub
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; | ... | ... |
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java
... | ... | @@ -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() { | ... | ... |
application/src/main/java/org/thingsboard/server/actors/ruleChain/TbToRuleNodeActorMsg.java
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2021 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.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 | ... | ... |
application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java
... | ... | @@ -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); | ... | ... |
application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java
... | ... | @@ -36,6 +36,9 @@ import org.thingsboard.server.common.data.TenantProfile; |
36 | 36 | import org.thingsboard.server.common.data.User; |
37 | 37 | import org.thingsboard.server.common.data.alarm.AlarmSeverity; |
38 | 38 | import org.thingsboard.server.common.data.device.profile.AlarmCondition; |
39 | +import org.thingsboard.server.common.data.device.profile.AlarmConditionFilter; | |
40 | +import org.thingsboard.server.common.data.device.profile.AlarmConditionFilterKey; | |
41 | +import org.thingsboard.server.common.data.device.profile.AlarmConditionKeyType; | |
39 | 42 | import org.thingsboard.server.common.data.device.profile.AlarmRule; |
40 | 43 | import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; |
41 | 44 | import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration; |
... | ... | @@ -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 | } | ... | ... |
... | ... | @@ -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") | ... | ... |
application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MModelsRepository.java
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2021 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.service.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); | ... | ... |
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CookieUtils.java
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2021 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.service.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 | } | ... | ... |
application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractSubCtx.java
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2021 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.service.subscription; | |
17 | + | |
18 | +import com.google.common.util.concurrent.Futures; | |
19 | +import com.google.common.util.concurrent.ListenableFuture; | |
20 | +import com.google.common.util.concurrent.MoreExecutors; | |
21 | +import lombok.Data; | |
22 | +import lombok.Getter; | |
23 | +import lombok.Setter; | |
24 | +import lombok.extern.slf4j.Slf4j; | |
25 | +import org.thingsboard.server.common.data.id.CustomerId; | |
26 | +import org.thingsboard.server.common.data.id.EntityId; | |
27 | +import org.thingsboard.server.common.data.id.TenantId; | |
28 | +import org.thingsboard.server.common.data.id.UserId; | |
29 | +import org.thingsboard.server.common.data.kv.AttributeKvEntry; | |
30 | +import org.thingsboard.server.common.data.query.ComplexFilterPredicate; | |
31 | +import org.thingsboard.server.common.data.query.DynamicValue; | |
32 | +import org.thingsboard.server.common.data.query.DynamicValueSourceType; | |
33 | +import org.thingsboard.server.common.data.query.EntityCountQuery; | |
34 | +import org.thingsboard.server.common.data.query.EntityKeyType; | |
35 | +import org.thingsboard.server.common.data.query.FilterPredicateType; | |
36 | +import org.thingsboard.server.common.data.query.KeyFilter; | |
37 | +import org.thingsboard.server.common.data.query.KeyFilterPredicate; | |
38 | +import org.thingsboard.server.common.data.query.SimpleKeyFilterPredicate; | |
39 | +import org.thingsboard.server.common.data.query.TsValue; | |
40 | +import org.thingsboard.server.dao.attributes.AttributesService; | |
41 | +import org.thingsboard.server.dao.entity.EntityService; | |
42 | +import org.thingsboard.server.service.telemetry.TelemetryWebSocketService; | |
43 | +import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef; | |
44 | +import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate; | |
45 | + | |
46 | +import java.util.ArrayList; | |
47 | +import java.util.HashMap; | |
48 | +import java.util.List; | |
49 | +import java.util.Map; | |
50 | +import java.util.Optional; | |
51 | +import java.util.Set; | |
52 | +import java.util.concurrent.ConcurrentHashMap; | |
53 | +import java.util.concurrent.ExecutionException; | |
54 | +import java.util.concurrent.ScheduledFuture; | |
55 | + | |
56 | +@Slf4j | |
57 | +@Data | |
58 | +public abstract class TbAbstractSubCtx<T extends EntityCountQuery> { | |
59 | + | |
60 | + protected final String serviceId; | |
61 | + protected final SubscriptionServiceStatistics stats; | |
62 | + protected final TelemetryWebSocketService wsService; | |
63 | + protected final EntityService entityService; | |
64 | + protected final TbLocalSubscriptionService localSubscriptionService; | |
65 | + protected final AttributesService attributesService; | |
66 | + protected final TelemetryWebSocketSessionRef sessionRef; | |
67 | + protected final int cmdId; | |
68 | + protected final Set<Integer> subToDynamicValueKeySet; | |
69 | + @Getter | |
70 | + protected final Map<DynamicValueKey, List<DynamicValue>> dynamicValues; | |
71 | + @Getter | |
72 | + @Setter | |
73 | + protected T query; | |
74 | + @Setter | |
75 | + protected volatile ScheduledFuture<?> refreshTask; | |
76 | + | |
77 | + public TbAbstractSubCtx(String serviceId, TelemetryWebSocketService wsService, | |
78 | + EntityService entityService, TbLocalSubscriptionService localSubscriptionService, | |
79 | + AttributesService attributesService, SubscriptionServiceStatistics stats, | |
80 | + TelemetryWebSocketSessionRef sessionRef, int cmdId) { | |
81 | + this.serviceId = serviceId; | |
82 | + this.wsService = wsService; | |
83 | + this.entityService = entityService; | |
84 | + this.localSubscriptionService = localSubscriptionService; | |
85 | + this.attributesService = attributesService; | |
86 | + this.stats = stats; | |
87 | + this.sessionRef = sessionRef; | |
88 | + this.cmdId = cmdId; | |
89 | + this.subToDynamicValueKeySet = ConcurrentHashMap.newKeySet(); | |
90 | + this.dynamicValues = new ConcurrentHashMap<>(); | |
91 | + } | |
92 | + | |
93 | + public void setAndResolveQuery(T query) { | |
94 | + dynamicValues.clear(); | |
95 | + this.query = query; | |
96 | + if (query != null && query.getKeyFilters() != null) { | |
97 | + for (KeyFilter filter : query.getKeyFilters()) { | |
98 | + registerDynamicValues(filter.getPredicate()); | |
99 | + } | |
100 | + } | |
101 | + resolve(getTenantId(), getCustomerId(), getUserId()); | |
102 | + } | |
103 | + | |
104 | + public void resolve(TenantId tenantId, CustomerId customerId, UserId userId) { | |
105 | + List<ListenableFuture<DynamicValueKeySub>> futures = new ArrayList<>(); | |
106 | + for (DynamicValueKey key : dynamicValues.keySet()) { | |
107 | + switch (key.getSourceType()) { | |
108 | + case CURRENT_TENANT: | |
109 | + futures.add(resolveEntityValue(tenantId, tenantId, key)); | |
110 | + break; | |
111 | + case CURRENT_CUSTOMER: | |
112 | + if (customerId != null && !customerId.isNullUid()) { | |
113 | + futures.add(resolveEntityValue(tenantId, customerId, key)); | |
114 | + } | |
115 | + break; | |
116 | + case CURRENT_USER: | |
117 | + if (userId != null && !userId.isNullUid()) { | |
118 | + futures.add(resolveEntityValue(tenantId, userId, key)); | |
119 | + } | |
120 | + break; | |
121 | + } | |
122 | + } | |
123 | + try { | |
124 | + Map<EntityId, Map<String, DynamicValueKeySub>> tmpSubMap = new HashMap<>(); | |
125 | + for (DynamicValueKeySub sub : Futures.successfulAsList(futures).get()) { | |
126 | + tmpSubMap.computeIfAbsent(sub.getEntityId(), tmp -> new HashMap<>()).put(sub.getKey().getSourceAttribute(), sub); | |
127 | + } | |
128 | + for (EntityId entityId : tmpSubMap.keySet()) { | |
129 | + Map<String, Long> keyStates = new HashMap<>(); | |
130 | + Map<String, DynamicValueKeySub> dynamicValueKeySubMap = tmpSubMap.get(entityId); | |
131 | + dynamicValueKeySubMap.forEach((k, v) -> keyStates.put(k, v.getLastUpdateTs())); | |
132 | + int subIdx = sessionRef.getSessionSubIdSeq().incrementAndGet(); | |
133 | + TbAttributeSubscription sub = TbAttributeSubscription.builder() | |
134 | + .serviceId(serviceId) | |
135 | + .sessionId(sessionRef.getSessionId()) | |
136 | + .subscriptionId(subIdx) | |
137 | + .tenantId(sessionRef.getSecurityCtx().getTenantId()) | |
138 | + .entityId(entityId) | |
139 | + .updateConsumer((s, subscriptionUpdate) -> dynamicValueSubUpdate(s, subscriptionUpdate, dynamicValueKeySubMap)) | |
140 | + .allKeys(false) | |
141 | + .keyStates(keyStates) | |
142 | + .scope(TbAttributeSubscriptionScope.SERVER_SCOPE) | |
143 | + .build(); | |
144 | + subToDynamicValueKeySet.add(subIdx); | |
145 | + localSubscriptionService.addSubscription(sub); | |
146 | + } | |
147 | + } catch (InterruptedException | ExecutionException e) { | |
148 | + log.info("[{}][{}][{}] Failed to resolve dynamic values: {}", tenantId, customerId, userId, dynamicValues.keySet()); | |
149 | + } | |
150 | + | |
151 | + } | |
152 | + | |
153 | + private void dynamicValueSubUpdate(String sessionId, TelemetrySubscriptionUpdate subscriptionUpdate, | |
154 | + Map<String, DynamicValueKeySub> dynamicValueKeySubMap) { | |
155 | + Map<String, TsValue> latestUpdate = new HashMap<>(); | |
156 | + subscriptionUpdate.getData().forEach((k, v) -> { | |
157 | + Object[] data = (Object[]) v.get(0); | |
158 | + latestUpdate.put(k, new TsValue((Long) data[0], (String) data[1])); | |
159 | + }); | |
160 | + | |
161 | + boolean invalidateFilter = false; | |
162 | + for (Map.Entry<String, TsValue> entry : latestUpdate.entrySet()) { | |
163 | + String k = entry.getKey(); | |
164 | + TsValue tsValue = entry.getValue(); | |
165 | + DynamicValueKeySub sub = dynamicValueKeySubMap.get(k); | |
166 | + if (sub.updateValue(tsValue)) { | |
167 | + invalidateFilter = true; | |
168 | + updateDynamicValuesByKey(sub, tsValue); | |
169 | + } | |
170 | + } | |
171 | + | |
172 | + if (invalidateFilter) { | |
173 | + update(); | |
174 | + } | |
175 | + } | |
176 | + | |
177 | + public abstract void fetchData(); | |
178 | + | |
179 | + protected abstract void update(); | |
180 | + | |
181 | + public void clearSubscriptions() { | |
182 | + clearDynamicValueSubscriptions(); | |
183 | + } | |
184 | + | |
185 | + @Data | |
186 | + private static class DynamicValueKeySub { | |
187 | + private final DynamicValueKey key; | |
188 | + private final EntityId entityId; | |
189 | + private long lastUpdateTs; | |
190 | + private String lastUpdateValue; | |
191 | + | |
192 | + boolean updateValue(TsValue value) { | |
193 | + if (value.getTs() > lastUpdateTs && (lastUpdateValue == null || !lastUpdateValue.equals(value.getValue()))) { | |
194 | + this.lastUpdateTs = value.getTs(); | |
195 | + this.lastUpdateValue = value.getValue(); | |
196 | + return true; | |
197 | + } else { | |
198 | + return false; | |
199 | + } | |
200 | + } | |
201 | + } | |
202 | + | |
203 | + private ListenableFuture<DynamicValueKeySub> resolveEntityValue(TenantId tenantId, EntityId entityId, DynamicValueKey key) { | |
204 | + ListenableFuture<Optional<AttributeKvEntry>> entry = attributesService.find(tenantId, entityId, | |
205 | + TbAttributeSubscriptionScope.SERVER_SCOPE.name(), key.getSourceAttribute()); | |
206 | + return Futures.transform(entry, attributeOpt -> { | |
207 | + DynamicValueKeySub sub = new DynamicValueKeySub(key, entityId); | |
208 | + if (attributeOpt.isPresent()) { | |
209 | + AttributeKvEntry attribute = attributeOpt.get(); | |
210 | + sub.setLastUpdateTs(attribute.getLastUpdateTs()); | |
211 | + sub.setLastUpdateValue(attribute.getValueAsString()); | |
212 | + updateDynamicValuesByKey(sub, new TsValue(attribute.getLastUpdateTs(), attribute.getValueAsString())); | |
213 | + } | |
214 | + return sub; | |
215 | + }, MoreExecutors.directExecutor()); | |
216 | + } | |
217 | + | |
218 | + @SuppressWarnings("unchecked") | |
219 | + protected void updateDynamicValuesByKey(DynamicValueKeySub sub, TsValue tsValue) { | |
220 | + DynamicValueKey dvk = sub.getKey(); | |
221 | + switch (dvk.getPredicateType()) { | |
222 | + case STRING: | |
223 | + dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(tsValue.getValue())); | |
224 | + break; | |
225 | + case NUMERIC: | |
226 | + try { | |
227 | + Double dValue = Double.parseDouble(tsValue.getValue()); | |
228 | + dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(dValue)); | |
229 | + } catch (NumberFormatException e) { | |
230 | + dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(null)); | |
231 | + } | |
232 | + break; | |
233 | + case BOOLEAN: | |
234 | + Boolean bValue = Boolean.parseBoolean(tsValue.getValue()); | |
235 | + dynamicValues.get(dvk).forEach(dynamicValue -> dynamicValue.setResolvedValue(bValue)); | |
236 | + break; | |
237 | + } | |
238 | + } | |
239 | + | |
240 | + @SuppressWarnings("unchecked") | |
241 | + private void registerDynamicValues(KeyFilterPredicate predicate) { | |
242 | + switch (predicate.getType()) { | |
243 | + case STRING: | |
244 | + case NUMERIC: | |
245 | + case BOOLEAN: | |
246 | + Optional<DynamicValue> value = getDynamicValueFromSimplePredicate((SimpleKeyFilterPredicate) predicate); | |
247 | + if (value.isPresent()) { | |
248 | + DynamicValue dynamicValue = value.get(); | |
249 | + DynamicValueKey key = new DynamicValueKey( | |
250 | + predicate.getType(), | |
251 | + dynamicValue.getSourceType(), | |
252 | + dynamicValue.getSourceAttribute()); | |
253 | + dynamicValues.computeIfAbsent(key, tmp -> new ArrayList<>()).add(dynamicValue); | |
254 | + } | |
255 | + break; | |
256 | + case COMPLEX: | |
257 | + ((ComplexFilterPredicate) predicate).getPredicates().forEach(this::registerDynamicValues); | |
258 | + } | |
259 | + } | |
260 | + | |
261 | + private Optional<DynamicValue<T>> getDynamicValueFromSimplePredicate(SimpleKeyFilterPredicate<T> predicate) { | |
262 | + if (predicate.getValue().getUserValue() == null) { | |
263 | + return Optional.ofNullable(predicate.getValue().getDynamicValue()); | |
264 | + } else { | |
265 | + return Optional.empty(); | |
266 | + } | |
267 | + } | |
268 | + | |
269 | + public String getSessionId() { | |
270 | + return sessionRef.getSessionId(); | |
271 | + } | |
272 | + | |
273 | + public TenantId getTenantId() { | |
274 | + return sessionRef.getSecurityCtx().getTenantId(); | |
275 | + } | |
276 | + | |
277 | + public CustomerId getCustomerId() { | |
278 | + return sessionRef.getSecurityCtx().getCustomerId(); | |
279 | + } | |
280 | + | |
281 | + public UserId getUserId() { | |
282 | + return sessionRef.getSecurityCtx().getId(); | |
283 | + } | |
284 | + | |
285 | + protected void clearDynamicValueSubscriptions() { | |
286 | + if (subToDynamicValueKeySet != null) { | |
287 | + for (Integer subId : subToDynamicValueKeySet) { | |
288 | + localSubscriptionService.cancelSubscription(sessionRef.getSessionId(), subId); | |
289 | + } | |
290 | + subToDynamicValueKeySet.clear(); | |
291 | + } | |
292 | + } | |
293 | + | |
294 | + public void setRefreshTask(ScheduledFuture<?> task) { | |
295 | + this.refreshTask = task; | |
296 | + } | |
297 | + | |
298 | + public void cancelTasks() { | |
299 | + if (this.refreshTask != null) { | |
300 | + log.trace("[{}][{}] Canceling old refresh task", sessionRef.getSessionId(), cmdId); | |
301 | + this.refreshTask.cancel(true); | |
302 | + } | |
303 | + } | |
304 | + | |
305 | + @Data | |
306 | + public static class DynamicValueKey { | |
307 | + @Getter | |
308 | + private final FilterPredicateType predicateType; | |
309 | + @Getter | |
310 | + private final DynamicValueSourceType sourceType; | |
311 | + @Getter | |
312 | + private final String sourceAttribute; | |
313 | + } | |
314 | + | |
315 | +} | ... | ... |
... | ... | @@ -90,8 +90,7 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx<AlarmDataQuery> { |
90 | 90 | AlarmDataUpdate update; |
91 | 91 | if (!entitiesMap.isEmpty()) { |
92 | 92 | long start = System.currentTimeMillis(); |
93 | - PageData<AlarmData> alarms = alarmService.findAlarmDataByQueryForEntities(getTenantId(), getCustomerId(), | |
94 | - query, getOrderedEntityIds()); | |
93 | + PageData<AlarmData> alarms = alarmService.findAlarmDataByQueryForEntities(getTenantId(), getCustomerId(), query, getOrderedEntityIds()); | |
95 | 94 | long end = System.currentTimeMillis(); |
96 | 95 | stats.getAlarmQueryInvocationCnt().incrementAndGet(); |
97 | 96 | stats.getAlarmQueryTimeSpent().addAndGet(end - start); | ... | ... |
application/src/main/java/org/thingsboard/server/service/subscription/TbEntityCountSubCtx.java
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2021 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.service.subscription; | |
17 | + | |
18 | +import lombok.extern.slf4j.Slf4j; | |
19 | +import org.thingsboard.server.common.data.query.EntityCountQuery; | |
20 | +import org.thingsboard.server.common.data.query.EntityKeyType; | |
21 | +import org.thingsboard.server.dao.attributes.AttributesService; | |
22 | +import org.thingsboard.server.dao.entity.EntityService; | |
23 | +import org.thingsboard.server.service.telemetry.TelemetryWebSocketService; | |
24 | +import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef; | |
25 | +import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountUpdate; | |
26 | +import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate; | |
27 | +import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate; | |
28 | + | |
29 | +@Slf4j | |
30 | +public class TbEntityCountSubCtx extends TbAbstractSubCtx<EntityCountQuery> { | |
31 | + | |
32 | + private volatile int result; | |
33 | + | |
34 | + public TbEntityCountSubCtx(String serviceId, TelemetryWebSocketService wsService, EntityService entityService, | |
35 | + TbLocalSubscriptionService localSubscriptionService, AttributesService attributesService, | |
36 | + SubscriptionServiceStatistics stats, TelemetryWebSocketSessionRef sessionRef, int cmdId) { | |
37 | + super(serviceId, wsService, entityService, localSubscriptionService, attributesService, stats, sessionRef, cmdId); | |
38 | + } | |
39 | + | |
40 | + @Override | |
41 | + public void fetchData() { | |
42 | + result = (int) entityService.countEntitiesByQuery(getTenantId(), getCustomerId(), query); | |
43 | + wsService.sendWsMsg(sessionRef.getSessionId(), new EntityCountUpdate(cmdId, result)); | |
44 | + } | |
45 | + | |
46 | + @Override | |
47 | + protected void update() { | |
48 | + int newCount = (int) entityService.countEntitiesByQuery(getTenantId(), getCustomerId(), query); | |
49 | + if (newCount != result) { | |
50 | + result = newCount; | |
51 | + wsService.sendWsMsg(sessionRef.getSessionId(), new EntityCountUpdate(cmdId, result)); | |
52 | + } | |
53 | + } | |
54 | + | |
55 | +} | ... | ... |
... | ... | @@ -17,6 +17,7 @@ package org.thingsboard.server.service.subscription; |
17 | 17 | |
18 | 18 | import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef; |
19 | 19 | import org.thingsboard.server.service.telemetry.cmd.v2.AlarmDataCmd; |
20 | +import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountCmd; | |
20 | 21 | import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd; |
21 | 22 | import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUnsubscribeCmd; |
22 | 23 | import org.thingsboard.server.service.telemetry.cmd.v2.UnsubscribeCmd; |
... | ... | @@ -25,6 +26,8 @@ public interface TbEntityDataSubscriptionService { |
25 | 26 | |
26 | 27 | void handleCmd(TelemetryWebSocketSessionRef sessionId, EntityDataCmd cmd); |
27 | 28 | |
29 | + void handleCmd(TelemetryWebSocketSessionRef sessionId, EntityCountCmd cmd); | |
30 | + | |
28 | 31 | void handleCmd(TelemetryWebSocketSessionRef sessionId, AlarmDataCmd cmd); |
29 | 32 | |
30 | 33 | void cancelSubscription(String sessionId, UnsubscribeCmd subscriptionId); | ... | ... |
... | ... | @@ -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,24 +15,24 @@ |
15 | 15 | */ |
16 | 16 | package org.thingsboard.server.service.telemetry.cmd.v2; |
17 | 17 | |
18 | -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; | |
19 | -import lombok.AllArgsConstructor; | |
20 | -import lombok.Data; | |
18 | +import lombok.Getter; | |
21 | 19 | import org.thingsboard.server.common.data.page.PageData; |
22 | 20 | import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode; |
23 | 21 | |
24 | 22 | import java.util.List; |
25 | 23 | |
26 | -@Data | |
27 | -@AllArgsConstructor | |
28 | -@JsonIgnoreProperties(ignoreUnknown = true) | |
29 | -public abstract class DataUpdate<T> { | |
24 | +public abstract class DataUpdate<T> extends CmdUpdate { | |
30 | 25 | |
31 | - private final int cmdId; | |
26 | + @Getter | |
32 | 27 | private final PageData<T> data; |
28 | + @Getter | |
33 | 29 | private final List<T> update; |
34 | - private final int errorCode; | |
35 | - private final String errorMsg; | |
30 | + | |
31 | + public DataUpdate(int cmdId, PageData<T> data, List<T> update, int errorCode, String errorMsg) { | |
32 | + super(cmdId, errorCode, errorMsg); | |
33 | + this.data = data; | |
34 | + this.update = update; | |
35 | + } | |
36 | 36 | |
37 | 37 | public DataUpdate(int cmdId, PageData<T> data, List<T> update) { |
38 | 38 | this(cmdId, data, update, SubscriptionErrorCode.NO_ERROR.getCode(), null); |
... | ... | @@ -42,5 +42,4 @@ public abstract class DataUpdate<T> { |
42 | 42 | this(cmdId, null, null, errorCode, errorMsg); |
43 | 43 | } |
44 | 44 | |
45 | - public abstract DataUpdateType getDataUpdateType(); | |
46 | 45 | } | ... | ... |
application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityCountCmd.java
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2021 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.service.telemetry.cmd.v2; | |
17 | + | |
18 | +import com.fasterxml.jackson.annotation.JsonCreator; | |
19 | +import com.fasterxml.jackson.annotation.JsonProperty; | |
20 | +import lombok.Getter; | |
21 | +import org.thingsboard.server.common.data.query.EntityCountQuery; | |
22 | +import org.thingsboard.server.common.data.query.EntityDataQuery; | |
23 | + | |
24 | +public class EntityCountCmd extends DataCmd { | |
25 | + | |
26 | + @Getter | |
27 | + private final EntityCountQuery query; | |
28 | + | |
29 | + @JsonCreator | |
30 | + public EntityCountCmd(@JsonProperty("cmdId") int cmdId, | |
31 | + @JsonProperty("query") EntityCountQuery query) { | |
32 | + super(cmdId); | |
33 | + this.query = query; | |
34 | + } | |
35 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2021 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.service.telemetry.cmd.v2; | |
17 | + | |
18 | +import lombok.Data; | |
19 | + | |
20 | +@Data | |
21 | +public class EntityCountUnsubscribeCmd implements UnsubscribeCmd { | |
22 | + | |
23 | + private final int cmdId; | |
24 | + | |
25 | +} | ... | ... |